Skip to content

Commit 85979f5

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 4b2266b commit 85979f5

File tree

3 files changed

+228
-5
lines changed

3 files changed

+228
-5
lines changed

src/libcore/lib.rs

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

src/librustc_lint/builtin.rs

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

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
};

0 commit comments

Comments
 (0)