Skip to content

Commit 2cc8c39

Browse files
committed
Auto merge of #4885 - rust-lang:mut-key-types, r=flip1995
new lint: mutable_key_type This fixes #732 – well, partly, it doesn't adress `Hash` impls, but the use of mutable types as map keys or set members changelog: add lint [`mutable_key_type`] r? @flip1995
2 parents 143ad5e + e6d638f commit 2cc8c39

File tree

7 files changed

+194
-2
lines changed

7 files changed

+194
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,7 @@ Released 2018-09-13
11941194
[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
11951195
[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
11961196
[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
1197+
[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
11971198
[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
11981199
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
11991200
[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

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

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

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ pub mod missing_doc;
240240
pub mod missing_inline;
241241
pub mod mul_add;
242242
pub mod multiple_crate_versions;
243+
pub mod mut_key;
243244
pub mod mut_mut;
244245
pub mod mut_reference;
245246
pub mod mutable_debug_assertion;
@@ -667,6 +668,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
667668
&missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
668669
&mul_add::MANUAL_MUL_ADD,
669670
&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
671+
&mut_key::MUTABLE_KEY_TYPE,
670672
&mut_mut::MUT_MUT,
671673
&mut_reference::UNNECESSARY_MUT_PASSED,
672674
&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
@@ -939,6 +941,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
939941
store.register_late_pass(|| box trait_bounds::TraitBounds);
940942
store.register_late_pass(|| box comparison_chain::ComparisonChain);
941943
store.register_late_pass(|| box mul_add::MulAddCheck);
944+
store.register_late_pass(|| box mut_key::MutableKeyType);
942945
store.register_early_pass(|| box reference::DerefAddrOf);
943946
store.register_early_pass(|| box reference::RefInDeref);
944947
store.register_early_pass(|| box double_parens::DoubleParens);
@@ -1223,6 +1226,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
12231226
LintId::of(&misc_early::UNNEEDED_FIELD_PATTERN),
12241227
LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN),
12251228
LintId::of(&misc_early::ZERO_PREFIXED_LITERAL),
1229+
LintId::of(&mut_key::MUTABLE_KEY_TYPE),
12261230
LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED),
12271231
LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
12281232
LintId::of(&mutex_atomic::MUTEX_ATOMIC),
@@ -1532,6 +1536,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
15321536
LintId::of(&misc::CMP_NAN),
15331537
LintId::of(&misc::FLOAT_CMP),
15341538
LintId::of(&misc::MODULO_ONE),
1539+
LintId::of(&mut_key::MUTABLE_KEY_TYPE),
15351540
LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
15361541
LintId::of(&non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
15371542
LintId::of(&non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),

clippy_lints/src/mut_key.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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, Dynamic, Opaque, Param, RawPtr, Ref, Ty, TypeAndMut};
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+
if let hir::PatKind::Wild = local.pat.kind {
78+
return;
79+
}
80+
check_ty(cx, local.span, cx.tables.pat_ty(&*local.pat));
81+
}
82+
}
83+
84+
fn check_sig<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl) {
85+
let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
86+
let fn_sig = cx.tcx.fn_sig(fn_def_id);
87+
for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) {
88+
check_ty(cx, hir_ty.span, ty);
89+
}
90+
check_ty(
91+
cx,
92+
decl.output.span(),
93+
cx.tcx.erase_late_bound_regions(&fn_sig.output()),
94+
);
95+
}
96+
97+
// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
98+
// generics (because the compiler cannot ensure immutability for unknown types).
99+
fn check_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, span: Span, ty: Ty<'tcx>) {
100+
let ty = walk_ptrs_ty(ty);
101+
if let Adt(def, substs) = ty.kind {
102+
if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET]
103+
.iter()
104+
.any(|path| match_def_path(cx, def.did, &**path))
105+
{
106+
let key_type = substs.type_at(0);
107+
if is_concrete_type(key_type) && !key_type.is_freeze(cx.tcx, cx.param_env, span) {
108+
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
109+
}
110+
}
111+
}
112+
}
113+
114+
fn is_concrete_type(ty: Ty<'_>) -> bool {
115+
match ty.kind {
116+
RawPtr(TypeAndMut { ty: inner_ty, .. }) | Ref(_, inner_ty, _) => is_concrete_type(inner_ty),
117+
Dynamic(..) | Opaque(..) | Param(..) => false,
118+
_ => true,
119+
}
120+
}

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 340] = [
9+
pub const ALL_LINTS: [Lint; 341] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1239,6 +1239,13 @@ pub const ALL_LINTS: [Lint; 340] = [
12391239
deprecation: None,
12401240
module: "loops",
12411241
},
1242+
Lint {
1243+
name: "mutable_key_type",
1244+
group: "correctness",
1245+
desc: "Check for mutable Map/Set key type",
1246+
deprecation: None,
1247+
module: "mut_key",
1248+
},
12421249
Lint {
12431250
name: "mutex_atomic",
12441251
group: "perf",

tests/ui/mut_key.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::collections::{HashMap, HashSet};
2+
use std::hash::{Hash, Hasher};
3+
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
4+
5+
struct Key(AtomicUsize);
6+
7+
impl Clone for Key {
8+
fn clone(&self) -> Self {
9+
Key(AtomicUsize::new(self.0.load(Relaxed)))
10+
}
11+
}
12+
13+
impl PartialEq for Key {
14+
fn eq(&self, other: &Self) -> bool {
15+
self.0.load(Relaxed) == other.0.load(Relaxed)
16+
}
17+
}
18+
19+
impl Eq for Key {}
20+
21+
impl Hash for Key {
22+
fn hash<H: Hasher>(&self, h: &mut H) {
23+
self.0.load(Relaxed).hash(h);
24+
}
25+
}
26+
27+
fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
28+
let _other: HashMap<Key, bool> = HashMap::new();
29+
m.keys().cloned().collect()
30+
}
31+
32+
fn this_is_ok(m: &mut HashMap<usize, Key>) {}
33+
34+
fn main() {
35+
let _ = should_not_take_this_arg(&mut HashMap::new(), 1);
36+
this_is_ok(&mut HashMap::new());
37+
}

tests/ui/mut_key.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: mutable key type
2+
--> $DIR/mut_key.rs:27:32
3+
|
4+
LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[deny(clippy::mutable_key_type)]` on by default
8+
9+
error: mutable key type
10+
--> $DIR/mut_key.rs:27:72
11+
|
12+
LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
13+
| ^^^^^^^^^^^^
14+
15+
error: mutable key type
16+
--> $DIR/mut_key.rs:28:5
17+
|
18+
LL | let _other: HashMap<Key, bool> = HashMap::new();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)