From 312f8355eef0d646429350ff8d7ea3b8562ef158 Mon Sep 17 00:00:00 2001 From: jyn Date: Sun, 24 Dec 2023 19:49:23 -0500 Subject: [PATCH 1/5] don't show the full linker args unless `--verbose` is passed the linker arguments can be *very* long, especially for crates with many dependencies. often they are not useful. omit them unless the user specifically requests them. --- compiler/rustc_codegen_ssa/src/back/link.rs | 2 +- compiler/rustc_codegen_ssa/src/errors.rs | 9 ++++++- .../src/external_deps/rustc.rs | 6 +++++ tests/run-make/link-args-order/rmake.rs | 6 +++-- tests/run-make/link-dedup/rmake.rs | 12 ++++----- tests/run-make/linker-warning/fake-linker.rs | 13 ++++++++++ tests/run-make/linker-warning/main.rs | 1 + tests/run-make/linker-warning/rmake.rs | 26 +++++++++++++++++++ 8 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 tests/run-make/linker-warning/fake-linker.rs create mode 100644 tests/run-make/linker-warning/main.rs create mode 100644 tests/run-make/linker-warning/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 651485a18763c..487ef3f88a84f 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -998,12 +998,12 @@ fn link_natively( let mut output = prog.stderr.clone(); output.extend_from_slice(&prog.stdout); let escaped_output = escape_linker_output(&output, flavor); - // FIXME: Add UI tests for this error. let err = errors::LinkingFailed { linker_path: &linker_path, exit_status: prog.status, command: &cmd, escaped_output, + verbose: sess.opts.verbose, }; sess.dcx().emit_err(err); // If MSVC's `link.exe` was expected but the return code diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index f93cb52ea3ea2..2dfebf3e8f060 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -349,6 +349,7 @@ pub(crate) struct LinkingFailed<'a> { pub exit_status: ExitStatus, pub command: &'a Command, pub escaped_output: String, + pub verbose: bool, } impl Diagnostic<'_, G> for LinkingFailed<'_> { @@ -359,7 +360,13 @@ impl Diagnostic<'_, G> for LinkingFailed<'_> { let contains_undefined_ref = self.escaped_output.contains("undefined reference to"); - diag.note(format!("{:?}", self.command)).note(self.escaped_output); + if self.verbose { + diag.note(format!("{:?}", self.command)); + } else { + diag.note("use `--verbose` to show all linker arguments"); + } + + diag.note(self.escaped_output); // Trying to match an error from OS linkers // which by now we have no way to translate. diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index 494daeca96364..e09fd4e796bc3 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -319,6 +319,12 @@ impl Rustc { self } + /// Pass the `--verbose` flag. + pub fn verbose(&mut self) -> &mut Self { + self.cmd.arg("--verbose"); + self + } + /// `EXTRARSCXXFLAGS` pub fn extra_rs_cxx_flags(&mut self) -> &mut Self { // Adapted from tools.mk (trimmed): diff --git a/tests/run-make/link-args-order/rmake.rs b/tests/run-make/link-args-order/rmake.rs index b7ef8333267f2..fe0d02926eff4 100644 --- a/tests/run-make/link-args-order/rmake.rs +++ b/tests/run-make/link-args-order/rmake.rs @@ -15,8 +15,9 @@ fn main() { .link_args("b c") .link_args("d e") .link_arg("f") + .arg("--print=link-args") .run_fail() - .assert_stderr_contains(r#""a" "b" "c" "d" "e" "f""#); + .assert_stdout_contains(r#""a" "b" "c" "d" "e" "f""#); rustc() .input("empty.rs") .linker_flavor(linker) @@ -24,6 +25,7 @@ fn main() { .arg("-Zpre-link-args=b c") .arg("-Zpre-link-args=d e") .arg("-Zpre-link-arg=f") + .arg("--print=link-args") .run_fail() - .assert_stderr_contains(r#""a" "b" "c" "d" "e" "f""#); + .assert_stdout_contains(r#""a" "b" "c" "d" "e" "f""#); } diff --git a/tests/run-make/link-dedup/rmake.rs b/tests/run-make/link-dedup/rmake.rs index 6075f31095424..f38603dee8cbb 100644 --- a/tests/run-make/link-dedup/rmake.rs +++ b/tests/run-make/link-dedup/rmake.rs @@ -14,13 +14,13 @@ fn main() { rustc().input("depb.rs").run(); rustc().input("depc.rs").run(); - let output = rustc().input("empty.rs").cfg("bar").run_fail(); - output.assert_stderr_contains(needle_from_libs(&["testa", "testb", "testa"])); + let output = rustc().input("empty.rs").cfg("bar").arg("--print=link-args").run_fail(); + output.assert_stdout_contains(needle_from_libs(&["testa", "testb", "testa"])); - let output = rustc().input("empty.rs").run_fail(); - output.assert_stderr_contains(needle_from_libs(&["testa"])); - output.assert_stderr_not_contains(needle_from_libs(&["testb"])); - output.assert_stderr_not_contains(needle_from_libs(&["testa", "testa", "testa"])); + let output = rustc().input("empty.rs").arg("--print=link-args").run_fail(); + output.assert_stdout_contains(needle_from_libs(&["testa"])); + output.assert_stdout_not_contains(needle_from_libs(&["testb"])); + output.assert_stdout_not_contains(needle_from_libs(&["testa", "testa", "testa"])); // Adjacent identical native libraries are no longer deduplicated if // they come from different crates (https://github.com/rust-lang/rust/pull/103311) // so the following will fail: diff --git a/tests/run-make/linker-warning/fake-linker.rs b/tests/run-make/linker-warning/fake-linker.rs new file mode 100644 index 0000000000000..30497eea2ccd9 --- /dev/null +++ b/tests/run-make/linker-warning/fake-linker.rs @@ -0,0 +1,13 @@ +fn main() { + for arg in std::env::args() { + match &*arg { + "run_make_info" => println!("foo"), + "run_make_warn" => eprintln!("warning: bar"), + "run_make_error" => { + eprintln!("error: baz"); + std::process::exit(1); + } + _ => (), + } + } +} diff --git a/tests/run-make/linker-warning/main.rs b/tests/run-make/linker-warning/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/linker-warning/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs new file mode 100644 index 0000000000000..6de87190c313d --- /dev/null +++ b/tests/run-make/linker-warning/rmake.rs @@ -0,0 +1,26 @@ +use std::path::Path; + +use run_make_support::rfs::remove_file; +use run_make_support::{Rustc, rustc}; + +fn run_rustc() -> Rustc { + let mut rustc = rustc(); + rustc.arg("main.rs").output("main").linker("./fake-linker"); + rustc +} + +fn main() { + // first, compile our linker + rustc().arg("fake-linker.rs").output("fake-linker").run(); + + // Make sure we don't show the linker args unless `--verbose` is passed + run_rustc() + .link_arg("run_make_error") + .verbose() + .run_fail() + .assert_stderr_contains_regex("fake-linker.*run_make_error"); + run_rustc() + .link_arg("run_make_error") + .run_fail() + .assert_stderr_not_contains_regex("fake-linker.*run_make_error"); +} From 5f62ccba8199864bfac2b81a13b7d9b9b5d602b9 Mon Sep 17 00:00:00 2001 From: jyn Date: Sun, 24 Dec 2023 18:38:00 -0500 Subject: [PATCH 2/5] show linker warnings even if it returns 0 --- compiler/rustc_codegen_ssa/messages.ftl | 2 ++ compiler/rustc_codegen_ssa/src/back/link.rs | 27 +++++++++++++++++-- compiler/rustc_codegen_ssa/src/base.rs | 4 ++- compiler/rustc_codegen_ssa/src/lib.rs | 1 + tests/run-make/linker-warning/fake-linker.sh | 17 ++++++++++++ tests/run-make/linker-warning/rmake.rs | 19 +++++++++++++ .../rust-lld-by-default-nightly/rmake.rs | 17 ++++-------- .../run-make/rust-lld-custom-target/rmake.rs | 5 ++-- tests/run-make/rust-lld/rmake.rs | 9 ++++--- 9 files changed, 80 insertions(+), 21 deletions(-) create mode 100755 tests/run-make/linker-warning/fake-linker.sh diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 62db3d5a98cdd..2a84b4ea57f91 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -183,6 +183,8 @@ codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker codegen_ssa_linker_not_found = linker `{$linker_path}` not found .note = {$error} +codegen_ssa_linker_output = {$inner} + codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 487ef3f88a84f..ff40ff56ade29 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -18,6 +18,7 @@ use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, FatalError}; use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize}; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; +use rustc_macros::Diagnostic; use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file}; use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs}; use rustc_middle::bug; @@ -762,6 +763,14 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out } } +#[derive(Diagnostic)] +#[diag(codegen_ssa_linker_output)] +/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just +/// end up with inconsistent languages within the same diagnostic. +struct LinkerOutput { + inner: String, +} + /// Create a dynamic library or executable. /// /// This will invoke the system linker/cc to create the resulting file. This links to all upstream @@ -1045,8 +1054,22 @@ fn link_natively( sess.dcx().abort_if_errors(); } - info!("linker stderr:\n{}", escape_string(&prog.stderr)); - info!("linker stdout:\n{}", escape_string(&prog.stdout)); + + if !prog.stderr.is_empty() { + // We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present. + let stderr = escape_string(&prog.stderr); + debug!("original stderr: {stderr}"); + let stderr = stderr + .strip_prefix("warning: ") + .unwrap_or(&stderr) + .replace(": warning: ", ": "); + sess.dcx().emit_warn(LinkerOutput { inner: format!("linker stderr: {stderr}") }); + } + if !prog.stdout.is_empty() && sess.opts.verbose { + sess.dcx().emit_warn(LinkerOutput { + inner: format!("linker stdout: {}", escape_string(&prog.stdout)), + }); + } } Err(e) => { let linker_not_found = e.kind() == io::ErrorKind::NotFound; diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 27c9cb0b31edd..a2b21583ff6e2 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -45,7 +45,8 @@ use crate::mir::operand::OperandValue; use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ - CachedModuleCodegen, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind, errors, meth, mir, + CachedModuleCodegen, CodegenLintLevels, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind, + errors, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -926,6 +927,7 @@ impl CrateInfo { dependency_formats: Lrc::clone(tcx.dependency_formats(())), windows_subsystem, natvis_debugger_visualizers: Default::default(), + lint_levels: CodegenLintLevels::from_tcx(tcx), }; info.native_libraries.reserve(n_crates); diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 7dc8ab38a9764..be9ac7b748c7a 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -200,6 +200,7 @@ pub struct CrateInfo { pub dependency_formats: Lrc, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, + pub lint_levels: CodegenLintLevels, } #[derive(Encodable, Decodable)] diff --git a/tests/run-make/linker-warning/fake-linker.sh b/tests/run-make/linker-warning/fake-linker.sh new file mode 100755 index 0000000000000..ed4d472c3bfbd --- /dev/null +++ b/tests/run-make/linker-warning/fake-linker.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +code=0 +while ! [ $# = 0 ]; do + case "$1" in + run_make_info) echo "foo" + ;; + run_make_warn) echo "warning: bar" >&2 + ;; + run_make_error) echo "error: baz" >&2; code=1 + ;; + *) ;; # rustc passes lots of args we don't care about + esac + shift +done + +exit $code diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs index 6de87190c313d..fab4172a458ca 100644 --- a/tests/run-make/linker-warning/rmake.rs +++ b/tests/run-make/linker-warning/rmake.rs @@ -13,6 +13,25 @@ fn main() { // first, compile our linker rustc().arg("fake-linker.rs").output("fake-linker").run(); + // Run rustc with our fake linker, and make sure it shows warnings + let warnings = run_rustc().link_arg("run_make_warn").run(); + warnings.assert_stderr_contains("warning: linker stderr: bar"); + + // Make sure it shows stdout, but only when --verbose is passed + run_rustc() + .link_arg("run_make_info") + .verbose() + .run() + .assert_stderr_contains("warning: linker stdout: foo"); + run_rustc() + .link_arg("run_make_info") + .run() + .assert_stderr_not_contains("warning: linker stdout: foo"); + + // Make sure we short-circuit this new path if the linker exits with an error + // (so the diagnostic is less verbose) + run_rustc().link_arg("run_make_error").run_fail().assert_stderr_contains("note: error: baz"); + // Make sure we don't show the linker args unless `--verbose` is passed run_rustc() .link_arg("run_make_error") diff --git a/tests/run-make/rust-lld-by-default-nightly/rmake.rs b/tests/run-make/rust-lld-by-default-nightly/rmake.rs index 02bbe8227f032..4026af8306450 100644 --- a/tests/run-make/rust-lld-by-default-nightly/rmake.rs +++ b/tests/run-make/rust-lld-by-default-nightly/rmake.rs @@ -12,11 +12,7 @@ use run_make_support::rustc; fn main() { // A regular compilation should use rust-lld by default. We'll check that by asking the linker // to display its version number with a link-arg. - let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") - .link_arg("-Wl,-v") - .input("main.rs") - .run(); + let output = rustc().verbose().link_arg("-Wl,-v").input("main.rs").run(); assert!( find_lld_version_in_logs(output.stderr_utf8()), "the LLD version string should be present in the output logs:\n{}", @@ -24,12 +20,8 @@ fn main() { ); // But it can still be disabled by turning the linker feature off. - let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") - .link_arg("-Wl,-v") - .arg("-Zlinker-features=-lld") - .input("main.rs") - .run(); + let output = + rustc().verbose().link_arg("-Wl,-v").arg("-Zlinker-features=-lld").input("main.rs").run(); assert!( !find_lld_version_in_logs(output.stderr_utf8()), "the LLD version string should not be present in the output logs:\n{}", @@ -38,6 +30,7 @@ fn main() { } fn find_lld_version_in_logs(stderr: String) -> bool { - let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); + let lld_version_re = + Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); stderr.lines().any(|line| lld_version_re.is_match(line.trim())) } diff --git a/tests/run-make/rust-lld-custom-target/rmake.rs b/tests/run-make/rust-lld-custom-target/rmake.rs index a6f7c33793afe..7976cd9c2c1dd 100644 --- a/tests/run-make/rust-lld-custom-target/rmake.rs +++ b/tests/run-make/rust-lld-custom-target/rmake.rs @@ -15,7 +15,7 @@ fn main() { // Compile to a custom target spec with rust-lld enabled by default. We'll check that by asking // the linker to display its version number with a link-arg. let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") + .verbose() .crate_type("cdylib") .target("custom-target.json") .link_arg("-Wl,-v") @@ -44,6 +44,7 @@ fn main() { } fn find_lld_version_in_logs(stderr: String) -> bool { - let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); + let lld_version_re = + Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); stderr.lines().any(|line| lld_version_re.is_match(line.trim())) } diff --git a/tests/run-make/rust-lld/rmake.rs b/tests/run-make/rust-lld/rmake.rs index 1f311af1ed591..732bc6f6ba2f9 100644 --- a/tests/run-make/rust-lld/rmake.rs +++ b/tests/run-make/rust-lld/rmake.rs @@ -14,10 +14,10 @@ fn main() { // Opt-in to lld and the self-contained linker, to link with rust-lld. We'll check that by // asking the linker to display its version number with a link-arg. let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") .arg("-Zlinker-features=+lld") .arg("-Clink-self-contained=+linker") .arg("-Zunstable-options") + .verbose() .link_arg(linker_version_flag) .input("main.rs") .run(); @@ -29,8 +29,8 @@ fn main() { // It should not be used when we explicitly opt-out of lld. let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") .link_arg(linker_version_flag) + .verbose() .arg("-Zlinker-features=-lld") .input("main.rs") .run(); @@ -43,8 +43,8 @@ fn main() { // While we're here, also check that the last linker feature flag "wins" when passed multiple // times to rustc. let output = rustc() - .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info") .link_arg(linker_version_flag) + .verbose() .arg("-Clink-self-contained=+linker") .arg("-Zunstable-options") .arg("-Zlinker-features=-lld") @@ -60,6 +60,7 @@ fn main() { } fn find_lld_version_in_logs(stderr: String) -> bool { - let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); + let lld_version_re = + Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap(); stderr.lines().any(|line| lld_version_re.is_match(line.trim())) } From 5e51d983c4741ce610f7f7b1adfb56361cbc1a4d Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 16 Oct 2024 01:14:10 -0400 Subject: [PATCH 3/5] make it possible to silence linker warnings with a crate-level attribute this was slightly complicated because codegen_ssa doesn't have access to a tcx. --- compiler/rustc_codegen_ssa/src/back/link.rs | 21 ++++++--- compiler/rustc_codegen_ssa/src/lib.rs | 21 +++++++++ compiler/rustc_lint/src/levels.rs | 21 +++++---- compiler/rustc_lint_defs/src/builtin.rs | 34 +++++++++++++++ compiler/rustc_lint_defs/src/lib.rs | 14 +++++- compiler/rustc_middle/src/lint.rs | 48 +++++++++++++-------- 6 files changed, 125 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index ff40ff56ade29..6af952739a60e 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -15,13 +15,14 @@ use rustc_ast::CRATE_NODE_ID; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::temp_dir::MaybeTempDir; -use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, FatalError}; +use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, FatalError, LintDiagnostic}; use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize}; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; -use rustc_macros::Diagnostic; +use rustc_macros::LintDiagnostic; use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file}; use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs}; use rustc_middle::bug; +use rustc_middle::lint::lint_level; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::SymbolExportKind; @@ -30,6 +31,7 @@ use rustc_session::config::{ OutputType, PrintKind, SplitDwarfKind, Strip, }; use rustc_session::cstore::DllImport; +use rustc_session::lint::builtin::LINKER_MESSAGES; use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename}; use rustc_session::search_paths::PathKind; use rustc_session::utils::NativeLibKind; @@ -763,7 +765,7 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out } } -#[derive(Diagnostic)] +#[derive(LintDiagnostic)] #[diag(codegen_ssa_linker_output)] /// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just /// end up with inconsistent languages within the same diagnostic. @@ -1055,6 +1057,13 @@ fn link_natively( sess.dcx().abort_if_errors(); } + let (level, src) = codegen_results.crate_info.lint_levels.linker_messages; + let lint = |msg| { + lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| { + LinkerOutput { inner: msg }.decorate_lint(diag) + }) + }; + if !prog.stderr.is_empty() { // We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present. let stderr = escape_string(&prog.stderr); @@ -1063,12 +1072,10 @@ fn link_natively( .strip_prefix("warning: ") .unwrap_or(&stderr) .replace(": warning: ", ": "); - sess.dcx().emit_warn(LinkerOutput { inner: format!("linker stderr: {stderr}") }); + lint(format!("linker stderr: {stderr}")); } if !prog.stdout.is_empty() && sess.opts.verbose { - sess.dcx().emit_warn(LinkerOutput { - inner: format!("linker stdout: {}", escape_string(&prog.stdout)), - }); + lint(format!("linker stdout: {}", escape_string(&prog.stdout))) } } Err(e) => { diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index be9ac7b748c7a..5d9454946a63e 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -29,18 +29,23 @@ use rustc_ast as ast; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::sync::Lrc; use rustc_data_structures::unord::UnordMap; +use rustc_hir::CRATE_HIR_ID; use rustc_hir::def_id::CrateNum; use rustc_macros::{Decodable, Encodable, HashStable}; use rustc_middle::dep_graph::WorkProduct; +use rustc_middle::lint::LintLevelSource; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Dependencies; use rustc_middle::middle::exported_symbols::SymbolExportKind; +use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; use rustc_serialize::opaque::{FileEncoder, MemDecoder}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT}; use rustc_session::cstore::{self, CrateSource}; +use rustc_session::lint::Level; +use rustc_session::lint::builtin::LINKER_MESSAGES; use rustc_session::utils::NativeLibKind; use rustc_span::symbol::Symbol; @@ -303,3 +308,19 @@ impl CodegenResults { Ok((codegen_results, outputs)) } } + +/// A list of lint levels used in codegen. +/// +/// When using `-Z link-only`, we don't have access to the tcx and must work +/// solely from the `.rlink` file. `Lint`s are defined too early to be encodeable. +/// Instead, encode exactly the information we need. +#[derive(Copy, Clone, Debug, Encodable, Decodable)] +pub struct CodegenLintLevels { + linker_messages: (Level, LintLevelSource), +} + +impl CodegenLintLevels { + pub fn from_tcx(tcx: TyCtxt<'_>) -> Self { + Self { linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID) } + } +} diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 4b1dafbdbeea2..951a8a1861817 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -130,12 +130,17 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet { !has_future_breakage && !lint.eval_always }) .filter_map(|lint| { - let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID); - if matches!(lint_level, (Level::Allow, ..)) - || (matches!(lint_level, (.., LintLevelSource::Default))) - && lint.default_level(tcx.sess.edition()) == Level::Allow - { - Some(LintId::of(lint)) + if !lint.eval_always { + let lint_level = + map.lint_level_id_at_node(Some(tcx), tcx.sess, LintId::of(lint), CRATE_HIR_ID); + if matches!(lint_level, (Level::Allow, ..)) + || (matches!(lint_level, (.., LintLevelSource::Default))) + && lint.default_level(tcx.sess.edition()) == Level::Allow + { + Some(LintId::of(lint)) + } else { + None + } } else { None } @@ -248,8 +253,8 @@ impl LintLevelsProvider for LintLevelQueryMap<'_> { fn insert(&mut self, id: LintId, lvl: LevelAndSource) { self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); } - fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { - self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) + fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource { + self.specs.lint_level_id_at_node(Some(self.tcx), sess, LintId::of(lint), self.cur) } fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { self.specs.expectations.push((id, expectation)) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 3b406c7e161a0..2f896f58aeafd 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -60,6 +60,7 @@ declare_lint_pass! { LARGE_ASSIGNMENTS, LATE_BOUND_LIFETIME_ARGUMENTS, LEGACY_DERIVE_HELPERS, + LINKER_MESSAGES, LONG_RUNNING_CONST_EVAL, LOSSY_PROVENANCE_CASTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, @@ -4080,6 +4081,39 @@ declare_lint! { "call to foreign functions or function pointers with FFI-unwind ABI" } +declare_lint! { + /// The `linker_messages` lint forwards warnings from the linker. + /// + /// ### Example + /// + /// ```rust,ignore (needs CLI args, platform-specific) + /// extern "C" { + /// fn foo(); + /// } + /// fn main () { unsafe { foo(); } } + /// ``` + /// + /// On Linux, using `gcc -Wl,--warn-unresolved-symbols` as a linker, this will produce + /// + /// ```text + /// warning: linker stderr: rust-lld: undefined symbol: foo + /// >>> referenced by rust_out.69edbd30df4ae57d-cgu.0 + /// >>> rust_out.rust_out.69edbd30df4ae57d-cgu.0.rcgu.o:(rust_out::main::h3a90094b06757803) + /// | + /// = note: `#[warn(linker_messages)]` on by default + /// + /// warning: 1 warning emitted + /// ``` + /// + /// ### Explanation + /// + /// Linkers emit platform-specific and program-specific warnings that cannot be predicted in advance by the rust compiler. + /// They are forwarded by default, but can be disabled by adding `#![allow(linker_messages)]` at the crate root. + pub LINKER_MESSAGES, + Warn, + "warnings emitted at runtime by the target-specific linker program" +} + declare_lint! { /// The `named_arguments_used_positionally` lint detects cases where named arguments are only /// used positionally in format strings. This usage is valid but potentially very confusing. diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index eb761bd6475fb..46f5bdd73e657 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -161,7 +161,19 @@ impl ToStableHashKey for LintExpectation /// Setting for how to handle a lint. /// /// See: -#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, HashStable_Generic)] +#[derive( + Clone, + Copy, + PartialEq, + PartialOrd, + Eq, + Ord, + Debug, + Hash, + Encodable, + Decodable, + HashStable_Generic +)] pub enum Level { /// The `allow` level will not issue any message. Allow, diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 971d036fa69dd..0fe887423dd1a 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -3,8 +3,8 @@ use std::cmp; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sorted_map::SortedMap; use rustc_errors::{Diag, MultiSpan}; -use rustc_hir::{HirId, ItemLocalId}; -use rustc_macros::HashStable; +use rustc_hir::{CRATE_HIR_ID, HirId, ItemLocalId}; +use rustc_macros::{Decodable, Encodable, HashStable}; use rustc_session::Session; use rustc_session::lint::builtin::{self, FORBIDDEN_LINT_GROUPS}; use rustc_session::lint::{FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId}; @@ -15,7 +15,7 @@ use tracing::instrument; use crate::ty::TyCtxt; /// How a lint level was set. -#[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, HashStable, Debug)] pub enum LintLevelSource { /// Lint is at the default level as declared in rustc. Default, @@ -119,7 +119,7 @@ impl ShallowLintLevelMap { #[instrument(level = "trace", skip(self, tcx), ret)] fn probe_for_lint_level( &self, - tcx: TyCtxt<'_>, + tcx: Option>, id: LintId, start: HirId, ) -> (Option, LintLevelSource) { @@ -132,15 +132,17 @@ impl ShallowLintLevelMap { let mut owner = start.owner; let mut specs = &self.specs; - for parent in tcx.hir().parent_id_iter(start) { - if parent.owner != owner { - owner = parent.owner; - specs = &tcx.shallow_lint_levels_on(owner).specs; - } - if let Some(map) = specs.get(&parent.local_id) - && let Some(&(level, src)) = map.get(&id) - { - return (Some(level), src); + if let Some(tcx) = tcx { + for parent in tcx.hir().parent_id_iter(start) { + if parent.owner != owner { + owner = parent.owner; + specs = &tcx.shallow_lint_levels_on(owner).specs; + } + if let Some(map) = specs.get(&parent.local_id) + && let Some(&(level, src)) = map.get(&id) + { + return (Some(level), src); + } } } @@ -148,15 +150,20 @@ impl ShallowLintLevelMap { } /// Fetch and return the user-visible lint level for the given lint at the given HirId. - #[instrument(level = "trace", skip(self, tcx), ret)] + #[instrument(level = "trace", skip(self, tcx, sess), ret)] pub fn lint_level_id_at_node( &self, - tcx: TyCtxt<'_>, + tcx: Option>, + sess: &Session, lint: LintId, cur: HirId, ) -> (Level, LintLevelSource) { + assert!( + tcx.is_some() || cur == CRATE_HIR_ID, + "must pass in a tcx to access any level other than the root" + ); let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur); - let level = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| { + let level = reveal_actual_level(level, &mut src, sess, lint, |lint| { self.probe_for_lint_level(tcx, lint, cur) }); (level, src) @@ -166,14 +173,19 @@ impl ShallowLintLevelMap { impl TyCtxt<'_> { /// Fetch and return the user-visible lint level for the given lint at the given HirId. pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) { - self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id) + self.shallow_lint_levels_on(id.owner).lint_level_id_at_node( + Some(self), + self.sess, + LintId::of(lint), + id, + ) } } /// This struct represents a lint expectation and holds all required information /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after /// the `LateLintPass` has completed. -#[derive(Clone, Debug, HashStable)] +#[derive(Clone, Debug, Encodable, Decodable, HashStable)] pub struct LintExpectation { /// The reason for this expectation that can optionally be added as part of /// the attribute. It will be displayed as part of the lint message. From fddff8ee0fd6543241080968c42d40afc96d06ae Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 16 Oct 2024 01:35:01 -0400 Subject: [PATCH 4/5] warn on unused linker_messages warning attributes --- compiler/rustc_passes/messages.ftl | 3 +++ compiler/rustc_passes/src/check_attr.rs | 10 ++++++++++ compiler/rustc_passes/src/errors.rs | 2 ++ compiler/rustc_span/src/symbol.rs | 1 + 4 files changed, 16 insertions(+) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 0712af422adf3..e339abb3e2510 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -798,6 +798,9 @@ passes_unused_duplicate = passes_unused_empty_lints_note = attribute `{$name}` with an empty list has no effect +passes_unused_linker_warnings_note = + the `linker_warnings` lint can only be controlled at the root of a crate with `--crate-type bin` + passes_unused_multiple = multiple `{$name}` attributes .suggestion = remove this attribute diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 074fe77324faf..a5a2c86b94cd4 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -2271,6 +2271,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> { && item.path == sym::reason { errors::UnusedNote::NoLints { name: attr.name_or_empty() } + } else if matches!( + attr.name_or_empty(), + sym::allow | sym::warn | sym::deny | sym::forbid | sym::expect + ) && let Some(meta) = attr.meta_item_list() + && meta.iter().any(|meta| { + meta.meta_item().map_or(false, |item| item.path == sym::linker_messages) + }) + && hir_id != CRATE_HIR_ID + { + errors::UnusedNote::LinkerWarningsBinaryCrateOnly } else if attr.name_or_empty() == sym::default_method_body_is_const { errors::UnusedNote::DefaultMethodBodyConst } else { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index fbf25cb476aee..f9b79dac45225 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -762,6 +762,8 @@ pub(crate) enum UnusedNote { NoLints { name: Symbol }, #[note(passes_unused_default_method_body_const_note)] DefaultMethodBodyConst, + #[note(passes_unused_linker_warnings_note)] + LinkerWarningsBinaryCrateOnly, } #[derive(LintDiagnostic)] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3a07c283e0ebb..ce2cdfd9b4fab 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1153,6 +1153,7 @@ symbols! { link_section, linkage, linker, + linker_messages, lint_reasons, literal, load, From fe9f2722a04346c1aa7cba8ca49169242393fdc1 Mon Sep 17 00:00:00 2001 From: jyn Date: Fri, 29 Nov 2024 10:28:54 -0500 Subject: [PATCH 5/5] ignore linker errors on macOS --- src/bootstrap/src/core/build_steps/compile.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 8e088682f92de..ba6af28f58a1f 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -592,6 +592,11 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car // separate setting for the compiler. cargo.rustflag("-Cforce-frame-pointers=yes"); + // Ignore linker warnings on macOS for now. These are complicated to fix and don't affect the build. + if target.contains("apple-darwin") { + cargo.rustflag("-Alinker-messages"); + } + let html_root = format!("-Zcrate-attr=doc(html_root_url=\"{}/\")", builder.doc_rust_lang_org_channel(),); cargo.rustflag(&html_root);