about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Wright <mikerite@lavabit.com>2020-10-27 07:42:13 +0200
committerMichael Wright <mikerite@lavabit.com>2020-10-27 07:42:13 +0200
commit66d56fefc5b7ce22d2db65ee9dc1a5f9f6bf2f09 (patch)
treeb030677266fb104fce1f9240465000a6a6a75b13
parentafbac8906e614a63ff5825710c3ebe45a3b5e01a (diff)
downloadrust-66d56fefc5b7ce22d2db65ee9dc1a5f9f6bf2f09.tar.gz
rust-66d56fefc5b7ce22d2db65ee9dc1a5f9f6bf2f09.zip
Add `invalid_paths` internal lint
-rw-r--r--clippy_lints/src/lib.rs3
-rw-r--r--clippy_lints/src/utils/internal_lints.rs79
-rw-r--r--clippy_lints/src/utils/mod.rs25
-rw-r--r--tests/ui/invalid_paths.rs23
-rw-r--r--tests/ui/invalid_paths.stderr16
5 files changed, 146 insertions, 0 deletions
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 3be8bc0e36d..7c8cb90fe1c 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -892,6 +892,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
         &utils::internal_lints::COMPILER_LINT_FUNCTIONS,
         &utils::internal_lints::DEFAULT_LINT,
+        &utils::internal_lints::INVALID_PATHS,
         &utils::internal_lints::LINT_WITHOUT_LINT_PASS,
         &utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
         &utils::internal_lints::OUTER_EXPN_EXPN_DATA,
@@ -919,6 +920,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
     store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
     store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
+    store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
     store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
     store.register_late_pass(|| box utils::author::Author);
     let vec_box_size_threshold = conf.vec_box_size_threshold;
@@ -1280,6 +1282,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
         LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
         LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
         LintId::of(&utils::internal_lints::DEFAULT_LINT),
+        LintId::of(&utils::internal_lints::INVALID_PATHS),
         LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),
         LintId::of(&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
         LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA),
diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs
index bfe426a25eb..6ca72d895c8 100644
--- a/clippy_lints/src/utils/internal_lints.rs
+++ b/clippy_lints/src/utils/internal_lints.rs
@@ -1,3 +1,4 @@
+use crate::consts::{constant_simple, Constant};
 use crate::utils::{
     is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, qpath_res, run_lints,
     snippet, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq,
@@ -14,9 +15,11 @@ use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
 use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Node, Path, StmtKind, Ty, TyKind};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::{Span, Spanned};
 use rustc_span::symbol::{Symbol, SymbolStr};
+use rustc_typeck::hir_ty_to_ty;
 
 use std::borrow::{Borrow, Cow};
 
@@ -229,6 +232,21 @@ declare_clippy_lint! {
     "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
 }
 
+declare_clippy_lint! {
+    /// **What it does:**
+    /// Checks the paths module for invalid paths.
+    ///
+    /// **Why is this bad?**
+    /// It indicates a bug in the code.
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:** None.
+    pub INVALID_PATHS,
+    internal,
+    "invalid path"
+}
+
 declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
 
 impl EarlyLintPass for ClippyLintsInternal {
@@ -761,3 +779,64 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
 
     None
 }
+
+// This is not a complete resolver for paths. It works on all the paths currently used in the paths
+// module.  That's all it does and all it needs to do.
+pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
+    if path_to_res(cx, path).is_some() {
+        return true;
+    }
+
+    // Some implementations can't be found by `path_to_res`, particularly inherent
+    // implementations of native types. Check lang items.
+    let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
+    let lang_items = cx.tcx.lang_items();
+    for lang_item in lang_items.items() {
+        if let Some(def_id) = lang_item {
+            let lang_item_path = cx.get_def_path(*def_id);
+            if path_syms.starts_with(&lang_item_path) {
+                if let [item] = &path_syms[lang_item_path.len()..] {
+                    for child in cx.tcx.item_children(*def_id) {
+                        if child.ident.name == *item {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    false
+}
+
+declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+        let local_def_id = &cx.tcx.parent_module(item.hir_id);
+        let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
+        if_chain! {
+            if mod_name.as_str() == "paths";
+            if let hir::ItemKind::Const(ty, body_id) = item.kind;
+            let ty = hir_ty_to_ty(cx.tcx, ty);
+            if let ty::Array(el_ty, _) = &ty.kind();
+            if let ty::Ref(_, el_ty, _) = &el_ty.kind();
+            if el_ty.is_str();
+            let body = cx.tcx.hir().body(body_id);
+            let typeck_results = cx.tcx.typeck_body(body_id);
+            if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value);
+            let path: Vec<&str> = path.iter().map(|x| {
+                    if let Constant::Str(s) = x {
+                        s.as_str()
+                    } else {
+                        // We checked the type of the constant above
+                        unreachable!()
+                    }
+                }).collect();
+            if !check_path(cx, &path[..]);
+            then {
+                span_lint(cx, CLIPPY_LINTS_INTERNAL, item.span, "invalid path");
+            }
+        }
+    }
+}
diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs
index 8297b9d128d..a1ecca0961a 100644
--- a/clippy_lints/src/utils/mod.rs
+++ b/clippy_lints/src/utils/mod.rs
@@ -268,6 +268,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
             krate: *krate,
             index: CRATE_DEF_INDEX,
         };
+        let mut current_item = None;
         let mut items = cx.tcx.item_children(krate);
         let mut path_it = path.iter().skip(1).peekable();
 
@@ -277,6 +278,12 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
                 None => return None,
             };
 
+            // `get_def_path` seems to generate these empty segments for extern blocks.
+            // We can just ignore them.
+            if segment.is_empty() {
+                continue;
+            }
+
             let result = SmallVec::<[_; 8]>::new();
             for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() {
                 if item.ident.name.as_str() == *segment {
@@ -284,10 +291,28 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
                         return Some(item.res);
                     }
 
+                    current_item = Some(item);
                     items = cx.tcx.item_children(item.res.def_id());
                     break;
                 }
             }
+
+            // The segment isn't a child_item.
+            // Try to find it under an inherent impl.
+            if_chain! {
+                if path_it.peek().is_none();
+                if let Some(current_item) = current_item;
+                let item_def_id = current_item.res.def_id();
+                if cx.tcx.def_kind(item_def_id) == DefKind::Struct;
+                then {
+                    // Bad `find_map` suggestion. See #4193.
+                    #[allow(clippy::find_map)]
+                    return cx.tcx.inherent_impls(item_def_id).iter()
+                        .flat_map(|&impl_def_id| cx.tcx.item_children(impl_def_id))
+                        .find(|item| item.ident.name.as_str() == *segment)
+                        .map(|item| item.res);
+                }
+            }
         }
     } else {
         None
diff --git a/tests/ui/invalid_paths.rs b/tests/ui/invalid_paths.rs
new file mode 100644
index 00000000000..01e28ae5e9d
--- /dev/null
+++ b/tests/ui/invalid_paths.rs
@@ -0,0 +1,23 @@
+#![warn(clippy::internal)]
+
+mod paths {
+    // Good path
+    pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
+
+    // Path to method on inherent impl of a primitive type
+    pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+
+    // Path to method on inherent impl
+    pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+
+    // Path with empty segment
+    pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
+
+    // Path with bad crate
+    pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
+
+    // Path with bad module
+    pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
+}
+
+fn main() {}
diff --git a/tests/ui/invalid_paths.stderr b/tests/ui/invalid_paths.stderr
new file mode 100644
index 00000000000..bd69d661b71
--- /dev/null
+++ b/tests/ui/invalid_paths.stderr
@@ -0,0 +1,16 @@
+error: invalid path
+  --> $DIR/invalid_paths.rs:17:5
+   |
+LL |     pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::clippy-lints-internal` implied by `-D warnings`
+
+error: invalid path
+  --> $DIR/invalid_paths.rs:20:5
+   |
+LL |     pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+