-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
7 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
use clippy_utils::diagnostics::span_lint; | ||
use clippy_utils::higher::ForLoop; | ||
use clippy_utils::match_any_def_paths; | ||
use clippy_utils::paths::{ | ||
HASHMAP_DRAIN, HASHMAP_ITER, HASHMAP_ITER_MUT, HASHMAP_KEYS, HASHMAP_VALUES, HASHMAP_VALUES_MUT, HASHSET_DRAIN, | ||
HASHSET_ITER_TY, | ||
}; | ||
use clippy_utils::ty::is_type_diagnostic_item; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::sym; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops. | ||
/// | ||
/// ### Why is this bad? | ||
/// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in | ||
/// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies. | ||
/// In addition, the unknown order of the elements may reduce readability or introduce other undesired | ||
/// side effects. | ||
/// | ||
/// ### Example | ||
/// ```no_run | ||
/// let my_map = std::collections::HashMap::<i32, String>::new(); | ||
/// for (key, value) in my_map { /* ... */ } | ||
/// ``` | ||
/// Use instead: | ||
/// ```no_run | ||
/// let my_map = std::collections::HashMap::<i32, String>::new(); | ||
/// let mut keys = my_map.keys().clone().collect::<Vec<_>>(); | ||
/// keys.sort(); | ||
/// for key in keys { | ||
/// let value = &my_map[key]; | ||
/// } | ||
/// ``` | ||
#[clippy::version = "1.75.0"] | ||
pub ITER_OVER_HASH_TYPE, | ||
restriction, | ||
"iterating over unordered hash-based types (`HashMap` and `HashSet`)" | ||
} | ||
|
||
declare_lint_pass!(IterOverHashType => [ITER_OVER_HASH_TYPE]); | ||
|
||
impl LateLintPass<'_> for IterOverHashType { | ||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) { | ||
if let Some(for_loop) = ForLoop::hir(expr) | ||
&& !for_loop.body.span.from_expansion() | ||
&& let ty = cx.typeck_results().expr_ty(for_loop.arg).peel_refs() | ||
&& let Some(adt) = ty.ty_adt_def() | ||
&& let did = adt.did() | ||
&& (match_any_def_paths( | ||
cx, | ||
did, | ||
&[ | ||
&HASHMAP_KEYS, | ||
&HASHMAP_VALUES, | ||
&HASHMAP_VALUES_MUT, | ||
&HASHMAP_ITER, | ||
&HASHMAP_ITER_MUT, | ||
&HASHMAP_DRAIN, | ||
&HASHSET_ITER_TY, | ||
&HASHSET_DRAIN, | ||
], | ||
) | ||
.is_some() | ||
|| is_type_diagnostic_item(cx, ty, sym::HashMap) | ||
|| is_type_diagnostic_item(cx, ty, sym::HashSet)) | ||
{ | ||
span_lint( | ||
cx, | ||
ITER_OVER_HASH_TYPE, | ||
expr.span, | ||
"iteration over unordered hash-based type", | ||
); | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
//@aux-build:proc_macros.rs | ||
#![feature(rustc_private)] | ||
#![warn(clippy::iter_over_hash_type)] | ||
use std::collections::{HashMap, HashSet}; | ||
|
||
extern crate rustc_data_structures; | ||
|
||
extern crate proc_macros; | ||
|
||
fn main() { | ||
let mut hash_set = HashSet::<i32>::new(); | ||
let mut hash_map = HashMap::<i32, i32>::new(); | ||
let mut fx_hash_map = rustc_data_structures::fx::FxHashMap::<i32, i32>::default(); | ||
let mut fx_hash_set = rustc_data_structures::fx::FxHashMap::<i32, i32>::default(); | ||
let vec = Vec::<i32>::new(); | ||
|
||
// test hashset | ||
for x in &hash_set { | ||
let _ = x; | ||
} | ||
for x in hash_set.iter() { | ||
let _ = x; | ||
} | ||
for x in hash_set.clone() { | ||
let _ = x; | ||
} | ||
for x in hash_set.drain() { | ||
let _ = x; | ||
} | ||
|
||
// test hashmap | ||
for (x, y) in &hash_map { | ||
let _ = (x, y); | ||
} | ||
for x in hash_map.keys() { | ||
let _ = x; | ||
} | ||
for x in hash_map.values() { | ||
let _ = x; | ||
} | ||
for x in hash_map.values_mut() { | ||
*x += 1; | ||
} | ||
for x in hash_map.iter() { | ||
let _ = x; | ||
} | ||
for x in hash_map.clone() { | ||
let _ = x; | ||
} | ||
for x in hash_map.drain() { | ||
let _ = x; | ||
} | ||
|
||
// test type-aliased hashers | ||
for x in fx_hash_set { | ||
let _ = x; | ||
} | ||
for x in fx_hash_map { | ||
let _ = x; | ||
} | ||
|
||
// shouldnt fire | ||
for x in &vec { | ||
let _ = x; | ||
} | ||
for x in vec { | ||
let _ = x; | ||
} | ||
|
||
// should not lint, this comes from an external crate | ||
proc_macros::external! { | ||
for _ in HashMap::<i32, i32>::new() {} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:18:5 | ||
| | ||
LL | / for x in &hash_set { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
| | ||
= note: `-D clippy::iter-over-hash-type` implied by `-D warnings` | ||
= help: to override `-D warnings` add `#[allow(clippy::iter_over_hash_type)]` | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:21:5 | ||
| | ||
LL | / for x in hash_set.iter() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:24:5 | ||
| | ||
LL | / for x in hash_set.clone() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:27:5 | ||
| | ||
LL | / for x in hash_set.drain() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:32:5 | ||
| | ||
LL | / for (x, y) in &hash_map { | ||
LL | | let _ = (x, y); | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:35:5 | ||
| | ||
LL | / for x in hash_map.keys() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:38:5 | ||
| | ||
LL | / for x in hash_map.values() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:41:5 | ||
| | ||
LL | / for x in hash_map.values_mut() { | ||
LL | | *x += 1; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:44:5 | ||
| | ||
LL | / for x in hash_map.iter() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:47:5 | ||
| | ||
LL | / for x in hash_map.clone() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:50:5 | ||
| | ||
LL | / for x in hash_map.drain() { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:55:5 | ||
| | ||
LL | / for x in fx_hash_set { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: iteration over unordered hash-based type | ||
--> $DIR/iter_over_hash_type.rs:58:5 | ||
| | ||
LL | / for x in fx_hash_map { | ||
LL | | let _ = x; | ||
LL | | } | ||
| |_____^ | ||
|
||
error: aborting due to 13 previous errors | ||
|