Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 66d56fe

Browse files
author
Michael Wright
committed
Add invalid_paths internal lint
1 parent afbac89 commit 66d56fe

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
892892
&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
893893
&utils::internal_lints::COMPILER_LINT_FUNCTIONS,
894894
&utils::internal_lints::DEFAULT_LINT,
895+
&utils::internal_lints::INVALID_PATHS,
895896
&utils::internal_lints::LINT_WITHOUT_LINT_PASS,
896897
&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
897898
&utils::internal_lints::OUTER_EXPN_EXPN_DATA,
@@ -919,6 +920,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
919920
store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
920921
store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
921922
store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
923+
store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
922924
store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
923925
store.register_late_pass(|| box utils::author::Author);
924926
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:
12801282
LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
12811283
LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
12821284
LintId::of(&utils::internal_lints::DEFAULT_LINT),
1285+
LintId::of(&utils::internal_lints::INVALID_PATHS),
12831286
LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),
12841287
LintId::of(&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
12851288
LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA),

clippy_lints/src/utils/internal_lints.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::consts::{constant_simple, Constant};
12
use crate::utils::{
23
is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, qpath_res, run_lints,
34
snippet, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq,
@@ -14,9 +15,11 @@ use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
1415
use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Node, Path, StmtKind, Ty, TyKind};
1516
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
1617
use rustc_middle::hir::map::Map;
18+
use rustc_middle::ty;
1719
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
1820
use rustc_span::source_map::{Span, Spanned};
1921
use rustc_span::symbol::{Symbol, SymbolStr};
22+
use rustc_typeck::hir_ty_to_ty;
2023

2124
use std::borrow::{Borrow, Cow};
2225

@@ -229,6 +232,21 @@ declare_clippy_lint! {
229232
"using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
230233
}
231234

235+
declare_clippy_lint! {
236+
/// **What it does:**
237+
/// Checks the paths module for invalid paths.
238+
///
239+
/// **Why is this bad?**
240+
/// It indicates a bug in the code.
241+
///
242+
/// **Known problems:** None.
243+
///
244+
/// **Example:** None.
245+
pub INVALID_PATHS,
246+
internal,
247+
"invalid path"
248+
}
249+
232250
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
233251

234252
impl EarlyLintPass for ClippyLintsInternal {
@@ -761,3 +779,64 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
761779

762780
None
763781
}
782+
783+
// This is not a complete resolver for paths. It works on all the paths currently used in the paths
784+
// module. That's all it does and all it needs to do.
785+
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
786+
if path_to_res(cx, path).is_some() {
787+
return true;
788+
}
789+
790+
// Some implementations can't be found by `path_to_res`, particularly inherent
791+
// implementations of native types. Check lang items.
792+
let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
793+
let lang_items = cx.tcx.lang_items();
794+
for lang_item in lang_items.items() {
795+
if let Some(def_id) = lang_item {
796+
let lang_item_path = cx.get_def_path(*def_id);
797+
if path_syms.starts_with(&lang_item_path) {
798+
if let [item] = &path_syms[lang_item_path.len()..] {
799+
for child in cx.tcx.item_children(*def_id) {
800+
if child.ident.name == *item {
801+
return true;
802+
}
803+
}
804+
}
805+
}
806+
}
807+
}
808+
809+
false
810+
}
811+
812+
declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
813+
814+
impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
815+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
816+
let local_def_id = &cx.tcx.parent_module(item.hir_id);
817+
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
818+
if_chain! {
819+
if mod_name.as_str() == "paths";
820+
if let hir::ItemKind::Const(ty, body_id) = item.kind;
821+
let ty = hir_ty_to_ty(cx.tcx, ty);
822+
if let ty::Array(el_ty, _) = &ty.kind();
823+
if let ty::Ref(_, el_ty, _) = &el_ty.kind();
824+
if el_ty.is_str();
825+
let body = cx.tcx.hir().body(body_id);
826+
let typeck_results = cx.tcx.typeck_body(body_id);
827+
if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value);
828+
let path: Vec<&str> = path.iter().map(|x| {
829+
if let Constant::Str(s) = x {
830+
s.as_str()
831+
} else {
832+
// We checked the type of the constant above
833+
unreachable!()
834+
}
835+
}).collect();
836+
if !check_path(cx, &path[..]);
837+
then {
838+
span_lint(cx, CLIPPY_LINTS_INTERNAL, item.span, "invalid path");
839+
}
840+
}
841+
}
842+
}

clippy_lints/src/utils/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
268268
krate: *krate,
269269
index: CRATE_DEF_INDEX,
270270
};
271+
let mut current_item = None;
271272
let mut items = cx.tcx.item_children(krate);
272273
let mut path_it = path.iter().skip(1).peekable();
273274

@@ -277,17 +278,41 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> {
277278
None => return None,
278279
};
279280

281+
// `get_def_path` seems to generate these empty segments for extern blocks.
282+
// We can just ignore them.
283+
if segment.is_empty() {
284+
continue;
285+
}
286+
280287
let result = SmallVec::<[_; 8]>::new();
281288
for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() {
282289
if item.ident.name.as_str() == *segment {
283290
if path_it.peek().is_none() {
284291
return Some(item.res);
285292
}
286293

294+
current_item = Some(item);
287295
items = cx.tcx.item_children(item.res.def_id());
288296
break;
289297
}
290298
}
299+
300+
// The segment isn't a child_item.
301+
// Try to find it under an inherent impl.
302+
if_chain! {
303+
if path_it.peek().is_none();
304+
if let Some(current_item) = current_item;
305+
let item_def_id = current_item.res.def_id();
306+
if cx.tcx.def_kind(item_def_id) == DefKind::Struct;
307+
then {
308+
// Bad `find_map` suggestion. See #4193.
309+
#[allow(clippy::find_map)]
310+
return cx.tcx.inherent_impls(item_def_id).iter()
311+
.flat_map(|&impl_def_id| cx.tcx.item_children(impl_def_id))
312+
.find(|item| item.ident.name.as_str() == *segment)
313+
.map(|item| item.res);
314+
}
315+
}
291316
}
292317
} else {
293318
None

tests/ui/invalid_paths.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![warn(clippy::internal)]
2+
3+
mod paths {
4+
// Good path
5+
pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
6+
7+
// Path to method on inherent impl of a primitive type
8+
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
9+
10+
// Path to method on inherent impl
11+
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
12+
13+
// Path with empty segment
14+
pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
15+
16+
// Path with bad crate
17+
pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
18+
19+
// Path with bad module
20+
pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
21+
}
22+
23+
fn main() {}

tests/ui/invalid_paths.stderr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: invalid path
2+
--> $DIR/invalid_paths.rs:17:5
3+
|
4+
LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::clippy-lints-internal` implied by `-D warnings`
8+
9+
error: invalid path
10+
--> $DIR/invalid_paths.rs:20:5
11+
|
12+
LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: aborting due to 2 previous errors
16+

0 commit comments

Comments
 (0)