Skip to content

Commit

Permalink
new lint: mutable_key_type
Browse files Browse the repository at this point in the history
  • Loading branch information
llogiq committed Dec 14, 2019
1 parent d82debb commit e6d638f
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,7 @@ Released 2018-09-13
[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are 340 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 341 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:

Expand Down
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ pub mod missing_doc;
pub mod missing_inline;
pub mod mul_add;
pub mod multiple_crate_versions;
pub mod mut_key;
pub mod mut_mut;
pub mod mut_reference;
pub mod mutable_debug_assertion;
Expand Down Expand Up @@ -662,6 +663,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
&missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
&mul_add::MANUAL_MUL_ADD,
&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
&mut_key::MUTABLE_KEY_TYPE,
&mut_mut::MUT_MUT,
&mut_reference::UNNECESSARY_MUT_PASSED,
&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
Expand Down Expand Up @@ -937,6 +939,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
store.register_late_pass(|| box trait_bounds::TraitBounds);
store.register_late_pass(|| box comparison_chain::ComparisonChain);
store.register_late_pass(|| box mul_add::MulAddCheck);
store.register_late_pass(|| box mut_key::MutableKeyType);
store.register_early_pass(|| box reference::DerefAddrOf);
store.register_early_pass(|| box reference::RefInDeref);
store.register_early_pass(|| box double_parens::DoubleParens);
Expand Down Expand Up @@ -1218,6 +1221,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN),
LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN),
LintId::of(&misc_early::ZERO_PREFIXED_LITERAL),
LintId::of(&mut_key::MUTABLE_KEY_TYPE),
LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
LintId::of(&mutex_atomic::MUTEX_ATOMIC),
Expand Down Expand Up @@ -1529,6 +1533,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
LintId::of(&misc::CMP_NAN),
LintId::of(&misc::FLOAT_CMP),
LintId::of(&misc::MODULO_ONE),
LintId::of(&mut_key::MUTABLE_KEY_TYPE),
LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
Expand Down
120 changes: 120 additions & 0 deletions clippy_lints/src/mut_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method, walk_ptrs_ty};
use rustc::declare_lint_pass;
use rustc::hir;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::ty::{Adt, Dynamic, Opaque, Param, RawPtr, Ref, Ty, TypeAndMut};
use rustc_session::declare_tool_lint;
use syntax::source_map::Span;

declare_clippy_lint! {
/// **What it does:** Checks for sets/maps with mutable key types.
///
/// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and
/// `BtreeSet` rely on either the hash or the order of keys be unchanging,
/// so having types with interior mutability is a bad idea.
///
/// **Known problems:** We don't currently account for `Rc` or `Arc`, so
/// this may yield false positives.
///
/// **Example:**
/// ```rust
/// use std::cmp::{PartialEq, Eq};
/// use std::collections::HashSet;
/// use std::hash::{Hash, Hasher};
/// use std::sync::atomic::AtomicUsize;
///# #[allow(unused)]
///
/// struct Bad(AtomicUsize);
/// impl PartialEq for Bad {
/// fn eq(&self, rhs: &Self) -> bool {
/// ..
/// ; unimplemented!();
/// }
/// }
///
/// impl Eq for Bad {}
///
/// impl Hash for Bad {
/// fn hash<H: Hasher>(&self, h: &mut H) {
/// ..
/// ; unimplemented!();
/// }
/// }
///
/// fn main() {
/// let _: HashSet<Bad> = HashSet::new();
/// }
/// ```
pub MUTABLE_KEY_TYPE,
correctness,
"Check for mutable Map/Set key type"
}

declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MutableKeyType {
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
check_sig(cx, item.hir_id, &sig.decl);
}
}

fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem) {
if let hir::ImplItemKind::Method(ref sig, ..) = item.kind {
if trait_ref_of_method(cx, item.hir_id).is_none() {
check_sig(cx, item.hir_id, &sig.decl);
}
}
}

fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem) {
if let hir::TraitItemKind::Method(ref sig, ..) = item.kind {
check_sig(cx, item.hir_id, &sig.decl);
}
}

fn check_local(&mut self, cx: &LateContext<'_, '_>, local: &hir::Local) {
if let hir::PatKind::Wild = local.pat.kind {
return;
}
check_ty(cx, local.span, cx.tables.pat_ty(&*local.pat));
}
}

fn check_sig<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl) {
let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
let fn_sig = cx.tcx.fn_sig(fn_def_id);
for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) {
check_ty(cx, hir_ty.span, ty);
}
check_ty(
cx,
decl.output.span(),
cx.tcx.erase_late_bound_regions(&fn_sig.output()),
);
}

// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
// generics (because the compiler cannot ensure immutability for unknown types).
fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, ty: Ty<'tcx>) {
let ty = walk_ptrs_ty(ty);
if let Adt(def, substs) = ty.kind {
if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET]
.iter()
.any(|path| match_def_path(cx, def.did, &**path))
{
let key_type = substs.type_at(0);
if is_concrete_type(key_type) && !key_type.is_freeze(cx.tcx, cx.param_env, span) {
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
}
}
}
}

fn is_concrete_type(ty: Ty<'_>) -> bool {
match ty.kind {
RawPtr(TypeAndMut { ty: inner_ty, .. }) | Ref(_, inner_ty, _) => is_concrete_type(inner_ty),
Dynamic(..) | Opaque(..) | Param(..) => false,
_ => true,
}
}
9 changes: 8 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 340] = [
pub const ALL_LINTS: [Lint; 341] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -1232,6 +1232,13 @@ pub const ALL_LINTS: [Lint; 340] = [
deprecation: None,
module: "loops",
},
Lint {
name: "mutable_key_type",
group: "correctness",
desc: "Check for mutable Map/Set key type",
deprecation: None,
module: "mut_key",
},
Lint {
name: "mutex_atomic",
group: "perf",
Expand Down
37 changes: 37 additions & 0 deletions tests/ui/mut_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};

struct Key(AtomicUsize);

impl Clone for Key {
fn clone(&self) -> Self {
Key(AtomicUsize::new(self.0.load(Relaxed)))
}
}

impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
self.0.load(Relaxed) == other.0.load(Relaxed)
}
}

impl Eq for Key {}

impl Hash for Key {
fn hash<H: Hasher>(&self, h: &mut H) {
self.0.load(Relaxed).hash(h);
}
}

fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
let _other: HashMap<Key, bool> = HashMap::new();
m.keys().cloned().collect()
}

fn this_is_ok(m: &mut HashMap<usize, Key>) {}

fn main() {
let _ = should_not_take_this_arg(&mut HashMap::new(), 1);
this_is_ok(&mut HashMap::new());
}
22 changes: 22 additions & 0 deletions tests/ui/mut_key.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error: mutable key type
--> $DIR/mut_key.rs:27:32
|
LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[deny(clippy::mutable_key_type)]` on by default

error: mutable key type
--> $DIR/mut_key.rs:27:72
|
LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
| ^^^^^^^^^^^^

error: mutable key type
--> $DIR/mut_key.rs:28:5
|
LL | let _other: HashMap<Key, bool> = HashMap::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 3 previous errors

0 comments on commit e6d638f

Please sign in to comment.