Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new lint: mutable_key_type #4885

Merged
merged 1 commit into from
Dec 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,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 @@ -667,6 +668,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 @@ -939,6 +941,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 @@ -1223,6 +1226,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 @@ -1532,6 +1536,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<'tcx>) {
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<'tcx>) {
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<'tcx>) {
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 @@ -1239,6 +1239,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