Skip to content

Commit 0c42e45

Browse files
committed
Auto merge of #11791 - Jacherr:iter_over_hash_type, r=Jarcho
Implement new lint `iter_over_hash_type` Implements and fixes #11788 This PR adds a new *restriction* lint `iter_over_hash_type` which prevents `Hash`-types (that is, `HashSet` and `HashMap`) from being used as the iterator in `for` loops. The justification for this is because in `Hash`-based types, the ordering of items is not guaranteed and may vary between executions of the same program on the same hardware. In addition, it reduces readability due to the unclear iteration order. The implementation of this lint also ensures the following: - Calls to `HashMap::keys`, `HashMap::values`, and `HashSet::iter` are also denied when used in `for` loops, - When this expression is used in procedural macros, it is not linted/denied. changelog: add new `iter_over_hash_type` lint to prevent unordered iterations through hashed data structures
2 parents ca8f33e + f8ea496 commit 0c42e45

File tree

7 files changed

+273
-0
lines changed

7 files changed

+273
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5123,6 +5123,7 @@ Released 2018-09-13
51235123
[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
51245124
[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
51255125
[`iter_out_of_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_out_of_bounds
5126+
[`iter_over_hash_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_over_hash_type
51265127
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
51275128
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
51285129
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
231231
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
232232
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
233233
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
234+
crate::iter_over_hash_type::ITER_OVER_HASH_TYPE_INFO,
234235
crate::iter_without_into_iter::INTO_ITER_WITHOUT_ITER_INFO,
235236
crate::iter_without_into_iter::ITER_WITHOUT_INTO_ITER_INFO,
236237
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::higher::ForLoop;
3+
use clippy_utils::match_any_def_paths;
4+
use clippy_utils::paths::{
5+
HASHMAP_DRAIN, HASHMAP_ITER, HASHMAP_ITER_MUT, HASHMAP_KEYS, HASHMAP_VALUES, HASHMAP_VALUES_MUT, HASHSET_DRAIN,
6+
HASHSET_ITER_TY,
7+
};
8+
use clippy_utils::ty::is_type_diagnostic_item;
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_span::sym;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops.
16+
///
17+
/// ### Why is this bad?
18+
/// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in
19+
/// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies.
20+
/// In addition, the unknown order of the elements may reduce readability or introduce other undesired
21+
/// side effects.
22+
///
23+
/// ### Example
24+
/// ```no_run
25+
/// let my_map = std::collections::HashMap::<i32, String>::new();
26+
/// for (key, value) in my_map { /* ... */ }
27+
/// ```
28+
/// Use instead:
29+
/// ```no_run
30+
/// let my_map = std::collections::HashMap::<i32, String>::new();
31+
/// let mut keys = my_map.keys().clone().collect::<Vec<_>>();
32+
/// keys.sort();
33+
/// for key in keys {
34+
/// let value = &my_map[key];
35+
/// }
36+
/// ```
37+
#[clippy::version = "1.75.0"]
38+
pub ITER_OVER_HASH_TYPE,
39+
restriction,
40+
"iterating over unordered hash-based types (`HashMap` and `HashSet`)"
41+
}
42+
43+
declare_lint_pass!(IterOverHashType => [ITER_OVER_HASH_TYPE]);
44+
45+
impl LateLintPass<'_> for IterOverHashType {
46+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
47+
if let Some(for_loop) = ForLoop::hir(expr)
48+
&& !for_loop.body.span.from_expansion()
49+
&& let ty = cx.typeck_results().expr_ty(for_loop.arg).peel_refs()
50+
&& let Some(adt) = ty.ty_adt_def()
51+
&& let did = adt.did()
52+
&& (match_any_def_paths(
53+
cx,
54+
did,
55+
&[
56+
&HASHMAP_KEYS,
57+
&HASHMAP_VALUES,
58+
&HASHMAP_VALUES_MUT,
59+
&HASHMAP_ITER,
60+
&HASHMAP_ITER_MUT,
61+
&HASHMAP_DRAIN,
62+
&HASHSET_ITER_TY,
63+
&HASHSET_DRAIN,
64+
],
65+
)
66+
.is_some()
67+
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
68+
|| is_type_diagnostic_item(cx, ty, sym::HashSet))
69+
{
70+
span_lint(
71+
cx,
72+
ITER_OVER_HASH_TYPE,
73+
expr.span,
74+
"iteration over unordered hash-based type",
75+
);
76+
};
77+
}
78+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ mod item_name_repetitions;
164164
mod items_after_statements;
165165
mod items_after_test_module;
166166
mod iter_not_returning_iterator;
167+
mod iter_over_hash_type;
167168
mod iter_without_into_iter;
168169
mod large_const_arrays;
169170
mod large_enum_variant;
@@ -1064,6 +1065,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10641065
});
10651066
store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(msrv())));
10661067
store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter));
1068+
store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType));
10671069
// add lints here, do not remove this comment, it's used in `new_lint`
10681070
}
10691071

clippy_utils/src/paths.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncRead
3232
pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"];
3333
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
3434
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
35+
pub const HASHMAP_ITER: [&str; 5] = ["std", "collections", "hash", "map", "Iter"];
36+
pub const HASHMAP_ITER_MUT: [&str; 5] = ["std", "collections", "hash", "map", "IterMut"];
37+
pub const HASHMAP_KEYS: [&str; 5] = ["std", "collections", "hash", "map", "Keys"];
38+
pub const HASHMAP_VALUES: [&str; 5] = ["std", "collections", "hash", "map", "Values"];
39+
pub const HASHMAP_DRAIN: [&str; 5] = ["std", "collections", "hash", "map", "Drain"];
40+
pub const HASHMAP_VALUES_MUT: [&str; 5] = ["std", "collections", "hash", "map", "ValuesMut"];
41+
pub const HASHSET_ITER_TY: [&str; 5] = ["std", "collections", "hash", "set", "Iter"];
3542
pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"];
43+
pub const HASHSET_DRAIN: [&str; 5] = ["std", "collections", "hash", "set", "Drain"];
3644
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
3745
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
3846
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];

tests/ui/iter_over_hash_type.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//@aux-build:proc_macros.rs
2+
#![feature(rustc_private)]
3+
#![warn(clippy::iter_over_hash_type)]
4+
use std::collections::{HashMap, HashSet};
5+
6+
extern crate rustc_data_structures;
7+
8+
extern crate proc_macros;
9+
10+
fn main() {
11+
let mut hash_set = HashSet::<i32>::new();
12+
let mut hash_map = HashMap::<i32, i32>::new();
13+
let mut fx_hash_map = rustc_data_structures::fx::FxHashMap::<i32, i32>::default();
14+
let mut fx_hash_set = rustc_data_structures::fx::FxHashMap::<i32, i32>::default();
15+
let vec = Vec::<i32>::new();
16+
17+
// test hashset
18+
for x in &hash_set {
19+
let _ = x;
20+
}
21+
for x in hash_set.iter() {
22+
let _ = x;
23+
}
24+
for x in hash_set.clone() {
25+
let _ = x;
26+
}
27+
for x in hash_set.drain() {
28+
let _ = x;
29+
}
30+
31+
// test hashmap
32+
for (x, y) in &hash_map {
33+
let _ = (x, y);
34+
}
35+
for x in hash_map.keys() {
36+
let _ = x;
37+
}
38+
for x in hash_map.values() {
39+
let _ = x;
40+
}
41+
for x in hash_map.values_mut() {
42+
*x += 1;
43+
}
44+
for x in hash_map.iter() {
45+
let _ = x;
46+
}
47+
for x in hash_map.clone() {
48+
let _ = x;
49+
}
50+
for x in hash_map.drain() {
51+
let _ = x;
52+
}
53+
54+
// test type-aliased hashers
55+
for x in fx_hash_set {
56+
let _ = x;
57+
}
58+
for x in fx_hash_map {
59+
let _ = x;
60+
}
61+
62+
// shouldnt fire
63+
for x in &vec {
64+
let _ = x;
65+
}
66+
for x in vec {
67+
let _ = x;
68+
}
69+
70+
// should not lint, this comes from an external crate
71+
proc_macros::external! {
72+
for _ in HashMap::<i32, i32>::new() {}
73+
}
74+
}

tests/ui/iter_over_hash_type.stderr

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
error: iteration over unordered hash-based type
2+
--> $DIR/iter_over_hash_type.rs:18:5
3+
|
4+
LL | / for x in &hash_set {
5+
LL | | let _ = x;
6+
LL | | }
7+
| |_____^
8+
|
9+
= note: `-D clippy::iter-over-hash-type` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::iter_over_hash_type)]`
11+
12+
error: iteration over unordered hash-based type
13+
--> $DIR/iter_over_hash_type.rs:21:5
14+
|
15+
LL | / for x in hash_set.iter() {
16+
LL | | let _ = x;
17+
LL | | }
18+
| |_____^
19+
20+
error: iteration over unordered hash-based type
21+
--> $DIR/iter_over_hash_type.rs:24:5
22+
|
23+
LL | / for x in hash_set.clone() {
24+
LL | | let _ = x;
25+
LL | | }
26+
| |_____^
27+
28+
error: iteration over unordered hash-based type
29+
--> $DIR/iter_over_hash_type.rs:27:5
30+
|
31+
LL | / for x in hash_set.drain() {
32+
LL | | let _ = x;
33+
LL | | }
34+
| |_____^
35+
36+
error: iteration over unordered hash-based type
37+
--> $DIR/iter_over_hash_type.rs:32:5
38+
|
39+
LL | / for (x, y) in &hash_map {
40+
LL | | let _ = (x, y);
41+
LL | | }
42+
| |_____^
43+
44+
error: iteration over unordered hash-based type
45+
--> $DIR/iter_over_hash_type.rs:35:5
46+
|
47+
LL | / for x in hash_map.keys() {
48+
LL | | let _ = x;
49+
LL | | }
50+
| |_____^
51+
52+
error: iteration over unordered hash-based type
53+
--> $DIR/iter_over_hash_type.rs:38:5
54+
|
55+
LL | / for x in hash_map.values() {
56+
LL | | let _ = x;
57+
LL | | }
58+
| |_____^
59+
60+
error: iteration over unordered hash-based type
61+
--> $DIR/iter_over_hash_type.rs:41:5
62+
|
63+
LL | / for x in hash_map.values_mut() {
64+
LL | | *x += 1;
65+
LL | | }
66+
| |_____^
67+
68+
error: iteration over unordered hash-based type
69+
--> $DIR/iter_over_hash_type.rs:44:5
70+
|
71+
LL | / for x in hash_map.iter() {
72+
LL | | let _ = x;
73+
LL | | }
74+
| |_____^
75+
76+
error: iteration over unordered hash-based type
77+
--> $DIR/iter_over_hash_type.rs:47:5
78+
|
79+
LL | / for x in hash_map.clone() {
80+
LL | | let _ = x;
81+
LL | | }
82+
| |_____^
83+
84+
error: iteration over unordered hash-based type
85+
--> $DIR/iter_over_hash_type.rs:50:5
86+
|
87+
LL | / for x in hash_map.drain() {
88+
LL | | let _ = x;
89+
LL | | }
90+
| |_____^
91+
92+
error: iteration over unordered hash-based type
93+
--> $DIR/iter_over_hash_type.rs:55:5
94+
|
95+
LL | / for x in fx_hash_set {
96+
LL | | let _ = x;
97+
LL | | }
98+
| |_____^
99+
100+
error: iteration over unordered hash-based type
101+
--> $DIR/iter_over_hash_type.rs:58:5
102+
|
103+
LL | / for x in fx_hash_map {
104+
LL | | let _ = x;
105+
LL | | }
106+
| |_____^
107+
108+
error: aborting due to 13 previous errors
109+

0 commit comments

Comments
 (0)