about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libcore/lib.rs3
-rw-r--r--src/librustc_lint/builtin.rs231
-rw-r--r--src/librustc_lint/lib.rs2
-rw-r--r--src/libstd/sys/unix/args.rs2
-rw-r--r--src/test/ui/issues/issue-1866.rs2
-rw-r--r--src/test/ui/issues/issue-1866.stderr19
-rw-r--r--src/test/ui/issues/issue-5791.rs2
-rw-r--r--src/test/ui/issues/issue-5791.stderr21
-rw-r--r--src/test/ui/lint/auxiliary/external_extern_fn.rs3
-rw-r--r--src/test/ui/lint/clashing-extern-fn.rs159
-rw-r--r--src/test/ui/lint/clashing-extern-fn.stderr121
-rw-r--r--src/test/ui/lint/dead-code/lint-dead-code-3.rs1
-rw-r--r--src/test/ui/lint/dead-code/lint-dead-code-3.stderr12
-rw-r--r--src/test/ui/parser/extern-abi-from-mac-literal-frag.rs1
14 files changed, 568 insertions, 11 deletions
diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs
index fe05e914e6d..0115c4df2fd 100644
--- a/src/libcore/lib.rs
+++ b/src/libcore/lib.rs
@@ -277,6 +277,9 @@ pub mod primitive;
 // crate uses the this crate as its libcore.
 #[path = "../stdarch/crates/core_arch/src/mod.rs"]
 #[allow(missing_docs, missing_debug_implementations, dead_code, unused_imports)]
+// FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_decl is
+// merged. It currently cannot because bootstrap fails as the lint hasn't been defined yet.
+#[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
 #[unstable(feature = "stdsimd", issue = "48556")]
 mod core_arch;
 
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index efe60ce1b88..b7f728ec60c 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -26,15 +26,15 @@ use rustc_ast::attr::{self, HasAttrs};
 use rustc_ast::tokenstream::{TokenStream, TokenTree};
 use rustc_ast::visit::{FnCtxt, FnKind};
 use rustc_ast_pretty::pprust::{self, expr_to_string};
-use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString};
 use rustc_feature::{deprecated_attributes, AttributeGate, AttributeTemplate, AttributeType};
 use rustc_feature::{GateIssue, Stability};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
-use rustc_hir::{GenericParamKind, PatKind};
-use rustc_hir::{HirIdSet, Node};
+use rustc_hir::{ForeignItemKind, GenericParamKind, PatKind};
+use rustc_hir::{HirId, HirIdSet, Node};
 use rustc_middle::lint::LintDiagnosticBuilder;
 use rustc_middle::ty::subst::GenericArgKind;
 use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -48,7 +48,7 @@ use rustc_trait_selection::traits::misc::can_type_implement_copy;
 
 use crate::nonstandard_style::{method_context, MethodLateContext};
 
-use log::debug;
+use log::{debug, trace};
 use std::fmt::Write;
 
 // hardwired lints from librustc_middle
@@ -2053,3 +2053,224 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
         }
     }
 }
+
+declare_lint! {
+    pub CLASHING_EXTERN_DECL,
+    Warn,
+    "detects when an extern fn has been declared with the same name but different types"
+}
+
+pub struct ClashingExternDecl {
+    seen_decls: FxHashMap<Symbol, HirId>,
+}
+
+/// Differentiate between whether the name for an extern decl came from the link_name attribute or
+/// just from declaration itself. This is important because we don't want to report clashes on
+/// symbol name if they don't actually clash because one or the other links against a symbol with a
+/// different name.
+enum SymbolName {
+    /// The name of the symbol + the span of the annotation which introduced the link name.
+    Link(Symbol, Span),
+    /// No link name, so just the name of the symbol.
+    Normal(Symbol),
+}
+
+impl SymbolName {
+    fn get_name(&self) -> Symbol {
+        match self {
+            SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
+        }
+    }
+}
+
+impl ClashingExternDecl {
+    crate fn new() -> Self {
+        ClashingExternDecl { seen_decls: FxHashMap::default() }
+    }
+    /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
+    /// for the item, return its HirId without updating the set.
+    fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> {
+        let hid = fi.hir_id;
+
+        let name =
+            &tcx.codegen_fn_attrs(tcx.hir().local_def_id(hid)).link_name.unwrap_or(fi.ident.name);
+
+        if self.seen_decls.contains_key(name) {
+            // Avoid updating the map with the new entry when we do find a collision. We want to
+            // make sure we're always pointing to the first definition as the previous declaration.
+            // This lets us avoid emitting "knock-on" diagnostics.
+            Some(*self.seen_decls.get(name).unwrap())
+        } else {
+            self.seen_decls.insert(*name, hid)
+        }
+    }
+
+    /// Get the name of the symbol that's linked against for a given extern declaration. That is,
+    /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
+    /// symbol's name.
+    fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName {
+        let did = tcx.hir().local_def_id(fi.hir_id);
+        if let Some((overridden_link_name, overridden_link_name_span)) =
+            tcx.codegen_fn_attrs(did).link_name.map(|overridden_link_name| {
+                // FIXME: Instead of searching through the attributes again to get span
+                // information, we could have codegen_fn_attrs also give span information back for
+                // where the attribute was defined. However, until this is found to be a
+                // bottleneck, this does just fine.
+                (
+                    overridden_link_name,
+                    tcx.get_attrs(did.to_def_id())
+                        .iter()
+                        .find(|at| at.check_name(sym::link_name))
+                        .unwrap()
+                        .span,
+                )
+            })
+        {
+            SymbolName::Link(overridden_link_name, overridden_link_name_span)
+        } else {
+            SymbolName::Normal(fi.ident.name)
+        }
+    }
+
+    /// Checks whether two types are structurally the same enough that the declarations shouldn't
+    /// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
+    /// with the same members (as the declarations shouldn't clash).
+    fn structurally_same_type<'a, 'tcx>(
+        cx: &LateContext<'a, 'tcx>,
+        a: Ty<'tcx>,
+        b: Ty<'tcx>,
+    ) -> bool {
+        let tcx = cx.tcx;
+        if a == b || rustc_middle::ty::TyS::same_type(a, b) {
+            // All nominally-same types are structurally same, too.
+            true
+        } else {
+            // Do a full, depth-first comparison between the two.
+            use rustc_middle::ty::TyKind::*;
+            let a_kind = &a.kind;
+            let b_kind = &b.kind;
+
+            match (a_kind, b_kind) {
+                (Adt(..), Adt(..)) => {
+                    // Adts are pretty straightforward: just compare the layouts.
+                    use rustc_target::abi::LayoutOf;
+                    let a_layout = cx.layout_of(a).unwrap().layout;
+                    let b_layout = cx.layout_of(b).unwrap().layout;
+                    a_layout == b_layout
+                }
+                (Array(a_ty, a_const), Array(b_ty, b_const)) => {
+                    // For arrays, we also check the constness of the type.
+                    a_const.val == b_const.val
+                        && Self::structurally_same_type(cx, a_const.ty, b_const.ty)
+                        && Self::structurally_same_type(cx, a_ty, b_ty)
+                }
+                (Slice(a_ty), Slice(b_ty)) => Self::structurally_same_type(cx, a_ty, b_ty),
+                (RawPtr(a_tymut), RawPtr(b_tymut)) => {
+                    a_tymut.mutbl == a_tymut.mutbl
+                        && Self::structurally_same_type(cx, &a_tymut.ty, &b_tymut.ty)
+                }
+                (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
+                    // For structural sameness, we don't need the region to be same.
+                    a_mut == b_mut && Self::structurally_same_type(cx, a_ty, b_ty)
+                }
+                (FnDef(..), FnDef(..)) => {
+                    // As we don't compare regions, skip_binder is fine.
+                    let a_poly_sig = a.fn_sig(tcx);
+                    let b_poly_sig = b.fn_sig(tcx);
+
+                    let a_sig = a_poly_sig.skip_binder();
+                    let b_sig = b_poly_sig.skip_binder();
+
+                    (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
+                        == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
+                        && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
+                            Self::structurally_same_type(cx, a, b)
+                        })
+                        && Self::structurally_same_type(cx, a_sig.output(), b_sig.output())
+                }
+                (Tuple(a_substs), Tuple(b_substs)) => {
+                    a_substs.types().eq_by(b_substs.types(), |a_ty, b_ty| {
+                        Self::structurally_same_type(cx, a_ty, b_ty)
+                    })
+                }
+                // For these, it's not quite as easy to define structural-sameness quite so easily.
+                // For the purposes of this lint, take the conservative approach and mark them as
+                // not structurally same.
+                (Dynamic(..), Dynamic(..))
+                | (Error(..), Error(..))
+                | (Closure(..), Closure(..))
+                | (Generator(..), Generator(..))
+                | (GeneratorWitness(..), GeneratorWitness(..))
+                | (Projection(..), Projection(..))
+                | (Opaque(..), Opaque(..)) => false,
+                // These definitely should have been caught above.
+                (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
+                _ => false,
+            }
+        }
+    }
+}
+
+impl_lint_pass!(ClashingExternDecl => [CLASHING_EXTERN_DECL]);
+
+impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ClashingExternDecl {
+    fn check_foreign_item(&mut self, cx: &LateContext<'a, 'tcx>, this_fi: &hir::ForeignItem<'_>) {
+        trace!("ClashingExternDecl: check_foreign_item: {:?}", this_fi);
+        if let ForeignItemKind::Fn(..) = this_fi.kind {
+            let tcx = *&cx.tcx;
+            if let Some(existing_hid) = self.insert(tcx, this_fi) {
+                let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid));
+                let this_decl_ty = tcx.type_of(tcx.hir().local_def_id(this_fi.hir_id));
+                debug!(
+                    "ClashingExternDecl: Comparing existing {:?}: {:?} to this {:?}: {:?}",
+                    existing_hid, existing_decl_ty, this_fi.hir_id, this_decl_ty
+                );
+                // Check that the declarations match.
+                if !Self::structurally_same_type(cx, existing_decl_ty, this_decl_ty) {
+                    let orig_fi = tcx.hir().expect_foreign_item(existing_hid);
+                    let orig = Self::name_of_extern_decl(tcx, orig_fi);
+
+                    // We want to ensure that we use spans for both decls that include where the
+                    // name was defined, whether that was from the link_name attribute or not.
+                    let get_relevant_span =
+                        |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) {
+                            SymbolName::Normal(_) => fi.span,
+                            SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
+                        };
+                    // Finally, emit the diagnostic.
+                    tcx.struct_span_lint_hir(
+                        CLASHING_EXTERN_DECL,
+                        this_fi.hir_id,
+                        get_relevant_span(this_fi),
+                        |lint| {
+                            let mut expected_str = DiagnosticStyledString::new();
+                            expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false);
+                            let mut found_str = DiagnosticStyledString::new();
+                            found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
+
+                            lint.build(&format!(
+                                "`{}` redeclare{} with a different signature",
+                                this_fi.ident.name,
+                                if orig.get_name() == this_fi.ident.name {
+                                    "d".to_string()
+                                } else {
+                                    format!("s `{}`", orig.get_name())
+                                }
+                            ))
+                            .span_label(
+                                get_relevant_span(orig_fi),
+                                &format!("`{}` previously declared here", orig.get_name()),
+                            )
+                            .span_label(
+                                get_relevant_span(this_fi),
+                                "this signature doesn't match the previous declaration",
+                            )
+                            .note_expected_found(&"", expected_str, &"", found_str)
+                            .emit()
+                        },
+                    );
+                }
+            }
+        }
+    }
+}
diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs
index b791d313fc4..ca2ca3145ab 100644
--- a/src/librustc_lint/lib.rs
+++ b/src/librustc_lint/lib.rs
@@ -30,6 +30,7 @@
 #![feature(bool_to_option)]
 #![feature(box_syntax)]
 #![feature(crate_visibility_modifier)]
+#![feature(iter_order_by)]
 #![feature(never_type)]
 #![feature(nll)]
 #![feature(or_patterns)]
@@ -154,6 +155,7 @@ macro_rules! late_lint_passes {
                 // and change this to a module lint pass
                 MissingDebugImplementations: MissingDebugImplementations::default(),
                 ArrayIntoIter: ArrayIntoIter,
+                ClashingExternDecl: ClashingExternDecl::new(),
             ]
         );
     };
diff --git a/src/libstd/sys/unix/args.rs b/src/libstd/sys/unix/args.rs
index 4c3e8542d57..1d1cdda1257 100644
--- a/src/libstd/sys/unix/args.rs
+++ b/src/libstd/sys/unix/args.rs
@@ -205,6 +205,7 @@ mod imp {
         #[cfg(target_arch = "aarch64")]
         extern "C" {
             fn objc_msgSend(obj: NsId, sel: Sel) -> NsId;
+            #[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
             #[link_name = "objc_msgSend"]
             fn objc_msgSend_ul(obj: NsId, sel: Sel, i: libc::c_ulong) -> NsId;
         }
@@ -212,6 +213,7 @@ mod imp {
         #[cfg(not(target_arch = "aarch64"))]
         extern "C" {
             fn objc_msgSend(obj: NsId, sel: Sel, ...) -> NsId;
+            #[cfg_attr(not(bootstrap), allow(clashing_extern_decl))]
             #[link_name = "objc_msgSend"]
             fn objc_msgSend_ul(obj: NsId, sel: Sel, ...) -> NsId;
         }
diff --git a/src/test/ui/issues/issue-1866.rs b/src/test/ui/issues/issue-1866.rs
index e4fe26800ef..668baefa5e4 100644
--- a/src/test/ui/issues/issue-1866.rs
+++ b/src/test/ui/issues/issue-1866.rs
@@ -1,6 +1,7 @@
 // build-pass
 #![allow(dead_code)]
 #![allow(non_camel_case_types)]
+#![warn(clashing_extern_decl)]
 
 // pretty-expanded FIXME #23616
 
@@ -20,6 +21,7 @@ mod b {
         use super::rust_task;
         extern {
             pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
+            //~^ WARN `rust_task_is_unwinding` redeclared with a different signature
         }
     }
 }
diff --git a/src/test/ui/issues/issue-1866.stderr b/src/test/ui/issues/issue-1866.stderr
new file mode 100644
index 00000000000..13c08ebd373
--- /dev/null
+++ b/src/test/ui/issues/issue-1866.stderr
@@ -0,0 +1,19 @@
+warning: `rust_task_is_unwinding` redeclared with a different signature
+  --> $DIR/issue-1866.rs:23:13
+   |
+LL |             pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
+   |             ------------------------------------------------------------ `rust_task_is_unwinding` previously declared here
+...
+LL |             pub fn rust_task_is_unwinding(rt: *const rust_task) -> bool;
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+note: the lint level is defined here
+  --> $DIR/issue-1866.rs:4:9
+   |
+LL | #![warn(clashing_extern_decl)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+   = note: expected `unsafe extern "C" fn(*const usize) -> bool`
+              found `unsafe extern "C" fn(*const bool) -> bool`
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/issues/issue-5791.rs b/src/test/ui/issues/issue-5791.rs
index 2f8bf1e9369..fda72a1b20e 100644
--- a/src/test/ui/issues/issue-5791.rs
+++ b/src/test/ui/issues/issue-5791.rs
@@ -1,11 +1,13 @@
 // run-pass
 #![allow(dead_code)]
+#![warn(clashing_extern_decl)]
 // pretty-expanded FIXME #23616
 
 extern {
     #[link_name = "malloc"]
     fn malloc1(len: i32) -> *const u8;
     #[link_name = "malloc"]
+    //~^ WARN `malloc2` redeclares `malloc` with a different signature
     fn malloc2(len: i32, foo: i32) -> *const u8;
 }
 
diff --git a/src/test/ui/issues/issue-5791.stderr b/src/test/ui/issues/issue-5791.stderr
new file mode 100644
index 00000000000..7ae83c43f13
--- /dev/null
+++ b/src/test/ui/issues/issue-5791.stderr
@@ -0,0 +1,21 @@
+warning: `malloc2` redeclares `malloc` with a different signature
+  --> $DIR/issue-5791.rs:9:5
+   |
+LL | /     #[link_name = "malloc"]
+LL | |     fn malloc1(len: i32) -> *const u8;
+   | |______________________________________- `malloc` previously declared here
+LL | /     #[link_name = "malloc"]
+LL | |
+LL | |     fn malloc2(len: i32, foo: i32) -> *const u8;
+   | |________________________________________________^ this signature doesn't match the previous declaration
+   |
+note: the lint level is defined here
+  --> $DIR/issue-5791.rs:3:9
+   |
+LL | #![warn(clashing_extern_decl)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+   = note: expected `unsafe extern "C" fn(i32) -> *const u8`
+              found `unsafe extern "C" fn(i32, i32) -> *const u8`
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/lint/auxiliary/external_extern_fn.rs b/src/test/ui/lint/auxiliary/external_extern_fn.rs
new file mode 100644
index 00000000000..b2caebc6fee
--- /dev/null
+++ b/src/test/ui/lint/auxiliary/external_extern_fn.rs
@@ -0,0 +1,3 @@
+extern {
+    pub fn extern_fn(x: u8);
+}
diff --git a/src/test/ui/lint/clashing-extern-fn.rs b/src/test/ui/lint/clashing-extern-fn.rs
new file mode 100644
index 00000000000..32f3a78f4e9
--- /dev/null
+++ b/src/test/ui/lint/clashing-extern-fn.rs
@@ -0,0 +1,159 @@
+// check-pass
+// aux-build:external_extern_fn.rs
+#![crate_type = "lib"]
+#![warn(clashing_extern_decl)]
+
+extern crate external_extern_fn;
+
+extern {
+    fn clash(x: u8);
+    fn no_clash(x: u8);
+}
+
+fn redeclared_different_signature() {
+    extern {
+        fn clash(x: u64); //~ WARN `clash` redeclared with a different signature
+    }
+
+    unsafe {
+        clash(123);
+        no_clash(123);
+    }
+}
+
+fn redeclared_same_signature() {
+    extern {
+        fn no_clash(x: u8);
+    }
+    unsafe {
+        no_clash(123);
+    }
+}
+
+extern {
+    fn extern_fn(x: u64);
+}
+
+fn extern_clash() {
+    extern {
+        fn extern_fn(x: u32); //~ WARN `extern_fn` redeclared with a different signature
+    }
+    unsafe {
+        extern_fn(123);
+    }
+}
+
+fn extern_no_clash() {
+    unsafe {
+        external_extern_fn::extern_fn(123);
+        crate::extern_fn(123);
+    }
+}
+extern {
+    fn some_other_new_name(x: i16);
+
+    #[link_name = "extern_link_name"]
+    fn some_new_name(x: i16);
+
+    #[link_name = "link_name_same"]
+    fn both_names_different(x: i16);
+}
+
+fn link_name_clash() {
+    extern {
+        fn extern_link_name(x: u32);
+        //~^ WARN `extern_link_name` redeclared with a different signature
+
+        #[link_name = "some_other_new_name"]
+        //~^ WARN `some_other_extern_link_name` redeclares `some_other_new_name` with a different
+        fn some_other_extern_link_name(x: u32);
+
+        #[link_name = "link_name_same"]
+        //~^ WARN `other_both_names_different` redeclares `link_name_same` with a different
+        fn other_both_names_different(x: u32);
+    }
+}
+
+mod a {
+    extern {
+        fn different_mod(x: u8);
+    }
+}
+mod b {
+    extern {
+        fn different_mod(x: u64); //~ WARN `different_mod` redeclared with a different signature
+    }
+}
+
+extern {
+    fn variadic_decl(x: u8, ...);
+}
+
+fn variadic_clash() {
+    extern {
+        fn variadic_decl(x: u8); //~ WARN `variadic_decl` redeclared with a different signature
+    }
+}
+
+#[no_mangle]
+fn no_mangle_name(x: u8) { }
+
+extern {
+    #[link_name = "unique_link_name"]
+    fn link_name_specified(x: u8);
+}
+
+fn tricky_no_clash() {
+    extern {
+        // Shouldn't warn, because the declaration above actually declares a different symbol (and
+        // Rust's name resolution rules around shadowing will handle this gracefully).
+        fn link_name_specified() -> u32;
+
+        // The case of a no_mangle name colliding with an extern decl (see #28179) is related but
+        // shouldn't be reported by ClashingExternDecl, because this is an example of unmangled
+        // name clash causing bad behaviour in functions with a defined body.
+        fn no_mangle_name() -> u32;
+    }
+}
+
+mod banana {
+    mod one {
+        #[repr(C)] struct Banana { weight: u32, length: u16 }
+        extern "C" { fn weigh_banana(count: *const Banana) -> u64; }
+    }
+
+    mod two {
+        #[repr(C)] struct Banana { weight: u32, length: u16 } // note: distinct type
+        // This should not trigger the lint because two::Banana is structurally equivalent to
+        // one::Banana.
+        extern "C" { fn weigh_banana(count: *const Banana) -> u64; }
+    }
+
+    mod three {
+        // This _should_ trigger the lint, because repr(packed) should generate a struct that has a
+        // different layout.
+        #[repr(packed)] struct Banana { weight: u32, length: u16 }
+        #[allow(improper_ctypes)]
+        extern "C" { fn weigh_banana(count: *const Banana) -> u64; }
+        //~^ WARN `weigh_banana` redeclared with a different signature
+    }
+}
+
+mod sameish_members {
+    mod a {
+        #[repr(C)]
+        struct Point { x: i16, y: i16 }
+
+        extern "C" { fn draw_point(p: Point); }
+    }
+    mod b {
+        #[repr(C)]
+        struct Point { coordinates: [i16; 2] }
+
+        // It's possible we are overconservative for this case, as accessing the elements of the
+        // coordinates array might end up correctly accessing `.x` and `.y`. However, this may not
+        // always be the case, for every architecture and situation. This is also a really odd
+        // thing to do anyway.
+        extern "C" { fn draw_point(p: Point); } //~ WARN `draw_point` redeclared with a different
+    }
+}
diff --git a/src/test/ui/lint/clashing-extern-fn.stderr b/src/test/ui/lint/clashing-extern-fn.stderr
new file mode 100644
index 00000000000..fb7bf135f53
--- /dev/null
+++ b/src/test/ui/lint/clashing-extern-fn.stderr
@@ -0,0 +1,121 @@
+warning: `clash` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:15:9
+   |
+LL |     fn clash(x: u8);
+   |     ---------------- `clash` previously declared here
+...
+LL |         fn clash(x: u64);
+   |         ^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+note: the lint level is defined here
+  --> $DIR/clashing-extern-fn.rs:4:9
+   |
+LL | #![warn(clashing_extern_decl)]
+   |         ^^^^^^^^^^^^^^^^^^^^
+   = note: expected `unsafe extern "C" fn(u8)`
+              found `unsafe extern "C" fn(u64)`
+
+warning: `extern_fn` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:39:9
+   |
+LL |     fn extern_fn(x: u64);
+   |     --------------------- `extern_fn` previously declared here
+...
+LL |         fn extern_fn(x: u32);
+   |         ^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(u64)`
+              found `unsafe extern "C" fn(u32)`
+
+warning: `extern_link_name` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:64:9
+   |
+LL | /     #[link_name = "extern_link_name"]
+LL | |     fn some_new_name(x: i16);
+   | |_____________________________- `extern_link_name` previously declared here
+...
+LL |           fn extern_link_name(x: u32);
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(i16)`
+              found `unsafe extern "C" fn(u32)`
+
+warning: `some_other_extern_link_name` redeclares `some_other_new_name` with a different signature
+  --> $DIR/clashing-extern-fn.rs:67:9
+   |
+LL |       fn some_other_new_name(x: i16);
+   |       ------------------------------- `some_other_new_name` previously declared here
+...
+LL | /         #[link_name = "some_other_new_name"]
+LL | |
+LL | |         fn some_other_extern_link_name(x: u32);
+   | |_______________________________________________^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(i16)`
+              found `unsafe extern "C" fn(u32)`
+
+warning: `other_both_names_different` redeclares `link_name_same` with a different signature
+  --> $DIR/clashing-extern-fn.rs:71:9
+   |
+LL | /     #[link_name = "link_name_same"]
+LL | |     fn both_names_different(x: i16);
+   | |____________________________________- `link_name_same` previously declared here
+...
+LL | /         #[link_name = "link_name_same"]
+LL | |
+LL | |         fn other_both_names_different(x: u32);
+   | |______________________________________________^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(i16)`
+              found `unsafe extern "C" fn(u32)`
+
+warning: `different_mod` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:84:9
+   |
+LL |         fn different_mod(x: u8);
+   |         ------------------------ `different_mod` previously declared here
+...
+LL |         fn different_mod(x: u64);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(u8)`
+              found `unsafe extern "C" fn(u64)`
+
+warning: `variadic_decl` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:94:9
+   |
+LL |     fn variadic_decl(x: u8, ...);
+   |     ----------------------------- `variadic_decl` previously declared here
+...
+LL |         fn variadic_decl(x: u8);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(u8, ...)`
+              found `unsafe extern "C" fn(u8)`
+
+warning: `weigh_banana` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:137:22
+   |
+LL |         extern "C" { fn weigh_banana(count: *const Banana) -> u64; }
+   |                      --------------------------------------------- `weigh_banana` previously declared here
+...
+LL |         extern "C" { fn weigh_banana(count: *const Banana) -> u64; }
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(*const banana::one::Banana) -> u64`
+              found `unsafe extern "C" fn(*const banana::three::Banana) -> u64`
+
+warning: `draw_point` redeclared with a different signature
+  --> $DIR/clashing-extern-fn.rs:157:22
+   |
+LL |         extern "C" { fn draw_point(p: Point); }
+   |                      ------------------------ `draw_point` previously declared here
+...
+LL |         extern "C" { fn draw_point(p: Point); }
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
+   |
+   = note: expected `unsafe extern "C" fn(sameish_members::a::Point)`
+              found `unsafe extern "C" fn(sameish_members::b::Point)`
+
+warning: 9 warnings emitted
+
diff --git a/src/test/ui/lint/dead-code/lint-dead-code-3.rs b/src/test/ui/lint/dead-code/lint-dead-code-3.rs
index 6826d2cd67e..ff33abfa645 100644
--- a/src/test/ui/lint/dead-code/lint-dead-code-3.rs
+++ b/src/test/ui/lint/dead-code/lint-dead-code-3.rs
@@ -1,5 +1,6 @@
 #![allow(unused_variables)]
 #![allow(non_camel_case_types)]
+#![allow(clashing_extern_decl)]
 #![deny(dead_code)]
 
 #![crate_type="lib"]
diff --git a/src/test/ui/lint/dead-code/lint-dead-code-3.stderr b/src/test/ui/lint/dead-code/lint-dead-code-3.stderr
index 6d174e8d9bc..cf8f01ea19f 100644
--- a/src/test/ui/lint/dead-code/lint-dead-code-3.stderr
+++ b/src/test/ui/lint/dead-code/lint-dead-code-3.stderr
@@ -1,35 +1,35 @@
 error: struct is never constructed: `Foo`
-  --> $DIR/lint-dead-code-3.rs:13:8
+  --> $DIR/lint-dead-code-3.rs:14:8
    |
 LL | struct Foo;
    |        ^^^
    |
 note: the lint level is defined here
-  --> $DIR/lint-dead-code-3.rs:3:9
+  --> $DIR/lint-dead-code-3.rs:4:9
    |
 LL | #![deny(dead_code)]
    |         ^^^^^^^^^
 
 error: associated function is never used: `foo`
-  --> $DIR/lint-dead-code-3.rs:15:8
+  --> $DIR/lint-dead-code-3.rs:16:8
    |
 LL |     fn foo(&self) {
    |        ^^^
 
 error: function is never used: `bar`
-  --> $DIR/lint-dead-code-3.rs:20:4
+  --> $DIR/lint-dead-code-3.rs:21:4
    |
 LL | fn bar() {
    |    ^^^
 
 error: enum is never used: `c_void`
-  --> $DIR/lint-dead-code-3.rs:59:6
+  --> $DIR/lint-dead-code-3.rs:60:6
    |
 LL | enum c_void {}
    |      ^^^^^^
 
 error: function is never used: `free`
-  --> $DIR/lint-dead-code-3.rs:61:5
+  --> $DIR/lint-dead-code-3.rs:62:5
    |
 LL |     fn free(p: *const c_void);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/test/ui/parser/extern-abi-from-mac-literal-frag.rs b/src/test/ui/parser/extern-abi-from-mac-literal-frag.rs
index 4ecb21d26ab..a516aa44c65 100644
--- a/src/test/ui/parser/extern-abi-from-mac-literal-frag.rs
+++ b/src/test/ui/parser/extern-abi-from-mac-literal-frag.rs
@@ -1,3 +1,4 @@
+#![allow(clashing_extern_decl)]
 // check-pass
 
 // In this test we check that the parser accepts an ABI string when it