Skip to content

Commit f7cc13a

Browse files
committed
Auto merge of rust-lang#119286 - jyn514:linker-output, r=bjorn3
show linker output even if the linker succeeds Show stderr and stderr by default, controlled by a new `linker_messages` lint. fixes rust-lang#83436. fixes rust-lang#38206. cc https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/uplift.20some.20-Zverbose.20calls.20and.20rename.20to.E2.80.A6.20compiler-team.23706/near/408986134 <!-- try-job: dist-x86_64-msvc --> try-job: aarch64-apple r? `@bjorn3`
2 parents 203e6c1 + 8b92619 commit f7cc13a

File tree

26 files changed

+350
-73
lines changed

26 files changed

+350
-73
lines changed

compiler/rustc_codegen_ssa/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker
183183
codegen_ssa_linker_not_found = linker `{$linker_path}` not found
184184
.note = {$error}
185185
186+
codegen_ssa_linker_output = {$inner}
187+
186188
codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker
187189
188190
codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status}

compiler/rustc_codegen_ssa/src/back/link.rs

+63-13
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ use rustc_ast::CRATE_NODE_ID;
1515
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
1616
use rustc_data_structures::memmap::Mmap;
1717
use rustc_data_structures::temp_dir::MaybeTempDir;
18-
use rustc_errors::DiagCtxtHandle;
18+
use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
1919
use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
2020
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
21+
use rustc_macros::LintDiagnostic;
2122
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
2223
use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs};
2324
use rustc_middle::bug;
25+
use rustc_middle::lint::lint_level;
2426
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
2527
use rustc_middle::middle::dependency_format::Linkage;
2628
use rustc_middle::middle::exported_symbols::SymbolExportKind;
@@ -29,6 +31,7 @@ use rustc_session::config::{
2931
OutputType, PrintKind, SplitDwarfKind, Strip,
3032
};
3133
use rustc_session::cstore::DllImport;
34+
use rustc_session::lint::builtin::LINKER_MESSAGES;
3235
use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
3336
use rustc_session::search_paths::PathKind;
3437
use rustc_session::utils::NativeLibKind;
@@ -749,6 +752,14 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
749752
}
750753
}
751754

755+
#[derive(LintDiagnostic)]
756+
#[diag(codegen_ssa_linker_output)]
757+
/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
758+
/// end up with inconsistent languages within the same diagnostic.
759+
struct LinkerOutput {
760+
inner: String,
761+
}
762+
752763
/// Create a dynamic library or executable.
753764
///
754765
/// This will invoke the system linker/cc to create the resulting file. This links to all upstream
@@ -981,6 +992,11 @@ fn link_natively(
981992

982993
match prog {
983994
Ok(prog) => {
995+
let is_msvc_link_exe = sess.target.is_like_msvc
996+
&& flavor == LinkerFlavor::Msvc(Lld::No)
997+
// Match exactly "link.exe"
998+
&& linker_path.to_str() == Some("link.exe");
999+
9841000
if !prog.status.success() {
9851001
let mut output = prog.stderr.clone();
9861002
output.extend_from_slice(&prog.stdout);
@@ -997,16 +1013,9 @@ fn link_natively(
9971013
// is not a Microsoft LNK error then suggest a way to fix or
9981014
// install the Visual Studio build tools.
9991015
if let Some(code) = prog.status.code() {
1000-
if sess.target.is_like_msvc
1001-
&& flavor == LinkerFlavor::Msvc(Lld::No)
1002-
// Respect the command line override
1003-
&& sess.opts.cg.linker.is_none()
1004-
// Match exactly "link.exe"
1005-
&& linker_path.to_str() == Some("link.exe")
1006-
// All Microsoft `link.exe` linking error codes are
1007-
// four digit numbers in the range 1000 to 9999 inclusive
1008-
&& (code < 1000 || code > 9999)
1009-
{
1016+
// All Microsoft `link.exe` linking ror codes are
1017+
// four digit numbers in the range 1000 to 9999 inclusive
1018+
if is_msvc_link_exe && (code < 1000 || code > 9999) {
10101019
let is_vs_installed = windows_registry::find_vs_version().is_ok();
10111020
let has_linker =
10121021
windows_registry::find_tool(&sess.target.arch, "link.exe").is_some();
@@ -1028,8 +1037,49 @@ fn link_natively(
10281037

10291038
sess.dcx().abort_if_errors();
10301039
}
1031-
info!("linker stderr:\n{}", escape_string(&prog.stderr));
1032-
info!("linker stdout:\n{}", escape_string(&prog.stdout));
1040+
1041+
let stderr = escape_string(&prog.stderr);
1042+
let mut stdout = escape_string(&prog.stdout);
1043+
info!("linker stderr:\n{}", &stderr);
1044+
info!("linker stdout:\n{}", &stdout);
1045+
1046+
// Hide some progress messages from link.exe that we don't care about.
1047+
// See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146
1048+
if is_msvc_link_exe {
1049+
if let Ok(str) = str::from_utf8(&prog.stdout) {
1050+
let mut output = String::with_capacity(str.len());
1051+
for line in stdout.lines() {
1052+
if line.starts_with(" Creating library")
1053+
|| line.starts_with("Generating code")
1054+
|| line.starts_with("Finished generating code")
1055+
{
1056+
continue;
1057+
}
1058+
output += line;
1059+
output += "\r\n"
1060+
}
1061+
stdout = escape_string(output.trim().as_bytes())
1062+
}
1063+
}
1064+
1065+
let (level, src) = codegen_results.crate_info.lint_levels.linker_messages;
1066+
let lint = |msg| {
1067+
lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| {
1068+
LinkerOutput { inner: msg }.decorate_lint(diag)
1069+
})
1070+
};
1071+
1072+
if !prog.stderr.is_empty() {
1073+
// We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
1074+
let stderr = stderr
1075+
.strip_prefix("warning: ")
1076+
.unwrap_or(&stderr)
1077+
.replace(": warning: ", ": ");
1078+
lint(format!("linker stderr: {stderr}"));
1079+
}
1080+
if !stdout.is_empty() {
1081+
lint(format!("linker stdout: {}", stdout))
1082+
}
10331083
}
10341084
Err(e) => {
10351085
let linker_not_found = e.kind() == io::ErrorKind::NotFound;

compiler/rustc_codegen_ssa/src/base.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ use crate::mir::operand::OperandValue;
4444
use crate::mir::place::PlaceRef;
4545
use crate::traits::*;
4646
use crate::{
47-
CachedModuleCodegen, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind, errors, meth, mir,
47+
CachedModuleCodegen, CodegenLintLevels, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind,
48+
errors, meth, mir,
4849
};
4950

5051
pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate {
@@ -924,6 +925,7 @@ impl CrateInfo {
924925
dependency_formats: Lrc::clone(tcx.dependency_formats(())),
925926
windows_subsystem,
926927
natvis_debugger_visualizers: Default::default(),
928+
lint_levels: CodegenLintLevels::from_tcx(tcx),
927929
};
928930

929931
info.native_libraries.reserve(n_crates);

compiler/rustc_codegen_ssa/src/lib.rs

+22
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,23 @@ use rustc_ast as ast;
2929
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
3030
use rustc_data_structures::sync::Lrc;
3131
use rustc_data_structures::unord::UnordMap;
32+
use rustc_hir::CRATE_HIR_ID;
3233
use rustc_hir::def_id::CrateNum;
3334
use rustc_macros::{Decodable, Encodable, HashStable};
3435
use rustc_middle::dep_graph::WorkProduct;
36+
use rustc_middle::lint::LintLevelSource;
3537
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
3638
use rustc_middle::middle::dependency_format::Dependencies;
3739
use rustc_middle::middle::exported_symbols::SymbolExportKind;
40+
use rustc_middle::ty::TyCtxt;
3841
use rustc_middle::util::Providers;
3942
use rustc_serialize::opaque::{FileEncoder, MemDecoder};
4043
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
4144
use rustc_session::Session;
4245
use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT};
4346
use rustc_session::cstore::{self, CrateSource};
47+
use rustc_session::lint::Level;
48+
use rustc_session::lint::builtin::LINKER_MESSAGES;
4449
use rustc_session::utils::NativeLibKind;
4550
use rustc_span::Symbol;
4651

@@ -200,6 +205,7 @@ pub struct CrateInfo {
200205
pub dependency_formats: Lrc<Dependencies>,
201206
pub windows_subsystem: Option<String>,
202207
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
208+
pub lint_levels: CodegenLintLevels,
203209
}
204210

205211
#[derive(Encodable, Decodable)]
@@ -302,3 +308,19 @@ impl CodegenResults {
302308
Ok((codegen_results, outputs))
303309
}
304310
}
311+
312+
/// A list of lint levels used in codegen.
313+
///
314+
/// When using `-Z link-only`, we don't have access to the tcx and must work
315+
/// solely from the `.rlink` file. `Lint`s are defined too early to be encodeable.
316+
/// Instead, encode exactly the information we need.
317+
#[derive(Copy, Clone, Debug, Encodable, Decodable)]
318+
pub struct CodegenLintLevels {
319+
linker_messages: (Level, LintLevelSource),
320+
}
321+
322+
impl CodegenLintLevels {
323+
pub fn from_tcx(tcx: TyCtxt<'_>) -> Self {
324+
Self { linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID) }
325+
}
326+
}

compiler/rustc_errors/src/json.rs

+30-17
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_error_messages::FluentArgs;
2121
use rustc_lint_defs::Applicability;
2222
use rustc_span::Span;
2323
use rustc_span::hygiene::ExpnData;
24-
use rustc_span::source_map::SourceMap;
24+
use rustc_span::source_map::{FilePathMapping, SourceMap};
2525
use serde::Serialize;
2626
use termcolor::{ColorSpec, WriteColor};
2727

@@ -45,7 +45,7 @@ pub struct JsonEmitter {
4545
#[setters(skip)]
4646
dst: IntoDynSyncSend<Box<dyn Write + Send>>,
4747
#[setters(skip)]
48-
sm: Lrc<SourceMap>,
48+
sm: Option<Lrc<SourceMap>>,
4949
fluent_bundle: Option<Lrc<FluentBundle>>,
5050
#[setters(skip)]
5151
fallback_bundle: LazyFallbackBundle,
@@ -65,7 +65,7 @@ pub struct JsonEmitter {
6565
impl JsonEmitter {
6666
pub fn new(
6767
dst: Box<dyn Write + Send>,
68-
sm: Lrc<SourceMap>,
68+
sm: Option<Lrc<SourceMap>>,
6969
fallback_bundle: LazyFallbackBundle,
7070
pretty: bool,
7171
json_rendered: HumanReadableErrorType,
@@ -171,7 +171,7 @@ impl Emitter for JsonEmitter {
171171
}
172172

173173
fn source_map(&self) -> Option<&SourceMap> {
174-
Some(&self.sm)
174+
self.sm.as_deref()
175175
}
176176

177177
fn should_show_explain(&self) -> bool {
@@ -371,7 +371,7 @@ impl Diagnostic {
371371
}
372372
HumanEmitter::new(dst, Lrc::clone(&je.fallback_bundle))
373373
.short_message(short)
374-
.sm(Some(Lrc::clone(&je.sm)))
374+
.sm(je.sm.clone())
375375
.fluent_bundle(je.fluent_bundle.clone())
376376
.diagnostic_width(je.diagnostic_width)
377377
.macro_backtrace(je.macro_backtrace)
@@ -458,23 +458,34 @@ impl DiagnosticSpan {
458458
mut backtrace: impl Iterator<Item = ExpnData>,
459459
je: &JsonEmitter,
460460
) -> DiagnosticSpan {
461-
let start = je.sm.lookup_char_pos(span.lo());
461+
let empty_source_map;
462+
let sm = match &je.sm {
463+
Some(s) => s,
464+
None => {
465+
span = rustc_span::DUMMY_SP;
466+
empty_source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
467+
empty_source_map
468+
.new_source_file(std::path::PathBuf::from("empty.rs").into(), String::new());
469+
&empty_source_map
470+
}
471+
};
472+
let start = sm.lookup_char_pos(span.lo());
462473
// If this goes from the start of a line to the end and the replacement
463474
// is an empty string, increase the length to include the newline so we don't
464475
// leave an empty line
465476
if start.col.0 == 0
466477
&& let Some((suggestion, _)) = suggestion
467478
&& suggestion.is_empty()
468-
&& let Ok(after) = je.sm.span_to_next_source(span)
479+
&& let Ok(after) = sm.span_to_next_source(span)
469480
&& after.starts_with('\n')
470481
{
471482
span = span.with_hi(span.hi() + rustc_span::BytePos(1));
472483
}
473-
let end = je.sm.lookup_char_pos(span.hi());
484+
let end = sm.lookup_char_pos(span.hi());
474485
let backtrace_step = backtrace.next().map(|bt| {
475486
let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
476487
let def_site_span = Self::from_span_full(
477-
je.sm.guess_head_span(bt.def_site),
488+
sm.guess_head_span(bt.def_site),
478489
false,
479490
None,
480491
None,
@@ -489,7 +500,7 @@ impl DiagnosticSpan {
489500
});
490501

491502
DiagnosticSpan {
492-
file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(),
503+
file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
493504
byte_start: start.file.original_relative_byte_pos(span.lo()).0,
494505
byte_end: start.file.original_relative_byte_pos(span.hi()).0,
495506
line_start: start.line,
@@ -559,19 +570,20 @@ impl DiagnosticSpanLine {
559570
/// `span` within the line.
560571
fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
561572
je.sm
562-
.span_to_lines(span)
563-
.map(|lines| {
573+
.as_ref()
574+
.and_then(|sm| {
575+
let lines = sm.span_to_lines(span).ok()?;
564576
// We can't get any lines if the source is unavailable.
565577
if !should_show_source_code(
566578
&je.ignored_directories_in_source_blocks,
567-
&je.sm,
579+
&sm,
568580
&lines.file,
569581
) {
570-
return vec![];
582+
return None;
571583
}
572584

573585
let sf = &*lines.file;
574-
lines
586+
let span_lines = lines
575587
.lines
576588
.iter()
577589
.map(|line| {
@@ -582,8 +594,9 @@ impl DiagnosticSpanLine {
582594
line.end_col.0 + 1,
583595
)
584596
})
585-
.collect()
597+
.collect();
598+
Some(span_lines)
586599
})
587-
.unwrap_or_else(|_| vec![])
600+
.unwrap_or_default()
588601
}
589602
}

compiler/rustc_errors/src/json/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
4747
let output = Arc::new(Mutex::new(Vec::new()));
4848
let je = JsonEmitter::new(
4949
Box::new(Shared { data: output.clone() }),
50-
sm,
50+
Some(sm),
5151
fallback_bundle,
5252
true, // pretty
5353
HumanReadableErrorType::Short,

compiler/rustc_lint_defs/src/builtin.rs

+34
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ declare_lint_pass! {
6060
LARGE_ASSIGNMENTS,
6161
LATE_BOUND_LIFETIME_ARGUMENTS,
6262
LEGACY_DERIVE_HELPERS,
63+
LINKER_MESSAGES,
6364
LONG_RUNNING_CONST_EVAL,
6465
LOSSY_PROVENANCE_CASTS,
6566
MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
@@ -4085,6 +4086,39 @@ declare_lint! {
40854086
"call to foreign functions or function pointers with FFI-unwind ABI"
40864087
}
40874088

4089+
declare_lint! {
4090+
/// The `linker_messages` lint forwards warnings from the linker.
4091+
///
4092+
/// ### Example
4093+
///
4094+
/// ```rust,ignore (needs CLI args, platform-specific)
4095+
/// extern "C" {
4096+
/// fn foo();
4097+
/// }
4098+
/// fn main () { unsafe { foo(); } }
4099+
/// ```
4100+
///
4101+
/// On Linux, using `gcc -Wl,--warn-unresolved-symbols` as a linker, this will produce
4102+
///
4103+
/// ```text
4104+
/// warning: linker stderr: rust-lld: undefined symbol: foo
4105+
/// >>> referenced by rust_out.69edbd30df4ae57d-cgu.0
4106+
/// >>> rust_out.rust_out.69edbd30df4ae57d-cgu.0.rcgu.o:(rust_out::main::h3a90094b06757803)
4107+
/// |
4108+
/// = note: `#[warn(linker_messages)]` on by default
4109+
///
4110+
/// warning: 1 warning emitted
4111+
/// ```
4112+
///
4113+
/// ### Explanation
4114+
///
4115+
/// Linkers emit platform-specific and program-specific warnings that cannot be predicted in advance by the rust compiler.
4116+
/// They are forwarded by default, but can be disabled by adding `#![allow(linker_messages)]` at the crate root.
4117+
pub LINKER_MESSAGES,
4118+
Warn,
4119+
"warnings emitted at runtime by the target-specific linker program"
4120+
}
4121+
40884122
declare_lint! {
40894123
/// The `named_arguments_used_positionally` lint detects cases where named arguments are only
40904124
/// used positionally in format strings. This usage is valid but potentially very confusing.

0 commit comments

Comments
 (0)