Skip to content

Commit 463d668

Browse files
committed
auto merge of #16110 : alexcrichton/rust/issue-15460, r=brson
As discovered in #15460, a particular #[link(kind = "static", ...)] line is not actually guaranteed to link the library at all. The reason for this is that if the external library doesn't have any referenced symbols in the object generated by rustc, the entire library is dropped by the linker. For dynamic native libraries, this is solved by passing -lfoo for all downstream compilations unconditionally. For static libraries in rlibs this is solved because the entire archive is bundled in the rlib. The only situation in which this was a problem was when a static native library was linked to a rust dynamic library. This commit brings the behavior of dylibs in line with rlibs by passing the --whole-archive flag to the linker when linking native libraries. On OSX, this uses the -force_load flag. This flag ensures that the entire archive is considered candidate for being linked into the final dynamic library. This is a breaking change because if any static library is included twice in the same compilation unit then the linker will start emitting errors about duplicate definitions now. The fix for this would involve only statically linking to a library once. Closes #15460 [breaking-change]
2 parents 9de2019 + 1ae1461 commit 463d668

File tree

15 files changed

+168
-44
lines changed

15 files changed

+168
-44
lines changed

src/liballoc/heap.rs

+3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ mod imp {
136136
use libc::{c_char, c_int, c_void, size_t};
137137

138138
#[link(name = "jemalloc", kind = "static")]
139+
#[cfg(not(test))]
140+
extern {}
141+
139142
extern {
140143
fn je_mallocx(size: size_t, flags: c_int) -> *mut c_void;
141144
fn je_rallocx(ptr: *mut c_void, size: size_t,

src/librustc/back/link.rs

+49-16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use super::archive::{Archive, ArchiveBuilder, ArchiveConfig, METADATA_FILENAME};
12+
use super::archive;
1213
use super::rpath;
1314
use super::rpath::RPathConfig;
1415
use super::svh::Svh;
@@ -1597,29 +1598,61 @@ fn add_local_native_libraries(cmd: &mut Command, sess: &Session) {
15971598
// For those that support this, we ensure we pass the option if the library
15981599
// was flagged "static" (most defaults are dynamic) to ensure that if
15991600
// libfoo.a and libfoo.so both exist that the right one is chosen.
1600-
let takes_hints = sess.targ_cfg.os != abi::OsMacos && sess.targ_cfg.os != abi::OsiOS;
1601+
let takes_hints = sess.targ_cfg.os != abi::OsMacos &&
1602+
sess.targ_cfg.os != abi::OsiOS;
1603+
1604+
let libs = sess.cstore.get_used_libraries();
1605+
let libs = libs.borrow();
1606+
1607+
let mut staticlibs = libs.iter().filter_map(|&(ref l, kind)| {
1608+
if kind == cstore::NativeStatic {Some(l)} else {None}
1609+
});
1610+
let mut others = libs.iter().filter(|&&(_, kind)| {
1611+
kind != cstore::NativeStatic
1612+
});
1613+
1614+
// Platforms that take hints generally also support the --whole-archive
1615+
// flag. We need to pass this flag when linking static native libraries to
1616+
// ensure the entire library is included.
1617+
//
1618+
// For more details see #15460, but the gist is that the linker will strip
1619+
// away any unused objects in the archive if we don't otherwise explicitly
1620+
// reference them. This can occur for libraries which are just providing
1621+
// bindings, libraries with generic functions, etc.
1622+
if takes_hints {
1623+
cmd.arg("-Wl,--whole-archive").arg("-Wl,-Bstatic");
1624+
}
1625+
let search_path = archive_search_paths(sess);
1626+
for l in staticlibs {
1627+
if takes_hints {
1628+
cmd.arg(format!("-l{}", l));
1629+
} else {
1630+
// -force_load is the OSX equivalent of --whole-archive, but it
1631+
// involves passing the full path to the library to link.
1632+
let lib = archive::find_library(l.as_slice(),
1633+
sess.targ_cfg.os,
1634+
search_path.as_slice(),
1635+
&sess.diagnostic().handler);
1636+
let mut v = b"-Wl,-force_load,".to_vec();
1637+
v.push_all(lib.as_vec());
1638+
cmd.arg(v.as_slice());
1639+
}
1640+
}
1641+
if takes_hints {
1642+
cmd.arg("-Wl,--no-whole-archive").arg("-Wl,-Bdynamic");
1643+
}
16011644

1602-
for &(ref l, kind) in sess.cstore.get_used_libraries().borrow().iter() {
1645+
for &(ref l, kind) in others {
16031646
match kind {
1604-
cstore::NativeUnknown | cstore::NativeStatic => {
1605-
if takes_hints {
1606-
if kind == cstore::NativeStatic {
1607-
cmd.arg("-Wl,-Bstatic");
1608-
} else {
1609-
cmd.arg("-Wl,-Bdynamic");
1610-
}
1611-
}
1612-
cmd.arg(format!("-l{}", *l));
1647+
cstore::NativeUnknown => {
1648+
cmd.arg(format!("-l{}", l));
16131649
}
16141650
cstore::NativeFramework => {
1615-
cmd.arg("-framework");
1616-
cmd.arg(l.as_slice());
1651+
cmd.arg("-framework").arg(l.as_slice());
16171652
}
1653+
cstore::NativeStatic => unreachable!(),
16181654
}
16191655
}
1620-
if takes_hints {
1621-
cmd.arg("-Wl,-Bdynamic");
1622-
}
16231656
}
16241657

16251658
// # Rust Crate linking

src/librustc_back/archive.rs

+27-24
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,30 @@ fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
9595
}
9696
}
9797

98+
pub fn find_library(name: &str, os: abi::Os, search_paths: &[Path],
99+
handler: &ErrorHandler) -> Path {
100+
let (osprefix, osext) = match os {
101+
abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
102+
};
103+
// On Windows, static libraries sometimes show up as libfoo.a and other
104+
// times show up as foo.lib
105+
let oslibname = format!("{}{}.{}", osprefix, name, osext);
106+
let unixlibname = format!("lib{}.a", name);
107+
108+
for path in search_paths.iter() {
109+
debug!("looking for {} inside {}", name, path.display());
110+
let test = path.join(oslibname.as_slice());
111+
if test.exists() { return test }
112+
if oslibname != unixlibname {
113+
let test = path.join(unixlibname.as_slice());
114+
if test.exists() { return test }
115+
}
116+
}
117+
handler.fatal(format!("could not find native static library `{}`, \
118+
perhaps an -L flag is missing?",
119+
name).as_slice());
120+
}
121+
98122
impl<'a> Archive<'a> {
99123
fn new(config: ArchiveConfig<'a>) -> Archive<'a> {
100124
let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
@@ -153,7 +177,9 @@ impl<'a> ArchiveBuilder<'a> {
153177
/// Adds all of the contents of a native library to this archive. This will
154178
/// search in the relevant locations for a library named `name`.
155179
pub fn add_native_library(&mut self, name: &str) -> io::IoResult<()> {
156-
let location = self.find_library(name);
180+
let location = find_library(name, self.archive.os,
181+
self.archive.lib_search_paths.as_slice(),
182+
self.archive.handler);
157183
self.add_archive(&location, name, [])
158184
}
159185

@@ -285,28 +311,5 @@ impl<'a> ArchiveBuilder<'a> {
285311
}
286312
Ok(())
287313
}
288-
289-
fn find_library(&self, name: &str) -> Path {
290-
let (osprefix, osext) = match self.archive.os {
291-
abi::OsWin32 => ("", "lib"), _ => ("lib", "a"),
292-
};
293-
// On Windows, static libraries sometimes show up as libfoo.a and other
294-
// times show up as foo.lib
295-
let oslibname = format!("{}{}.{}", osprefix, name, osext);
296-
let unixlibname = format!("lib{}.a", name);
297-
298-
for path in self.archive.lib_search_paths.iter() {
299-
debug!("looking for {} inside {}", name, path.display());
300-
let test = path.join(oslibname.as_slice());
301-
if test.exists() { return test }
302-
if oslibname != unixlibname {
303-
let test = path.join(unixlibname.as_slice());
304-
if test.exists() { return test }
305-
}
306-
}
307-
self.archive.handler.fatal(format!("could not find native static library `{}`, \
308-
perhaps an -L flag is missing?",
309-
name).as_slice());
310-
}
311314
}
312315

src/librustrt/unwind.rs

+3
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub unsafe fn try(f: ||) -> ::core::result::Result<(), Box<Any + Send>> {
159159
}
160160

161161
#[link(name = "rustrt_native", kind = "static")]
162+
#[cfg(not(test))]
163+
extern {}
164+
162165
extern {
163166
// Rust's try-catch
164167
// When f(...) returns normally, the return value is null.

src/libstd/rt/backtrace.rs

+3
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ mod imp {
415415
errnum: libc::c_int);
416416
enum backtrace_state {}
417417
#[link(name = "backtrace", kind = "static")]
418+
#[cfg(not(test))]
419+
extern {}
420+
418421
extern {
419422
fn backtrace_create_state(filename: *const libc::c_char,
420423
threaded: libc::c_int,

src/libstd/rtdeps.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#![experimental]
1616

1717
// All platforms need to link to rustrt
18+
#[cfg(not(test))]
1819
#[link(name = "rust_builtin", kind = "static")]
1920
extern {}
2021

src/test/run-make/extern-fn-with-union/test.rs

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ extern crate testcrate;
1212

1313
use std::mem;
1414

15-
#[link(name = "test", kind = "static")]
1615
extern {
1716
fn give_back(tu: testcrate::TestUnion) -> u64;
1817
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-include ../tools.mk
2+
3+
all: $(TMPDIR)/libfoo.a
4+
$(RUSTC) foo.rs -C extra-filename=-383hf8
5+
$(RUSTC) bar.rs
6+
$(call RUN,bar)

src/test/run-make/issue-15460/bar.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
extern crate foo;
12+
fn main() {
13+
unsafe { foo::foo() }
14+
}

src/test/run-make/issue-15460/foo.c

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void foo() {}

src/test/run-make/issue-15460/foo.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_type = "dylib"]
12+
13+
#[link(name = "foo", kind = "static")]
14+
extern {
15+
pub fn foo();
16+
}

src/test/run-make/no-duplicate-libs/Makefile

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
-include ../tools.mk
22

3-
all: $(call STATICLIB,foo) $(call STATICLIB,bar)
3+
all:
4+
$(RUSTC) foo.rs
5+
$(RUSTC) bar.rs
46
$(RUSTC) main.rs
57
$(call RUN,main)
6-
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![no_std]
12+
#![feature(lang_items)]
13+
#![crate_type = "dylib"]
14+
15+
extern crate libc;
16+
17+
#[no_mangle]
18+
pub extern fn bar() {}
19+
20+
#[lang = "stack_exhausted"] fn stack_exhausted() {}
21+
#[lang = "eh_personality"] fn eh_personality() {}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![no_std]
12+
#![feature(lang_items)]
13+
#![crate_type = "dylib"]
14+
15+
extern crate libc;
16+
17+
#[no_mangle]
18+
pub extern fn foo() {}
19+
20+
#[lang = "stack_exhausted"] fn stack_exhausted() {}
21+
#[lang = "eh_personality"] fn eh_personality() {}

src/test/run-pass/foreign-dupe.rs

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ mod rustrt1 {
2222
mod rustrt2 {
2323
extern crate libc;
2424

25-
#[link(name = "rust_test_helpers")]
2625
extern {
2726
pub fn rust_get_test_int() -> libc::intptr_t;
2827
}

0 commit comments

Comments
 (0)