Skip to content

Commit 746a005

Browse files
committed
Auto merge of #7640 - kneasle:mut-key-false-positive, r=camsteffen
Improve accuracy of `mut_key` Fixes #6745. Whilst writing the tests for this, I noticed what I believe is a false negative (the code in `@xFrednet's` [comment](#6745 (comment)) doesn't trigger the lint). Currently the tests contain a case for this (which is blatantly ignored), but I'm not at all sure how to implement this (since the lint currently behaves completely differently for ADTs). I'm not sure what should be done - on the one hand the extra test cases are misleading, but on the other hand they don't cause much harm and would save effort for anyone fixing that false negative. --- changelog: Improve accuracy of `clippy::mutable_key_type`.
2 parents 7680c9b + b2ffb28 commit 746a005

File tree

3 files changed

+176
-27
lines changed

3 files changed

+176
-27
lines changed

clippy_lints/src/mut_key.rs

+61-20
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use clippy_utils::trait_ref_of_method;
33
use rustc_hir as hir;
44
use rustc_lint::{LateContext, LateLintPass};
55
use rustc_middle::ty::TypeFoldable;
6-
use rustc_middle::ty::{Adt, Array, RawPtr, Ref, Slice, Tuple, Ty, TypeAndMut};
6+
use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
77
use rustc_session::{declare_lint_pass, declare_tool_lint};
88
use rustc_span::source_map::Span;
99
use rustc_span::symbol::sym;
@@ -19,10 +19,29 @@ declare_clippy_lint! {
1919
/// so having types with interior mutability is a bad idea.
2020
///
2121
/// ### Known problems
22-
/// It's correct to use a struct, that contains interior mutability
23-
/// as a key, when its `Hash` implementation doesn't access any of the interior mutable types.
24-
/// However, this lint is unable to recognize this, so it causes a false positive in theses cases.
25-
/// The `bytes` crate is a great example of this.
22+
///
23+
/// #### False Positives
24+
/// It's correct to use a struct that contains interior mutability as a key, when its
25+
/// implementation of `Hash` or `Ord` doesn't access any of the interior mutable types.
26+
/// However, this lint is unable to recognize this, so it will often cause false positives in
27+
/// theses cases. The `bytes` crate is a great example of this.
28+
///
29+
/// #### False Negatives
30+
/// For custom `struct`s/`enum`s, this lint is unable to check for interior mutability behind
31+
/// indirection. For example, `struct BadKey<'a>(&'a Cell<usize>)` will be seen as immutable
32+
/// and cause a false negative if its implementation of `Hash`/`Ord` accesses the `Cell`.
33+
///
34+
/// This lint does check a few cases for indirection. Firstly, using some standard library
35+
/// types (`Option`, `Result`, `Box`, `Rc`, `Arc`, `Vec`, `VecDeque`, `BTreeMap` and
36+
/// `BTreeSet`) directly as keys (e.g. in `HashMap<Box<Cell<usize>>, ()>`) **will** trigger the
37+
/// lint, because the impls of `Hash`/`Ord` for these types directly call `Hash`/`Ord` on their
38+
/// contained type.
39+
///
40+
/// Secondly, the implementations of `Hash` and `Ord` for raw pointers (`*const T` or `*mut T`)
41+
/// apply only to the **address** of the contained value. Therefore, interior mutability
42+
/// behind raw pointers (e.g. in `HashSet<*mut Cell<usize>>`) can't impact the value of `Hash`
43+
/// or `Ord`, and therefore will not trigger this link. For more info, see issue
44+
/// [#6745](https://github.com/rust-lang/rust-clippy/issues/6745).
2645
///
2746
/// ### Example
2847
/// ```rust
@@ -103,30 +122,52 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::
103122
fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
104123
let ty = ty.peel_refs();
105124
if let Adt(def, substs) = ty.kind() {
106-
if [sym::hashmap_type, sym::BTreeMap, sym::hashset_type, sym::BTreeMap]
125+
let is_keyed_type = [sym::hashmap_type, sym::BTreeMap, sym::hashset_type, sym::BTreeSet]
107126
.iter()
108-
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did))
109-
&& is_mutable_type(cx, substs.type_at(0), span)
110-
{
127+
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
128+
if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
111129
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
112130
}
113131
}
114132
}
115133

116-
fn is_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
134+
/// Determines if a type contains interior mutability which would affect its implementation of
135+
/// [`Hash`] or [`Ord`].
136+
fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
117137
match *ty.kind() {
118-
RawPtr(TypeAndMut { ty: inner_ty, mutbl }) | Ref(_, inner_ty, mutbl) => {
119-
mutbl == hir::Mutability::Mut || is_mutable_type(cx, inner_ty, span)
120-
},
121-
Slice(inner_ty) => is_mutable_type(cx, inner_ty, span),
138+
Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || is_interior_mutable_type(cx, inner_ty, span),
139+
Slice(inner_ty) => is_interior_mutable_type(cx, inner_ty, span),
122140
Array(inner_ty, size) => {
123-
size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_mutable_type(cx, inner_ty, span)
141+
size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
142+
&& is_interior_mutable_type(cx, inner_ty, span)
124143
},
125-
Tuple(..) => ty.tuple_fields().any(|ty| is_mutable_type(cx, ty, span)),
126-
Adt(..) => {
127-
!ty.has_escaping_bound_vars()
128-
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
129-
&& !ty.is_freeze(cx.tcx.at(span), cx.param_env)
144+
Tuple(..) => ty.tuple_fields().any(|ty| is_interior_mutable_type(cx, ty, span)),
145+
Adt(def, substs) => {
146+
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
147+
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
148+
// because they have no impl for `Hash` or `Ord`.
149+
let is_std_collection = [
150+
sym::option_type,
151+
sym::result_type,
152+
sym::LinkedList,
153+
sym::vec_type,
154+
sym::vecdeque_type,
155+
sym::BTreeMap,
156+
sym::BTreeSet,
157+
sym::Rc,
158+
sym::Arc,
159+
]
160+
.iter()
161+
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
162+
let is_box = Some(def.did) == cx.tcx.lang_items().owned_box();
163+
if is_std_collection || is_box {
164+
// The type is mutable if any of its type parameters are
165+
substs.types().any(|ty| is_interior_mutable_type(cx, ty, span))
166+
} else {
167+
!ty.has_escaping_bound_vars()
168+
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
169+
&& !ty.is_freeze(cx.tcx.at(span), cx.param_env)
170+
}
130171
},
131172
_ => false,
132173
}

tests/ui/mut_key.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use std::collections::{HashMap, HashSet};
1+
use std::cell::Cell;
2+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
23
use std::hash::{Hash, Hasher};
4+
use std::rc::Rc;
35
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
6+
use std::sync::Arc;
47

58
struct Key(AtomicUsize);
69

@@ -31,11 +34,19 @@ fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<K
3134

3235
fn this_is_ok(_m: &mut HashMap<usize, Key>) {}
3336

37+
// Raw pointers are hashed by the address they point to, so it doesn't matter if they point to a
38+
// type with interior mutability. See:
39+
// - clippy issue: https://github.com/rust-lang/rust-clippy/issues/6745
40+
// - std lib: https://github.com/rust-lang/rust/blob/1.54.0/library/core/src/hash/mod.rs#L717-L736
41+
// So these are OK:
42+
fn raw_ptr_is_ok(_m: &mut HashMap<*const Key, ()>) {}
43+
fn raw_mut_ptr_is_ok(_m: &mut HashMap<*mut Key, ()>) {}
44+
3445
#[allow(unused)]
3546
trait Trait {
3647
type AssociatedType;
3748

38-
fn trait_fn(&self, set: std::collections::HashSet<Self::AssociatedType>);
49+
fn trait_fn(&self, set: HashSet<Self::AssociatedType>);
3950
}
4051

4152
fn generics_are_ok_too<K>(_m: &mut HashSet<K>) {
@@ -52,4 +63,23 @@ fn main() {
5263
tuples::<Key>(&mut HashMap::new());
5364
tuples::<()>(&mut HashMap::new());
5465
tuples_bad::<()>(&mut HashMap::new());
66+
67+
raw_ptr_is_ok(&mut HashMap::new());
68+
raw_mut_ptr_is_ok(&mut HashMap::new());
69+
70+
let _map = HashMap::<Cell<usize>, usize>::new();
71+
let _map = HashMap::<&mut Cell<usize>, usize>::new();
72+
let _map = HashMap::<&mut usize, usize>::new();
73+
// Collection types from `std` who's impl of `Hash` or `Ord` delegate their type parameters
74+
let _map = HashMap::<Vec<Cell<usize>>, usize>::new();
75+
let _map = HashMap::<BTreeMap<Cell<usize>, ()>, usize>::new();
76+
let _map = HashMap::<BTreeMap<(), Cell<usize>>, usize>::new();
77+
let _map = HashMap::<BTreeSet<Cell<usize>>, usize>::new();
78+
let _map = HashMap::<Option<Cell<usize>>, usize>::new();
79+
let _map = HashMap::<Option<Vec<Cell<usize>>>, usize>::new();
80+
let _map = HashMap::<Result<&mut usize, ()>, usize>::new();
81+
// Smart pointers from `std` who's impl of `Hash` or `Ord` delegate their type parameters
82+
let _map = HashMap::<Box<Cell<usize>>, usize>::new();
83+
let _map = HashMap::<Rc<Cell<usize>>, usize>::new();
84+
let _map = HashMap::<Arc<Cell<usize>>, usize>::new();
5585
}

tests/ui/mut_key.stderr

+83-5
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,106 @@
11
error: mutable key type
2-
--> $DIR/mut_key.rs:27:32
2+
--> $DIR/mut_key.rs:30:32
33
|
44
LL | fn should_not_take_this_arg(m: &mut HashMap<Key, usize>, _n: usize) -> HashSet<Key> {
55
| ^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::mutable-key-type` implied by `-D warnings`
88

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

1515
error: mutable key type
16-
--> $DIR/mut_key.rs:28:5
16+
--> $DIR/mut_key.rs:31:5
1717
|
1818
LL | let _other: HashMap<Key, bool> = HashMap::new();
1919
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2020

2121
error: mutable key type
22-
--> $DIR/mut_key.rs:47:22
22+
--> $DIR/mut_key.rs:58:22
2323
|
2424
LL | fn tuples_bad<U>(_m: &mut HashMap<(Key, U), bool>) {}
2525
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2626

27-
error: aborting due to 4 previous errors
27+
error: mutable key type
28+
--> $DIR/mut_key.rs:70:5
29+
|
30+
LL | let _map = HashMap::<Cell<usize>, usize>::new();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32+
33+
error: mutable key type
34+
--> $DIR/mut_key.rs:71:5
35+
|
36+
LL | let _map = HashMap::<&mut Cell<usize>, usize>::new();
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: mutable key type
40+
--> $DIR/mut_key.rs:72:5
41+
|
42+
LL | let _map = HashMap::<&mut usize, usize>::new();
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44+
45+
error: mutable key type
46+
--> $DIR/mut_key.rs:74:5
47+
|
48+
LL | let _map = HashMap::<Vec<Cell<usize>>, usize>::new();
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
50+
51+
error: mutable key type
52+
--> $DIR/mut_key.rs:75:5
53+
|
54+
LL | let _map = HashMap::<BTreeMap<Cell<usize>, ()>, usize>::new();
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56+
57+
error: mutable key type
58+
--> $DIR/mut_key.rs:76:5
59+
|
60+
LL | let _map = HashMap::<BTreeMap<(), Cell<usize>>, usize>::new();
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62+
63+
error: mutable key type
64+
--> $DIR/mut_key.rs:77:5
65+
|
66+
LL | let _map = HashMap::<BTreeSet<Cell<usize>>, usize>::new();
67+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68+
69+
error: mutable key type
70+
--> $DIR/mut_key.rs:78:5
71+
|
72+
LL | let _map = HashMap::<Option<Cell<usize>>, usize>::new();
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74+
75+
error: mutable key type
76+
--> $DIR/mut_key.rs:79:5
77+
|
78+
LL | let _map = HashMap::<Option<Vec<Cell<usize>>>, usize>::new();
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
81+
error: mutable key type
82+
--> $DIR/mut_key.rs:80:5
83+
|
84+
LL | let _map = HashMap::<Result<&mut usize, ()>, usize>::new();
85+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86+
87+
error: mutable key type
88+
--> $DIR/mut_key.rs:82:5
89+
|
90+
LL | let _map = HashMap::<Box<Cell<usize>>, usize>::new();
91+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
92+
93+
error: mutable key type
94+
--> $DIR/mut_key.rs:83:5
95+
|
96+
LL | let _map = HashMap::<Rc<Cell<usize>>, usize>::new();
97+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
98+
99+
error: mutable key type
100+
--> $DIR/mut_key.rs:84:5
101+
|
102+
LL | let _map = HashMap::<Arc<Cell<usize>>, usize>::new();
103+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104+
105+
error: aborting due to 17 previous errors
28106

0 commit comments

Comments
 (0)