Skip to content

Commit 6b74e3c

Browse files
committed
Add ClashingExternDecl lint.
This lint checks that all declarations for extern fns of the same name are declared with the same types.
1 parent a540b1b commit 6b74e3c

File tree

4 files changed

+233
-5
lines changed

4 files changed

+233
-5
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
}

0 commit comments

Comments
 (0)