Skip to content

Commit 228a0ed

Browse files
committed
Auto merge of #70946 - jumbatm:clashing-extern-decl, r=nagisa
Add a lint to catch clashing `extern` fn declarations. Closes #69390. Adds lint `clashing_extern_decl` to detect when, within a single crate, an extern function of the same name is declared with different types. Because two symbols of the same name cannot be resolved to two different functions at link time, and one function cannot possibly have two types, a clashing extern declaration is almost certainly a mistake. This lint does not run between crates because a project may have dependencies which both rely on the same extern function, but declare it in a different (but valid) way. For example, they may both declare an opaque type for one or more of the arguments (which would end up distinct types), or use types that are valid conversions in the language the extern fn is defined in. In these cases, we can't say that the clashing declaration is incorrect. r? @eddyb
2 parents 7058471 + 556b7ba commit 228a0ed

14 files changed

+568
-11
lines changed

src/libcore/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ pub mod primitive;
277277
// crate uses the this crate as its libcore.
278278
#[path = "../stdarch/crates/core_arch/src/mod.rs"]
279279
#[allow(missing_docs, missing_debug_implementations, dead_code, unused_imports)]
280+
// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_decl is
281+
// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
282+
#[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
280283
#[unstable(feature = "stdsimd", issue = "48556")]
281284
mod core_arch;
282285

src/librustc_lint/builtin.rs

+226-5
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ use rustc_ast::attr::{self, HasAttrs};
2626
use rustc_ast::tokenstream::{TokenStream, TokenTree};
2727
use rustc_ast::visit::{FnCtxt, FnKind};
2828
use rustc_ast_pretty::pprust::{self, expr_to_string};
29-
use rustc_data_structures::fx::FxHashSet;
30-
use rustc_errors::{Applicability, DiagnosticBuilder};
29+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
30+
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
3131
use rustc_feature::{deprecated_attributes, AttributeGate, AttributeTemplate, AttributeType};
3232
use rustc_feature::{GateIssue, Stability};
3333
use rustc_hir as hir;
3434
use rustc_hir::def::{DefKind, Res};
3535
use rustc_hir::def_id::DefId;
36-
use rustc_hir::{GenericParamKind, PatKind};
37-
use rustc_hir::{HirIdSet, Node};
36+
use rustc_hir::{ForeignItemKind, GenericParamKind, PatKind};
37+
use rustc_hir::{HirId, HirIdSet, Node};
3838
use rustc_middle::lint::LintDiagnosticBuilder;
3939
use rustc_middle::ty::subst::GenericArgKind;
4040
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -48,7 +48,7 @@ use rustc_trait_selection::traits::misc::can_type_implement_copy;
4848

4949
use crate::nonstandard_style::{method_context, MethodLateContext};
5050

51-
use log::debug;
51+
use log::{debug, trace};
5252
use std::fmt::Write;
5353

5454
// hardwired lints from librustc_middle
@@ -2053,3 +2053,224 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
20532053
}
20542054
}
20552055
}
2056+
2057+
declare_lint! {
2058+
pub CLASHING_EXTERN_DECL,
2059+
Warn,
2060+
"detects when an extern fn has been declared with the same name but different types"
2061+
}
2062+
2063+
pub struct ClashingExternDecl {
2064+
seen_decls: FxHashMap<Symbol, HirId>,
2065+
}
2066+
2067+
/// Differentiate between whether the name for an extern decl came from the link_name attribute or
2068+
/// just from declaration itself. This is important because we don't want to report clashes on
2069+
/// symbol name if they don't actually clash because one or the other links against a symbol with a
2070+
/// different name.
2071+
enum SymbolName {
2072+
/// The name of the symbol + the span of the annotation which introduced the link name.
2073+
Link(Symbol, Span),
2074+
/// No link name, so just the name of the symbol.
2075+
Normal(Symbol),
2076+
}
2077+
2078+
impl SymbolName {
2079+
fn get_name(&self) -> Symbol {
2080+
match self {
2081+
SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
2082+
}
2083+
}
2084+
}
2085+
2086+
impl ClashingExternDecl {
2087+
crate fn new() -> Self {
2088+
ClashingExternDecl { seen_decls: FxHashMap::default() }
2089+
}
2090+
/// Insert a new foreign item into the seen set. If a symbol with the same name already exists
2091+
/// for the item, return its HirId without updating the set.
2092+
fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> {
2093+
let hid = fi.hir_id;
2094+
2095+
let name =
2096+
&tcx.codegen_fn_attrs(tcx.hir().local_def_id(hid)).link_name.unwrap_or(fi.ident.name);
2097+
2098+
if self.seen_decls.contains_key(name) {
2099+
// Avoid updating the map with the new entry when we do find a collision. We want to
2100+
// make sure we're always pointing to the first definition as the previous declaration.
2101+
// This lets us avoid emitting "knock-on" diagnostics.
2102+
Some(*self.seen_decls.get(name).unwrap())
2103+
} else {
2104+
self.seen_decls.insert(*name, hid)
2105+
}
2106+
}
2107+
2108+
/// Get the name of the symbol that's linked against for a given extern declaration. That is,
2109+
/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
2110+
/// symbol's name.
2111+
fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName {
2112+
let did = tcx.hir().local_def_id(fi.hir_id);
2113+
if let Some((overridden_link_name, overridden_link_name_span)) =
2114+
tcx.codegen_fn_attrs(did).link_name.map(|overridden_link_name| {
2115+
// FIXME: Instead of searching through the attributes again to get span
2116+
// information, we could have codegen_fn_attrs also give span information back for
2117+
// where the attribute was defined. However, until this is found to be a
2118+
// bottleneck, this does just fine.
2119+
(
2120+
overridden_link_name,
2121+
tcx.get_attrs(did.to_def_id())
2122+
.iter()
2123+
.find(|at| at.check_name(sym::link_name))
2124+
.unwrap()
2125+
.span,
2126+
)
2127+
})
2128+
{
2129+
SymbolName::Link(overridden_link_name, overridden_link_name_span)
2130+
} else {
2131+
SymbolName::Normal(fi.ident.name)
2132+
}
2133+
}
2134+
2135+
/// Checks whether two types are structurally the same enough that the declarations shouldn't
2136+
/// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
2137+
/// with the same members (as the declarations shouldn't clash).
2138+
fn structurally_same_type<'a, 'tcx>(
2139+
cx: &LateContext<'a, 'tcx>,
2140+
a: Ty<'tcx>,
2141+
b: Ty<'tcx>,
2142+
) -> bool {
2143+
let tcx = cx.tcx;
2144+
if a == b || rustc_middle::ty::TyS::same_type(a, b) {
2145+
// All nominally-same types are structurally same, too.
2146+
true
2147+
} else {
2148+
// Do a full, depth-first comparison between the two.
2149+
use rustc_middle::ty::TyKind::*;
2150+
let a_kind = &a.kind;
2151+
let b_kind = &b.kind;
2152+
2153+
match (a_kind, b_kind) {
2154+
(Adt(..), Adt(..)) => {
2155+
// Adts are pretty straightforward: just compare the layouts.
2156+
use rustc_target::abi::LayoutOf;
2157+
let a_layout = cx.layout_of(a).unwrap().layout;
2158+
let b_layout = cx.layout_of(b).unwrap().layout;
2159+
a_layout == b_layout
2160+
}
2161+
(Array(a_ty, a_const), Array(b_ty, b_const)) => {
2162+
// For arrays, we also check the constness of the type.
2163+
a_const.val == b_const.val
2164+
&& Self::structurally_same_type(cx, a_const.ty, b_const.ty)
2165+
&& Self::structurally_same_type(cx, a_ty, b_ty)
2166+
}
2167+
(Slice(a_ty), Slice(b_ty)) => Self::structurally_same_type(cx, a_ty, b_ty),
2168+
(RawPtr(a_tymut), RawPtr(b_tymut)) => {
2169+
a_tymut.mutbl == a_tymut.mutbl
2170+
&& Self::structurally_same_type(cx, &a_tymut.ty, &b_tymut.ty)
2171+
}
2172+
(Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
2173+
// For structural sameness, we don't need the region to be same.
2174+
a_mut == b_mut && Self::structurally_same_type(cx, a_ty, b_ty)
2175+
}
2176+
(FnDef(..), FnDef(..)) => {
2177+
// As we don't compare regions, skip_binder is fine.
2178+
let a_poly_sig = a.fn_sig(tcx);
2179+
let b_poly_sig = b.fn_sig(tcx);
2180+
2181+
let a_sig = a_poly_sig.skip_binder();
2182+
let b_sig = b_poly_sig.skip_binder();
2183+
2184+
(a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
2185+
== (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
2186+
&& a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
2187+
Self::structurally_same_type(cx, a, b)
2188+
})
2189+
&& Self::structurally_same_type(cx, a_sig.output(), b_sig.output())
2190+
}
2191+
(Tuple(a_substs), Tuple(b_substs)) => {
2192+
a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| {
2193+
Self::structurally_same_type(cx, a_ty, b_ty)
2194+
})
2195+
}
2196+
// For these, it's not quite as easy to define structural-sameness quite so easily.
2197+
// For the purposes of this lint, take the conservative approach and mark them as
2198+
// not structurally same.
2199+
(Dynamic(..), Dynamic(..))
2200+
| (Error(..), Error(..))
2201+
| (Closure(..), Closure(..))
2202+
| (Generator(..), Generator(..))
2203+
| (GeneratorWitness(..), GeneratorWitness(..))
2204+
| (Projection(..), Projection(..))
2205+
| (Opaque(..), Opaque(..)) => false,
2206+
// These definitely should have been caught above.
2207+
(Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
2208+
_ => false,
2209+
}
2210+
}
2211+
}
2212+
}
2213+
2214+
impl_lint_pass!(ClashingExternDecl => [CLASHING_EXTERN_DECL]);
2215+
2216+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ClashingExternDecl {
2217+
fn check_foreign_item(&mut self, cx: &LateContext<'a, 'tcx>, this_fi: &hir::ForeignItem<'_>) {
2218+
trace!("ClashingExternDecl: check_foreign_item: {:?}", this_fi);
2219+
if let ForeignItemKind::Fn(..) = this_fi.kind {
2220+
let tcx = *&cx.tcx;
2221+
if let Some(existing_hid) = self.insert(tcx, this_fi) {
2222+
let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid));
2223+
let this_decl_ty = tcx.type_of(tcx.hir().local_def_id(this_fi.hir_id));
2224+
debug!(
2225+
"ClashingExternDecl: Comparing existing {:?}: {:?} to this {:?}: {:?}",
2226+
existing_hid, existing_decl_ty, this_fi.hir_id, this_decl_ty
2227+
);
2228+
// Check that the declarations match.
2229+
if !Self::structurally_same_type(cx, existing_decl_ty, this_decl_ty) {
2230+
let orig_fi = tcx.hir().expect_foreign_item(existing_hid);
2231+
let orig = Self::name_of_extern_decl(tcx, orig_fi);
2232+
2233+
// We want to ensure that we use spans for both decls that include where the
2234+
// name was defined, whether that was from the link_name attribute or not.
2235+
let get_relevant_span =
2236+
|fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) {
2237+
SymbolName::Normal(_) => fi.span,
2238+
SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
2239+
};
2240+
// Finally, emit the diagnostic.
2241+
tcx.struct_span_lint_hir(
2242+
CLASHING_EXTERN_DECL,
2243+
this_fi.hir_id,
2244+
get_relevant_span(this_fi),
2245+
|lint| {
2246+
let mut expected_str = DiagnosticStyledString::new();
2247+
expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false);
2248+
let mut found_str = DiagnosticStyledString::new();
2249+
found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
2250+
2251+
lint.build(&format!(
2252+
"`{}` redeclare{} with a different signature",
2253+
this_fi.ident.name,
2254+
if orig.get_name() == this_fi.ident.name {
2255+
"d".to_string()
2256+
} else {
2257+
format!("s `{}`", orig.get_name())
2258+
}
2259+
))
2260+
.span_label(
2261+
get_relevant_span(orig_fi),
2262+
&format!("`{}` previously declared here", orig.get_name()),
2263+
)
2264+
.span_label(
2265+
get_relevant_span(this_fi),
2266+
"this signature doesn't match the previous declaration",
2267+
)
2268+
.note_expected_found(&"", expected_str, &"", found_str)
2269+
.emit()
2270+
},
2271+
);
2272+
}
2273+
}
2274+
}
2275+
}
2276+
}

src/librustc_lint/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![feature(bool_to_option)]
3131
#![feature(box_syntax)]
3232
#![feature(crate_visibility_modifier)]
33+
#![feature(iter_order_by)]
3334
#![feature(never_type)]
3435
#![feature(nll)]
3536
#![feature(or_patterns)]
@@ -154,6 +155,7 @@ macro_rules! late_lint_passes {
154155
// and change this to a module lint pass
155156
MissingDebugImplementations: MissingDebugImplementations::default(),
156157
ArrayIntoIter: ArrayIntoIter,
158+
ClashingExternDecl: ClashingExternDecl::new(),
157159
]
158160
);
159161
};

src/libstd/sys/unix/args.rs

+2
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,15 @@ mod imp {
205205
#[cfg(target_arch = "aarch64")]
206206
extern "C" {
207207
fn objc_msgSend(obj: NsId, sel: Sel) -> NsId;
208+
#[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
208209
#[link_name = "objc_msgSend"]
209210
fn objc_msgSend_ul(obj: NsId, sel: Sel, i: libc::c_ulong) -> NsId;
210211
}
211212

212213
#[cfg(not(target_arch = "aarch64"))]
213214
extern "C" {
214215
fn objc_msgSend(obj: NsId, sel: Sel, ...) -> NsId;
216+
#[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
215217
#[link_name = "objc_msgSend"]
216218
fn objc_msgSend_ul(obj: NsId, sel: Sel, ...) -> NsId;
217219
}

src/test/ui/issues/issue-1866.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// build-pass
22
#![allow(dead_code)]
33
#![allow(non_camel_case_types)]
4+
#![warn(clashing_extern_decl)]
45

56
// pretty-expanded FIXME #23616
67

@@ -20,6 +21,7 @@ mod b {
2021
use super::rust_task;
2122
extern {
2223
pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
24+
//~^ WARN `rust_task_is_unwinding` redeclared with a different signature
2325
}
2426
}
2527
}

src/test/ui/issues/issue-1866.stderr

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
warning: `rust_task_is_unwinding` redeclared with a different signature
2+
--> $DIR/issue-1866.rs:23:13
3+
|
4+
LL | pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
5+
| ------------------------------------------------------------ `rust_task_is_unwinding` previously declared here
6+
...
7+
LL | pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
9+
|
10+
note: the lint level is defined here
11+
--> $DIR/issue-1866.rs:4:9
12+
|
13+
LL | #![warn(clashing_extern_decl)]
14+
| ^^^^^^^^^^^^^^^^^^^^
15+
= note: expected `unsafe extern "C" fn(*const usize) -> bool`
16+
found `unsafe extern "C" fn(*const bool) -> bool`
17+
18+
warning: 1 warning emitted
19+

src/test/ui/issues/issue-5791.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// run-pass
22
#![allow(dead_code)]
3+
#![warn(clashing_extern_decl)]
34
// pretty-expanded FIXME #23616
45

56
extern {
67
#[link_name = "malloc"]
78
fn malloc1(len: i32) -> *const u8;
89
#[link_name = "malloc"]
10+
//~^ WARN `malloc2` redeclares `malloc` with a different signature
911
fn malloc2(len: i32, foo: i32) -> *const u8;
1012
}
1113

src/test/ui/issues/issue-5791.stderr

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
warning: `malloc2` redeclares `malloc` with a different signature
2+
--> $DIR/issue-5791.rs:9:5
3+
|
4+
LL | / #[link_name = "malloc"]
5+
LL | | fn malloc1(len: i32) -> *const u8;
6+
| |______________________________________- `malloc` previously declared here
7+
LL | / #[link_name = "malloc"]
8+
LL | |
9+
LL | | fn malloc2(len: i32, foo: i32) -> *const u8;
10+
| |________________________________________________^ this signature doesn't match the previous declaration
11+
|
12+
note: the lint level is defined here
13+
--> $DIR/issue-5791.rs:3:9
14+
|
15+
LL | #![warn(clashing_extern_decl)]
16+
| ^^^^^^^^^^^^^^^^^^^^
17+
= note: expected `unsafe extern "C" fn(i32) -> *const u8`
18+
found `unsafe extern "C" fn(i32, i32) -> *const u8`
19+
20+
warning: 1 warning emitted
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extern {
2+
pub fn extern_fn(x: u8);
3+
}

0 commit comments

Comments
 (0)