Description
On some platforms (at least GNU/Linux, but I hear Windows and several others too), if you link together two static libraries that both export a symbol of the same name, it's undefined which symbol actually gets linked. In practice on my machine, the first library seems to win. This lets you defeat type/memory safety without the unsafe
keyword, by having two crates export a #[no_mangle] pub fn
with different signatures but compatible calling conventions:
// one.rs
#![crate_type = "lib"]
#[no_mangle]
pub fn convert(x: &'static i32) -> Result<i32, f32> {
Ok(*x)
}
// two.rs
#![crate_type = "lib"]
#[no_mangle]
pub fn convert(x: &'static f32) -> Result<i32, f32> {
Err(*x)
}
// transmute.rs
extern crate one;
extern crate two;
fn main() {
static X: f32 = 3.14;
let y: i32 = two::convert(&X).unwrap();
println!("{}", y);
}
geofft@titan:/tmp/transmute$ rustc one.rs
geofft@titan:/tmp/transmute$ rustc two.rs
geofft@titan:/tmp/transmute$ rustc transmute.rs -L .
geofft@titan:/tmp/transmute$ ./transmute
1078523331
Despite the stated call to two::convert
, it's actually one::convert
that gets called, which interprets the argument as a &'static i32
. (It may be clearer to understand with this simpler example, which doesn't break type safety.)
On at least GNU/Linux but not other platforms like Windows or Darwin, dynamically-linked symbols have the same ambiguity.
I don't know what the right response is here. The following options all seem pretty reasonable:
- Acknowledge it and ignore it. Maybe document it as a possible source of unsafety, despite not using the
unsafe
keyword. - Have
#[no_mangle]
export both a mangled and un-mangled name, and have Rust crates call each other via mangled names only, on the grounds that#[no_mangle]
is for external interfaces, not for crates linking to crates. ("External interfaces" includes other Rust code using FFI, but FFI is unsafe.) This is analogous to howextern "C" fn
s export both a Rust-ABI symbol as well as a C-ABI shim, and a direct, safe Rust call to those function happens through the Rust ABI, not through the C ABI. I'm pretty sure that all production uses of#[no_mangle]
areextern "C"
, anyway (see e.g. Can't define unsafe extern "C" fn #10025). - Deprecate
#[no_mangle]
on safe functions and data, and introduce a new#[unsafe_no_mangle]
, so it substring-matchesunsafe
. (#[no_mangle]
on unsafe functions or mutable statics is fine, since you need theunsafe
keyword to get at them.)
All of these are, I think, backwards-compatible.