Keep last redundant linker flag, not first

When a library (L1) is passed to the linker multiple times, this is
sometimes purposeful: there might be several other libraries in the
linker command (L2 and L3) that all depend on L1.  You'd end up with a
(simplified) linker command that looks like:

    -l2 -l1 -l3 -l1

With the previous behavior, when rustc encountered a redundant library,
it would keep the first instance, and remove the later ones, resulting
in:

    -l2 -l1 -l3

This can cause a linker error, because on some platforms (e.g. Linux),
the linker will only include symbols from L1 that are needed *at the
point it's referenced in the command line*.  So if L3 depends on
additional symbols from L1, which aren't needed by L2, the linker won't
know to include them, and you'll end up with "undefined symbols" errors.

A better behavior is to keep the *last* instance of the library:

    -l2 -l3 -l1

This ensures that all "downstream" libraries have been included in the
linker command before the "upstream" library is referenced.

Fixes rust-lang#47989
This commit is contained in:
Douglas Creager 2018-12-20 15:46:42 -05:00
parent 4755e2f3b6
commit d445e1ccaa
2 changed files with 27 additions and 25 deletions

View file

@ -13,6 +13,7 @@
html_root_url = "https://doc.rust-lang.org/nightly/")]
#![feature(box_patterns)]
#![feature(drain_filter)]
#![feature(libc)]
#![feature(nll)]
#![feature(proc_macro_internals)]

View file

@ -208,34 +208,31 @@ impl<'a, 'tcx> Collector<'a, 'tcx> {
}
// Update kind and, optionally, the name of all native libraries
// (there may be more than one) with the specified name.
// (there may be more than one) with the specified name. If any
// library is mentioned more than once, keep the latest mention
// of it, so that any possible dependent libraries appear before
// it. (This ensures that the linker is able to see symbols from
// all possible dependent libraries before linking in the library
// in question.)
for &(ref name, ref new_name, kind) in &self.tcx.sess.opts.libs {
let mut found = false;
for lib in self.libs.iter_mut() {
let lib_name = match lib.name {
Some(n) => n,
None => continue,
};
if lib_name == name as &str {
let mut changed = false;
if let Some(k) = kind {
lib.kind = k;
changed = true;
// If we've already added any native libraries with the same
// name, they will be pulled out into `moved`, so that we can
// move them to the end of the list below.
let mut existing = self.libs.drain_filter(|lib| {
if let Some(lib_name) = lib.name {
if lib_name == name as &str {
if let Some(k) = kind {
lib.kind = k;
}
if let &Some(ref new_name) = new_name {
lib.name = Some(Symbol::intern(new_name));
}
return true;
}
if let &Some(ref new_name) = new_name {
lib.name = Some(Symbol::intern(new_name));
changed = true;
}
if !changed {
let msg = format!("redundant linker flag specified for \
library `{}`", name);
self.tcx.sess.warn(&msg);
}
found = true;
}
}
if !found {
false
}).collect::<Vec<_>>();
if existing.is_empty() {
// Add if not found
let new_name = new_name.as_ref().map(|s| &**s); // &Option<String> -> Option<&str>
let lib = NativeLibrary {
@ -246,6 +243,10 @@ impl<'a, 'tcx> Collector<'a, 'tcx> {
wasm_import_module: None,
};
self.register_native_lib(None, lib);
} else {
// Move all existing libraries with the same name to the
// end of the command line.
self.libs.append(&mut existing);
}
}
}