|
| 1 | +use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method, walk_ptrs_ty}; |
| 2 | +use rustc::declare_lint_pass; |
| 3 | +use rustc::hir; |
| 4 | +use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; |
| 5 | +use rustc::ty::{Adt, Ty}; |
| 6 | +use rustc_session::declare_tool_lint; |
| 7 | +use syntax::source_map::Span; |
| 8 | + |
| 9 | +declare_clippy_lint! { |
| 10 | + /// **What it does:** Checks for sets/maps with mutable key types. |
| 11 | + /// |
| 12 | + /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and |
| 13 | + /// `BtreeSet` rely on either the hash or the order of keys be unchanging, |
| 14 | + /// so having types with interior mutability is a bad idea. |
| 15 | + /// |
| 16 | + /// **Known problems:** We don't currently account for `Rc` or `Arc`, so |
| 17 | + /// this may yield false positives. |
| 18 | + /// |
| 19 | + /// **Example:** |
| 20 | + /// ```rust |
| 21 | + /// use std::cmp::{PartialEq, Eq}; |
| 22 | + /// use std::collections::HashSet; |
| 23 | + /// use std::hash::{Hash, Hasher}; |
| 24 | + /// use std::sync::atomic::AtomicUsize; |
| 25 | + ///# #[allow(unused)] |
| 26 | + /// |
| 27 | + /// struct Bad(AtomicUsize); |
| 28 | + /// impl PartialEq for Bad { |
| 29 | + /// fn eq(&self, rhs: &Self) -> bool { |
| 30 | + /// .. |
| 31 | + /// ; unimplemented!(); |
| 32 | + /// } |
| 33 | + /// } |
| 34 | + /// |
| 35 | + /// impl Eq for Bad {} |
| 36 | + /// |
| 37 | + /// impl Hash for Bad { |
| 38 | + /// fn hash<H: Hasher>(&self, h: &mut H) { |
| 39 | + /// .. |
| 40 | + /// ; unimplemented!(); |
| 41 | + /// } |
| 42 | + /// } |
| 43 | + /// |
| 44 | + /// fn main() { |
| 45 | + /// let _: HashSet<Bad> = HashSet::new(); |
| 46 | + /// } |
| 47 | + /// ``` |
| 48 | + pub MUTABLE_KEY_TYPE, |
| 49 | + correctness, |
| 50 | + "Check for mutable Map/Set key type" |
| 51 | +} |
| 52 | + |
| 53 | +declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]); |
| 54 | + |
| 55 | +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutableKeyType { |
| 56 | + fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) { |
| 57 | + if let hir::ItemKind::Fn(ref sig, ..) = item.kind { |
| 58 | + check_sig(cx, item.hir_id, &sig.decl); |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem) { |
| 63 | + if let hir::ImplItemKind::Method(ref sig, ..) = item.kind { |
| 64 | + if trait_ref_of_method(cx, item.hir_id).is_none() { |
| 65 | + check_sig(cx, item.hir_id, &sig.decl); |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem) { |
| 71 | + if let hir::TraitItemKind::Method(ref sig, ..) = item.kind { |
| 72 | + check_sig(cx, item.hir_id, &sig.decl); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &hir::Local) { |
| 77 | + check_ty(cx, local.span, cx.tables.pat_ty(&*local.pat)); |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +fn check_sig<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl) { |
| 82 | + let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); |
| 83 | + let fn_sig = cx.tcx.fn_sig(fn_def_id); |
| 84 | + check_ty( |
| 85 | + cx, |
| 86 | + decl.output.span(), |
| 87 | + cx.tcx.erase_late_bound_regions(&fn_sig.output()), |
| 88 | + ); |
| 89 | + for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) { |
| 90 | + check_ty(cx, hir_ty.span, ty); |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased |
| 95 | +// generics (because the compiler cannot ensure immutability for unknown types). |
| 96 | +fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, ty: Ty<'tcx>) { |
| 97 | + let ty = walk_ptrs_ty(ty); |
| 98 | + if let Adt(def, substs) = ty.kind { |
| 99 | + if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET] |
| 100 | + .iter() |
| 101 | + .any(|path| match_def_path(cx, def.did, &**path)) |
| 102 | + && !substs.type_at(0).is_freeze(cx.tcx, cx.param_env, span) |
| 103 | + && substs.non_erasable_generics().next().is_none() |
| 104 | + { |
| 105 | + span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type"); |
| 106 | + } |
| 107 | + } |
| 108 | +} |
0 commit comments