Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 143 additions & 49 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::attrs::NativeLibKind;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_lint_defs::builtin::LINKER_INFO;
use rustc_macros::LintDiagnostic;
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
use rustc_metadata::{
Expand Down Expand Up @@ -60,7 +61,8 @@ use super::rpath::{self, RPathConfig};
use super::{apple, versioned_llvm_target};
use crate::base::needs_allocator_shim_for_linking;
use crate::{
CodegenResults, CompiledModule, CrateInfo, NativeLib, errors, looks_like_rust_object_file,
CodegenLintLevels, CodegenResults, CompiledModule, CrateInfo, NativeLib, errors,
looks_like_rust_object_file,
};

pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
Expand Down Expand Up @@ -670,6 +672,139 @@ struct LinkerOutput {
inner: String,
}

fn is_msvc_link_exe(sess: &Session) -> bool {
let (linker_path, flavor) = linker_and_flavor(sess);
sess.target.is_like_msvc
&& flavor == LinkerFlavor::Msvc(Lld::No)
// Match exactly "link.exe"
&& linker_path.to_str() == Some("link.exe")
}

fn is_macos_ld(sess: &Session) -> bool {
let (_, flavor) = linker_and_flavor(sess);
sess.target.is_like_darwin && matches!(flavor, LinkerFlavor::Darwin(_, Lld::No))
}

fn is_windows_gnu_ld(sess: &Session) -> bool {
let (_, flavor) = linker_and_flavor(sess);
sess.target.is_like_windows
&& !sess.target.is_like_msvc
&& matches!(flavor, LinkerFlavor::Gnu(_, Lld::No))
}

fn report_linker_output(sess: &Session, levels: CodegenLintLevels, stdout: &[u8], stderr: &[u8]) {
let mut escaped_stderr = escape_string(&stderr);
let mut escaped_stdout = escape_string(&stdout);
let mut linker_info = String::new();

info!("linker stderr:\n{}", &escaped_stderr);
info!("linker stdout:\n{}", &escaped_stdout);

fn for_each(bytes: &[u8], mut f: impl FnMut(&str, &mut String)) -> String {
let mut output = String::new();
if let Ok(str) = str::from_utf8(bytes) {
info!("line: {str}");
output = String::with_capacity(str.len());
for line in str.lines() {
f(line.trim(), &mut output);
}
}
escape_string(output.trim().as_bytes())
}

if is_msvc_link_exe(sess) {
escaped_stdout = for_each(&stdout, |line, output| {
// 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 line.starts_with(" Creating library")
|| line.starts_with("Generating code")
|| line.starts_with("Finished generating code")
{
linker_info += line;
linker_info += "\r\n";
} else {
*output += line;
*output += "\r\n"
}
});
} else if is_macos_ld(sess) {
// FIXME: Tracked by https://github.com/rust-lang/rust/issues/136113
let deployment_mismatch = |line: &str| {
line.starts_with("ld: warning: object file (")
&& line.contains("was built for newer 'macOS' version")
&& line.contains("than being linked")
};
// FIXME: This is a real warning we would like to show, but it hits too many crates
// to want to turn it on immediately.
let search_path = |line: &str| {
line.starts_with("ld: warning: search path '") && line.ends_with("' not found")
};
escaped_stderr = for_each(&stderr, |line, output| {
// This duplicate library warning is just not helpful at all.
if line.starts_with("ld: warning: ignoring duplicate libraries: ")
|| deployment_mismatch(line)
|| search_path(line)
{
linker_info += line;
linker_info += "\n";
} else {
*output += line;
*output += "\n"
}
});
} else if is_windows_gnu_ld(sess) {
let mut saw_exclude_symbol = false;
// See https://github.com/rust-lang/rust/issues/112368.
// FIXME: maybe check that binutils is older than 2.40 before downgrading this warning?
let exclude_symbols = |line: &str| {
line.starts_with("Warning: .drectve `-exclude-symbols:")
&& line.ends_with("' unrecognized")
};
escaped_stderr = for_each(&stderr, |line, output| {
if exclude_symbols(line) {
saw_exclude_symbol = true;
linker_info += line;
linker_info += "\n";
} else if saw_exclude_symbol && line == "Warning: corrupt .drectve at end of def file" {
linker_info += line;
linker_info += "\n";
} else {
*output += line;
*output += "\n"
}
});
}

let lint_msg = |msg| {
lint_level(sess, LINKER_MESSAGES, levels.linker_messages, None, |diag| {
LinkerOutput { inner: msg }.decorate_lint(diag)
})
};
let lint_info = |msg| {
lint_level(sess, LINKER_INFO, levels.linker_info, None, |diag| {
LinkerOutput { inner: msg }.decorate_lint(diag)
})
};

if !escaped_stderr.is_empty() {
// We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
escaped_stderr =
escaped_stderr.strip_prefix("warning: ").unwrap_or(&escaped_stderr).to_owned();
// Windows GNU LD prints uppercase Warning
escaped_stderr = escaped_stderr
.strip_prefix("Warning: ")
.unwrap_or(&escaped_stderr)
.replace(": warning: ", ": ");
lint_msg(format!("linker stderr: {escaped_stderr}"));
}
if !escaped_stdout.is_empty() {
lint_msg(format!("linker stdout: {}", escaped_stdout))
}
if !linker_info.is_empty() {
lint_info(linker_info);
}
}

/// 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 @@ -856,11 +991,6 @@ 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 @@ -880,7 +1010,7 @@ fn link_natively(
if let Some(code) = prog.status.code() {
// 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) {
if is_msvc_link_exe(sess) && (code < 1000 || code > 9999) {
let is_vs_installed = find_msvc_tools::find_vs_version().is_ok();
let has_linker =
find_msvc_tools::find_tool(sess.target.arch.desc(), "link.exe")
Expand Down Expand Up @@ -912,48 +1042,12 @@ fn link_natively(
sess.dcx().abort_if_errors();
}

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 = codegen_results.crate_info.lint_levels.linker_messages;
let lint = |msg| {
lint_level(sess, LINKER_MESSAGES, level, 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))
}
report_linker_output(
sess,
codegen_results.crate_info.lint_levels,
&prog.stdout,
&prog.stderr,
);
}
Err(e) => {
let linker_not_found = e.kind() == io::ErrorKind::NotFound;
Expand Down
7 changes: 6 additions & 1 deletion compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use rustc_data_structures::unord::UnordMap;
use rustc_hir::CRATE_HIR_ID;
use rustc_hir::attrs::{CfgEntry, NativeLibKind};
use rustc_hir::def_id::CrateNum;
use rustc_lint_defs::builtin::LINKER_INFO;
use rustc_macros::{Decodable, Encodable, HashStable};
use rustc_metadata::EncodedMetadata;
use rustc_middle::dep_graph::WorkProduct;
Expand Down Expand Up @@ -360,10 +361,14 @@ impl CodegenResults {
#[derive(Copy, Clone, Debug, Encodable, Decodable)]
pub struct CodegenLintLevels {
linker_messages: LevelAndSource,
linker_info: LevelAndSource,
}

impl CodegenLintLevels {
pub fn from_tcx(tcx: TyCtxt<'_>) -> Self {
Self { linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID) }
Self {
linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID),
linker_info: tcx.lint_level_at_node(LINKER_INFO, CRATE_HIR_ID),
}
}
}
37 changes: 36 additions & 1 deletion compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ declare_lint_pass! {
LARGE_ASSIGNMENTS,
LATE_BOUND_LIFETIME_ARGUMENTS,
LEGACY_DERIVE_HELPERS,
LINKER_INFO,
LINKER_MESSAGES,
LONG_RUNNING_CONST_EVAL,
LOSSY_PROVENANCE_CASTS,
Expand Down Expand Up @@ -4010,10 +4011,44 @@ declare_lint! {
/// and actionable warning of similar quality to our other diagnostics. See this tracking
/// issue for more details: <https://github.com/rust-lang/rust/issues/136096>.
pub LINKER_MESSAGES,
Allow,
Deny,
"warnings emitted at runtime by the target-specific linker program"
}

declare_lint! {
/// The `linker_info` lint forwards warnings from the linker that are known to be informational-only.
///
/// ### Example
///
/// ```rust,ignore (needs CLI args, platform-specific)
/// #[warn(linker_info)]
/// fn main () {}
/// ```
///
/// On MacOS, using `-C link-arg=-lc` and the default linker, this will produce
///
/// ```text
/// warning: linker stderr: ld: ignoring duplicate libraries: '-lc'
/// |
/// note: the lint level is defined here
/// --> ex.rs:1:9
/// |
/// 1 | #![warn(linker_info)]
/// | ^^^^^^^^^^^^^^^
/// ```
///
/// ### Explanation
///
/// Many linkers are very "chatty" and print lots of information that is not necessarily
/// indicative of an issue. This output has been ignored for many years and is often not
/// actionable by developers. It is silenced unless the developer specifically requests for it
/// to be printed. See this tracking issue for more details:
/// <https://github.com/rust-lang/rust/issues/136096>.
pub LINKER_INFO,
Allow,
"linker warnings known to be informational-only and not indicative of a problem"
}

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
2 changes: 1 addition & 1 deletion compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ passes_unused_empty_lints_note =
attribute `{$name}` with an empty list has no effect

passes_unused_linker_messages_note =
the `linker_messages` lint can only be controlled at the root of a crate that needs to be linked
the `linker_messages` and `linker_info` lints can only be controlled at the root of a crate that needs to be linked

passes_unused_multiple =
multiple `{$name}` attributes
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1947,7 +1947,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
} else if attr.has_any_name(&[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)
meta.meta_item().map_or(false, |item| {
item.path == sym::linker_messages || item.path == sym::linker_info
})
})
{
if hir_id != CRATE_HIR_ID {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ symbols! {
link_section,
linkage,
linker,
linker_info,
linker_messages,
linkonce,
linkonce_odr,
Expand Down
1 change: 1 addition & 0 deletions tests/run-make/branch-protection-check-IBT/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#![no_std]
5 changes: 0 additions & 5 deletions tests/run-make/branch-protection-check-IBT/main.rs

This file was deleted.

6 changes: 4 additions & 2 deletions tests/run-make/branch-protection-check-IBT/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ use run_make_support::{bare_rustc, llvm_readobj};
fn main() {
// `main.rs` is `#![no_std]` to not pull in the currently not-compiled-with-IBT precompiled std.
bare_rustc()
.input("main.rs")
.input("lib.rs")
.crate_type("lib")
.emit("obj=lib.o")
.target("x86_64-unknown-linux-gnu")
.arg("-Zcf-protection=branch")
.arg("-Clink-args=-nostartfiles")
.run();

llvm_readobj().arg("-nW").input("main").run().assert_stdout_contains(".note.gnu.property");
llvm_readobj().arg("-nW").input("lib.o").run().assert_stdout_contains(".note.gnu.property");
}
1 change: 1 addition & 0 deletions tests/run-make/macos-deployment-target-warning/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void foo() {}
8 changes: 8 additions & 0 deletions tests/run-make/macos-deployment-target-warning/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![warn(linker_info, linker_messages)]
unsafe extern "C" {
safe fn foo();
}

fn main() {
foo();
}
Loading
Loading