summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/loops/mod.rs
blob: 610a0233eee15b4564002c5420bb466cc2873c5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
mod empty_loop;
mod explicit_counter_loop;
mod explicit_into_iter_loop;
mod explicit_iter_loop;
mod for_kv_map;
mod iter_next_loop;
mod manual_find;
mod manual_flatten;
mod manual_memcpy;
mod missing_spin_loop;
mod mut_range_bound;
mod needless_range_loop;
mod never_loop;
mod same_item_push;
mod single_element_loop;
mod utils;
mod while_immutable_condition;
mod while_let_loop;
mod while_let_on_iterator;

use clippy_utils::higher;
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};

declare_clippy_lint! {
    /// ### What it does
    /// Checks for for-loops that manually copy items between
    /// slices that could be optimized by having a memcpy.
    ///
    /// ### Why is this bad?
    /// It is not as fast as a memcpy.
    ///
    /// ### Example
    /// ```rust
    /// # let src = vec![1];
    /// # let mut dst = vec![0; 65];
    /// for i in 0..src.len() {
    ///     dst[i + 64] = src[i];
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// # let src = vec![1];
    /// # let mut dst = vec![0; 65];
    /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]);
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub MANUAL_MEMCPY,
    perf,
    "manually copying items between slices"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for looping over the range of `0..len` of some
    /// collection just to get the values by index.
    ///
    /// ### Why is this bad?
    /// Just iterating the collection itself makes the intent
    /// more clear and is probably faster because it eliminates
    /// the bounds check that is done when indexing.
    ///
    /// ### Example
    /// ```rust
    /// let vec = vec!['a', 'b', 'c'];
    /// for i in 0..vec.len() {
    ///     println!("{}", vec[i]);
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// let vec = vec!['a', 'b', 'c'];
    /// for i in vec {
    ///     println!("{}", i);
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub NEEDLESS_RANGE_LOOP,
    style,
    "for-looping over a range of indices where an iterator over items would do"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for loops on `x.iter()` where `&x` will do, and
    /// suggests the latter.
    ///
    /// ### Why is this bad?
    /// Readability.
    ///
    /// ### Known problems
    /// False negatives. We currently only warn on some known
    /// types.
    ///
    /// ### Example
    /// ```rust
    /// // with `y` a `Vec` or slice:
    /// # let y = vec![1];
    /// for x in y.iter() {
    ///     // ..
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// # let y = vec![1];
    /// for x in &y {
    ///     // ..
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub EXPLICIT_ITER_LOOP,
    pedantic,
    "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for loops on `y.into_iter()` where `y` will do, and
    /// suggests the latter.
    ///
    /// ### Why is this bad?
    /// Readability.
    ///
    /// ### Example
    /// ```rust
    /// # let y = vec![1];
    /// // with `y` a `Vec` or slice:
    /// for x in y.into_iter() {
    ///     // ..
    /// }
    /// ```
    /// can be rewritten to
    /// ```rust
    /// # let y = vec![1];
    /// for x in y {
    ///     // ..
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub EXPLICIT_INTO_ITER_LOOP,
    pedantic,
    "for-looping over `_.into_iter()` when `_` would do"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for loops on `x.next()`.
    ///
    /// ### Why is this bad?
    /// `next()` returns either `Some(value)` if there was a
    /// value, or `None` otherwise. The insidious thing is that `Option<_>`
    /// implements `IntoIterator`, so that possibly one value will be iterated,
    /// leading to some hard to find bugs. No one will want to write such code
    /// [except to win an Underhanded Rust
    /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr).
    ///
    /// ### Example
    /// ```ignore
    /// for x in y.next() {
    ///     ..
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub ITER_NEXT_LOOP,
    correctness,
    "for-looping over `_.next()` which is probably not intended"
}

declare_clippy_lint! {
    /// ### What it does
    /// Detects `loop + match` combinations that are easier
    /// written as a `while let` loop.
    ///
    /// ### Why is this bad?
    /// The `while let` loop is usually shorter and more
    /// readable.
    ///
    /// ### Known problems
    /// Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)).
    ///
    /// ### Example
    /// ```rust,no_run
    /// # let y = Some(1);
    /// loop {
    ///     let x = match y {
    ///         Some(x) => x,
    ///         None => break,
    ///     };
    ///     // .. do something with x
    /// }
    /// // is easier written as
    /// while let Some(x) = y {
    ///     // .. do something with x
    /// };
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub WHILE_LET_LOOP,
    complexity,
    "`loop { if let { ... } else break }`, which can be written as a `while let` loop"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks `for` loops over slices with an explicit counter
    /// and suggests the use of `.enumerate()`.
    ///
    /// ### Why is this bad?
    /// Using `.enumerate()` makes the intent more clear,
    /// declutters the code and may be faster in some instances.
    ///
    /// ### Example
    /// ```rust
    /// # let v = vec![1];
    /// # fn bar(bar: usize, baz: usize) {}
    /// let mut i = 0;
    /// for item in &v {
    ///     bar(i, *item);
    ///     i += 1;
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// # let v = vec![1];
    /// # fn bar(bar: usize, baz: usize) {}
    /// for (i, item) in v.iter().enumerate() { bar(i, *item); }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub EXPLICIT_COUNTER_LOOP,
    complexity,
    "for-looping with an explicit counter when `_.enumerate()` would do"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for empty `loop` expressions.
    ///
    /// ### Why is this bad?
    /// These busy loops burn CPU cycles without doing
    /// anything. It is _almost always_ a better idea to `panic!` than to have
    /// a busy loop.
    ///
    /// If panicking isn't possible, think of the environment and either:
    ///   - block on something
    ///   - sleep the thread for some microseconds
    ///   - yield or pause the thread
    ///
    /// For `std` targets, this can be done with
    /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html)
    /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html).
    ///
    /// For `no_std` targets, doing this is more complicated, especially because
    /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will
    /// probably need to invoke some target-specific intrinsic. Examples include:
    ///   - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html)
    ///   - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html)
    ///
    /// ### Example
    /// ```no_run
    /// loop {}
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub EMPTY_LOOP,
    suspicious,
    "empty `loop {}`, which should block or sleep"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for `while let` expressions on iterators.
    ///
    /// ### Why is this bad?
    /// Readability. A simple `for` loop is shorter and conveys
    /// the intent better.
    ///
    /// ### Example
    /// ```ignore
    /// while let Some(val) = iter.next() {
    ///     ..
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```ignore
    /// for val in &mut iter {
    ///     ..
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub WHILE_LET_ON_ITERATOR,
    style,
    "using a `while let` loop instead of a for loop on an iterator"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for iterating a map (`HashMap` or `BTreeMap`) and
    /// ignoring either the keys or values.
    ///
    /// ### Why is this bad?
    /// Readability. There are `keys` and `values` methods that
    /// can be used to express that don't need the values or keys.
    ///
    /// ### Example
    /// ```ignore
    /// for (k, _) in &map {
    ///     ..
    /// }
    /// ```
    ///
    /// could be replaced by
    ///
    /// ```ignore
    /// for k in map.keys() {
    ///     ..
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub FOR_KV_MAP,
    style,
    "looping on a map using `iter` when `keys` or `values` would do"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for loops that will always `break`, `return` or
    /// `continue` an outer loop.
    ///
    /// ### Why is this bad?
    /// This loop never loops, all it does is obfuscating the
    /// code.
    ///
    /// ### Example
    /// ```rust
    /// loop {
    ///     ..;
    ///     break;
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub NEVER_LOOP,
    correctness,
    "any loop that will always `break` or `return`"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for loops which have a range bound that is a mutable variable
    ///
    /// ### Why is this bad?
    /// One might think that modifying the mutable variable changes the loop bounds
    ///
    /// ### Known problems
    /// False positive when mutation is followed by a `break`, but the `break` is not immediately
    /// after the mutation:
    ///
    /// ```rust
    /// let mut x = 5;
    /// for _ in 0..x {
    ///     x += 1; // x is a range bound that is mutated
    ///     ..; // some other expression
    ///     break; // leaves the loop, so mutation is not an issue
    /// }
    /// ```
    ///
    /// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
    ///
    /// ### Example
    /// ```rust
    /// let mut foo = 42;
    /// for i in 0..foo {
    ///     foo -= 1;
    ///     println!("{}", i); // prints numbers from 0 to 42, not 0 to 21
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub MUT_RANGE_BOUND,
    suspicious,
    "for loop over a range where one of the bounds is a mutable variable"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks whether variables used within while loop condition
    /// can be (and are) mutated in the body.
    ///
    /// ### Why is this bad?
    /// If the condition is unchanged, entering the body of the loop
    /// will lead to an infinite loop.
    ///
    /// ### Known problems
    /// If the `while`-loop is in a closure, the check for mutation of the
    /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is
    /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger.
    ///
    /// ### Example
    /// ```rust
    /// let i = 0;
    /// while i > 10 {
    ///     println!("let me loop forever!");
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub WHILE_IMMUTABLE_CONDITION,
    correctness,
    "variables used within while expression are not mutated in the body"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks whether a for loop is being used to push a constant
    /// value into a Vec.
    ///
    /// ### Why is this bad?
    /// This kind of operation can be expressed more succinctly with
    /// `vec![item; SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
    /// have better performance.
    ///
    /// ### Example
    /// ```rust
    /// let item1 = 2;
    /// let item2 = 3;
    /// let mut vec: Vec<u8> = Vec::new();
    /// for _ in 0..20 {
    ///    vec.push(item1);
    /// }
    /// for _ in 0..30 {
    ///     vec.push(item2);
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// let item1 = 2;
    /// let item2 = 3;
    /// let mut vec: Vec<u8> = vec![item1; 20];
    /// vec.resize(20 + 30, item2);
    /// ```
    #[clippy::version = "1.47.0"]
    pub SAME_ITEM_PUSH,
    style,
    "the same item is pushed inside of a for loop"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks whether a for loop has a single element.
    ///
    /// ### Why is this bad?
    /// There is no reason to have a loop of a
    /// single element.
    ///
    /// ### Example
    /// ```rust
    /// let item1 = 2;
    /// for item in &[item1] {
    ///     println!("{}", item);
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// let item1 = 2;
    /// let item = &item1;
    /// println!("{}", item);
    /// ```
    #[clippy::version = "1.49.0"]
    pub SINGLE_ELEMENT_LOOP,
    complexity,
    "there is no reason to have a single element loop"
}

declare_clippy_lint! {
    /// ### What it does
    /// Check for unnecessary `if let` usage in a for loop
    /// where only the `Some` or `Ok` variant of the iterator element is used.
    ///
    /// ### Why is this bad?
    /// It is verbose and can be simplified
    /// by first calling the `flatten` method on the `Iterator`.
    ///
    /// ### Example
    ///
    /// ```rust
    /// let x = vec![Some(1), Some(2), Some(3)];
    /// for n in x {
    ///     if let Some(n) = n {
    ///         println!("{}", n);
    ///     }
    /// }
    /// ```
    /// Use instead:
    /// ```rust
    /// let x = vec![Some(1), Some(2), Some(3)];
    /// for n in x.into_iter().flatten() {
    ///     println!("{}", n);
    /// }
    /// ```
    #[clippy::version = "1.52.0"]
    pub MANUAL_FLATTEN,
    complexity,
    "for loops over `Option`s or `Result`s with a single expression can be simplified"
}

declare_clippy_lint! {
    /// ### What it does
    /// Check for empty spin loops
    ///
    /// ### Why is this bad?
    /// The loop body should have something like `thread::park()` or at least
    /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve
    /// energy. Perhaps even better use an actual lock, if possible.
    ///
    /// ### Known problems
    /// This lint doesn't currently trigger on `while let` or
    /// `loop { match .. { .. } }` loops, which would be considered idiomatic in
    /// combination with e.g. `AtomicBool::compare_exchange_weak`.
    ///
    /// ### Example
    ///
    /// ```ignore
    /// use core::sync::atomic::{AtomicBool, Ordering};
    /// let b = AtomicBool::new(true);
    /// // give a ref to `b` to another thread,wait for it to become false
    /// while b.load(Ordering::Acquire) {};
    /// ```
    /// Use instead:
    /// ```rust,no_run
    ///# use core::sync::atomic::{AtomicBool, Ordering};
    ///# let b = AtomicBool::new(true);
    /// while b.load(Ordering::Acquire) {
    ///     std::hint::spin_loop()
    /// }
    /// ```
    #[clippy::version = "1.61.0"]
    pub MISSING_SPIN_LOOP,
    perf,
    "An empty busy waiting loop"
}

declare_clippy_lint! {
    /// ### What it does
    /// Check for manual implementations of Iterator::find
    ///
    /// ### Why is this bad?
    /// It doesn't affect performance, but using `find` is shorter and easier to read.
    ///
    /// ### Example
    ///
    /// ```rust
    /// fn example(arr: Vec<i32>) -> Option<i32> {
    ///     for el in arr {
    ///         if el == 1 {
    ///             return Some(el);
    ///         }
    ///     }
    ///     None
    /// }
    /// ```
    /// Use instead:
    /// ```rust
    /// fn example(arr: Vec<i32>) -> Option<i32> {
    ///     arr.into_iter().find(|&el| el == 1)
    /// }
    /// ```
    #[clippy::version = "1.64.0"]
    pub MANUAL_FIND,
    complexity,
    "manual implementation of `Iterator::find`"
}

declare_lint_pass!(Loops => [
    MANUAL_MEMCPY,
    MANUAL_FLATTEN,
    NEEDLESS_RANGE_LOOP,
    EXPLICIT_ITER_LOOP,
    EXPLICIT_INTO_ITER_LOOP,
    ITER_NEXT_LOOP,
    WHILE_LET_LOOP,
    EXPLICIT_COUNTER_LOOP,
    EMPTY_LOOP,
    WHILE_LET_ON_ITERATOR,
    FOR_KV_MAP,
    NEVER_LOOP,
    MUT_RANGE_BOUND,
    WHILE_IMMUTABLE_CONDITION,
    SAME_ITEM_PUSH,
    SINGLE_ELEMENT_LOOP,
    MISSING_SPIN_LOOP,
    MANUAL_FIND,
]);

impl<'tcx> LateLintPass<'tcx> for Loops {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
        let for_loop = higher::ForLoop::hir(expr);
        if let Some(higher::ForLoop {
            pat,
            arg,
            body,
            loop_id,
            span,
        }) = for_loop
        {
            // we don't want to check expanded macros
            // this check is not at the top of the function
            // since higher::for_loop expressions are marked as expansions
            if body.span.from_expansion() {
                return;
            }
            check_for_loop(cx, pat, arg, body, expr, span);
            if let ExprKind::Block(block, _) = body.kind {
                never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
            }
        }

        // we don't want to check expanded macros
        if expr.span.from_expansion() {
            return;
        }

        // check for never_loop
        if let ExprKind::Loop(block, ..) = expr.kind {
            never_loop::check(cx, block, expr.hir_id, expr.span, None);
        }

        // check for `loop { if let {} else break }` that could be `while let`
        // (also matches an explicit "match" instead of "if let")
        // (even if the "match" or "if let" is used for declaration)
        if let ExprKind::Loop(block, _, LoopSource::Loop, _) = expr.kind {
            // also check for empty `loop {}` statements, skipping those in #[panic_handler]
            empty_loop::check(cx, expr, block);
            while_let_loop::check(cx, expr, block);
        }

        while_let_on_iterator::check(cx, expr);

        if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
            while_immutable_condition::check(cx, condition, body);
            missing_spin_loop::check(cx, condition, body);
        }
    }
}

fn check_for_loop<'tcx>(
    cx: &LateContext<'tcx>,
    pat: &'tcx Pat<'_>,
    arg: &'tcx Expr<'_>,
    body: &'tcx Expr<'_>,
    expr: &'tcx Expr<'_>,
    span: Span,
) {
    let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
    if !is_manual_memcpy_triggered {
        needless_range_loop::check(cx, pat, arg, body, expr);
        explicit_counter_loop::check(cx, pat, arg, body, expr);
    }
    check_for_loop_arg(cx, pat, arg);
    for_kv_map::check(cx, pat, arg, body);
    mut_range_bound::check(cx, arg, body);
    single_element_loop::check(cx, pat, arg, body, expr);
    same_item_push::check(cx, pat, arg, body, expr);
    manual_flatten::check(cx, pat, arg, body, span);
    manual_find::check(cx, pat, arg, body, span, expr);
}

fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
    if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
        let method_name = method.ident.as_str();
        // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
        match method_name {
            "iter" | "iter_mut" => {
                explicit_iter_loop::check(cx, self_arg, arg, method_name);
            },
            "into_iter" => {
                explicit_iter_loop::check(cx, self_arg, arg, method_name);
                explicit_into_iter_loop::check(cx, self_arg, arg);
            },
            "next" => {
                iter_next_loop::check(cx, arg);
            },
            _ => {},
        }
    }
}