Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show linker output even if the linker succeeds #119286

Merged
merged 9 commits into from
Jan 25, 2025
2 changes: 2 additions & 0 deletions compiler/rustc_codegen_ssa/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
76 changes: 63 additions & 13 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +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;
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
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;
Expand All @@ -29,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;
Expand Down Expand Up @@ -749,6 +752,14 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
}
}

#[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.
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
Expand Down Expand Up @@ -981,6 +992,11 @@ fn link_natively(

match prog {
Ok(prog) => {
let is_msvc_link_exe = sess.target.is_like_msvc
&& flavor == LinkerFlavor::Msvc(Lld::No)
// Match exactly "link.exe"
&& linker_path.to_str() == Some("link.exe");

if !prog.status.success() {
let mut output = prog.stderr.clone();
output.extend_from_slice(&prog.stdout);
Expand All @@ -997,16 +1013,9 @@ fn link_natively(
// is not a Microsoft LNK error then suggest a way to fix or
// install the Visual Studio build tools.
if let Some(code) = prog.status.code() {
if sess.target.is_like_msvc
&& flavor == LinkerFlavor::Msvc(Lld::No)
// Respect the command line override
&& sess.opts.cg.linker.is_none()
// Match exactly "link.exe"
&& linker_path.to_str() == Some("link.exe")
// All Microsoft `link.exe` linking error codes are
// four digit numbers in the range 1000 to 9999 inclusive
&& (code < 1000 || code > 9999)
{
// All Microsoft `link.exe` linking ror codes are
// four digit numbers in the range 1000 to 9999 inclusive
if is_msvc_link_exe && (code < 1000 || code > 9999) {
let is_vs_installed = windows_registry::find_vs_version().is_ok();
let has_linker =
windows_registry::find_tool(&sess.target.arch, "link.exe").is_some();
Expand All @@ -1028,8 +1037,49 @@ fn link_natively(

sess.dcx().abort_if_errors();
}
info!("linker stderr:\n{}", escape_string(&prog.stderr));
info!("linker stdout:\n{}", escape_string(&prog.stdout));

let stderr = escape_string(&prog.stderr);
let mut stdout = escape_string(&prog.stdout);
info!("linker stderr:\n{}", &stderr);
info!("linker stdout:\n{}", &stdout);

// Hide some progress messages from link.exe that we don't care about.
// See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146
if is_msvc_link_exe {
if let Ok(str) = str::from_utf8(&prog.stdout) {
let mut output = String::with_capacity(str.len());
for line in stdout.lines() {
if line.starts_with(" Creating library")
|| line.starts_with("Generating code")
|| line.starts_with("Finished generating code")
{
continue;
}
output += line;
output += "\r\n"
}
stdout = escape_string(output.trim().as_bytes())
}
}

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 = stderr
.strip_prefix("warning: ")
.unwrap_or(&stderr)
.replace(": warning: ", ": ");
lint(format!("linker stderr: {stderr}"));
}
if !stdout.is_empty() {
lint(format!("linker stdout: {}", stdout))
}
}
Err(e) => {
let linker_not_found = e.kind() == io::ErrorKind::NotFound;
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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 {
Expand Down Expand Up @@ -927,6 +928,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);
Expand Down
22 changes: 22 additions & 0 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -200,6 +205,7 @@ pub struct CrateInfo {
pub dependency_formats: Lrc<Dependencies>,
pub windows_subsystem: Option<String>,
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
pub lint_levels: CodegenLintLevels,
}

#[derive(Encodable, Decodable)]
Expand Down Expand Up @@ -302,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 {
jyn514 marked this conversation as resolved.
Show resolved Hide resolved
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) }
}
}
47 changes: 30 additions & 17 deletions compiler/rustc_errors/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use rustc_error_messages::FluentArgs;
use rustc_lint_defs::Applicability;
use rustc_span::Span;
use rustc_span::hygiene::ExpnData;
use rustc_span::source_map::SourceMap;
use rustc_span::source_map::{FilePathMapping, SourceMap};
use serde::Serialize;
use termcolor::{ColorSpec, WriteColor};

Expand All @@ -45,7 +45,7 @@ pub struct JsonEmitter {
#[setters(skip)]
dst: IntoDynSyncSend<Box<dyn Write + Send>>,
#[setters(skip)]
sm: Lrc<SourceMap>,
sm: Option<Lrc<SourceMap>>,
fluent_bundle: Option<Lrc<FluentBundle>>,
#[setters(skip)]
fallback_bundle: LazyFallbackBundle,
Expand All @@ -65,7 +65,7 @@ pub struct JsonEmitter {
impl JsonEmitter {
pub fn new(
dst: Box<dyn Write + Send>,
sm: Lrc<SourceMap>,
sm: Option<Lrc<SourceMap>>,
fallback_bundle: LazyFallbackBundle,
pretty: bool,
json_rendered: HumanReadableErrorType,
Expand Down Expand Up @@ -171,7 +171,7 @@ impl Emitter for JsonEmitter {
}

fn source_map(&self) -> Option<&SourceMap> {
Some(&self.sm)
self.sm.as_deref()
}

fn should_show_explain(&self) -> bool {
Expand Down Expand Up @@ -371,7 +371,7 @@ impl Diagnostic {
}
HumanEmitter::new(dst, Lrc::clone(&je.fallback_bundle))
.short_message(short)
.sm(Some(Lrc::clone(&je.sm)))
.sm(je.sm.clone())
.fluent_bundle(je.fluent_bundle.clone())
.diagnostic_width(je.diagnostic_width)
.macro_backtrace(je.macro_backtrace)
Expand Down Expand Up @@ -458,23 +458,34 @@ impl DiagnosticSpan {
mut backtrace: impl Iterator<Item = ExpnData>,
je: &JsonEmitter,
) -> DiagnosticSpan {
let start = je.sm.lookup_char_pos(span.lo());
let empty_source_map;
let sm = match &je.sm {
Some(s) => s,
None => {
span = rustc_span::DUMMY_SP;
empty_source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
empty_source_map
.new_source_file(std::path::PathBuf::from("empty.rs").into(), String::new());
bjorn3 marked this conversation as resolved.
Show resolved Hide resolved
&empty_source_map
}
};
let start = sm.lookup_char_pos(span.lo());
// If this goes from the start of a line to the end and the replacement
// is an empty string, increase the length to include the newline so we don't
// leave an empty line
if start.col.0 == 0
&& let Some((suggestion, _)) = suggestion
&& suggestion.is_empty()
&& let Ok(after) = je.sm.span_to_next_source(span)
&& let Ok(after) = sm.span_to_next_source(span)
&& after.starts_with('\n')
{
span = span.with_hi(span.hi() + rustc_span::BytePos(1));
}
let end = je.sm.lookup_char_pos(span.hi());
let end = sm.lookup_char_pos(span.hi());
let backtrace_step = backtrace.next().map(|bt| {
let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
let def_site_span = Self::from_span_full(
je.sm.guess_head_span(bt.def_site),
sm.guess_head_span(bt.def_site),
false,
None,
None,
Expand All @@ -489,7 +500,7 @@ impl DiagnosticSpan {
});

DiagnosticSpan {
file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(),
file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
byte_start: start.file.original_relative_byte_pos(span.lo()).0,
byte_end: start.file.original_relative_byte_pos(span.hi()).0,
line_start: start.line,
Expand Down Expand Up @@ -559,19 +570,20 @@ impl DiagnosticSpanLine {
/// `span` within the line.
fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
je.sm
.span_to_lines(span)
.map(|lines| {
.as_ref()
.and_then(|sm| {
let lines = sm.span_to_lines(span).ok()?;
// We can't get any lines if the source is unavailable.
if !should_show_source_code(
&je.ignored_directories_in_source_blocks,
&je.sm,
&sm,
&lines.file,
) {
return vec![];
return None;
}

let sf = &*lines.file;
lines
let span_lines = lines
.lines
.iter()
.map(|line| {
Expand All @@ -582,8 +594,9 @@ impl DiagnosticSpanLine {
line.end_col.0 + 1,
)
})
.collect()
.collect();
Some(span_lines)
})
.unwrap_or_else(|_| vec![])
.unwrap_or_default()
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/json/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
let output = Arc::new(Mutex::new(Vec::new()));
let je = JsonEmitter::new(
Box::new(Shared { data: output.clone() }),
sm,
Some(sm),
fallback_bundle,
true, // pretty
HumanReadableErrorType::Short,
Expand Down
34 changes: 34 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -4085,6 +4086,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.
Expand Down
Loading
Loading