Skip to content

Commit 387efd4

Browse files
committed
Merge branch 'pr-1945'
2 parents b1302a0 + d337c7f commit 387efd4

File tree

6 files changed

+208
-1
lines changed

6 files changed

+208
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
## Trunk
5+
* New lint: [`mut_range_bound`]
6+
47
## 0.0.164
58
* Update to *rustc 1.22.0-nightly (6c476ce46 2017-09-25)*
69
* New lint: [`int_plus_one`]
@@ -561,6 +564,7 @@ All notable changes to this project will be documented in this file.
561564
[`modulo_one`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#modulo_one
562565
[`mut_from_ref`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mut_from_ref
563566
[`mut_mut`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mut_mut
567+
[`mut_range_bound`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mut_range_bound
564568
[`mutex_atomic`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mutex_atomic
565569
[`mutex_integer`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#mutex_integer
566570
[`naive_bytecount`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#naive_bytecount

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
449449
loops::FOR_LOOP_OVER_RESULT,
450450
loops::ITER_NEXT_LOOP,
451451
loops::MANUAL_MEMCPY,
452+
loops::MUT_RANGE_BOUND,
452453
loops::NEEDLESS_RANGE_LOOP,
453454
loops::NEVER_LOOP,
454455
loops::REVERSE_RANGE_LOOP,

clippy_lints/src/loops.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@ use itertools::Itertools;
22
use reexport::*;
33
use rustc::hir::*;
44
use rustc::hir::def::Def;
5+
use rustc::hir::def_id;
56
use rustc::hir::intravisit::{walk_block, walk_decl, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
67
use rustc::hir::map::Node::{NodeBlock, NodeExpr, NodeStmt};
78
use rustc::lint::*;
89
use rustc::middle::const_val::ConstVal;
910
use rustc::middle::region;
11+
// use rustc::middle::region::CodeExtent;
12+
use rustc::middle::expr_use_visitor::*;
13+
use rustc::middle::mem_categorization::Categorization;
14+
use rustc::middle::mem_categorization::cmt;
1015
use rustc::ty::{self, Ty};
1116
use rustc::ty::subst::{Subst, Substs};
1217
use rustc_const_eval::ConstContext;
1318
use std::collections::{HashMap, HashSet};
1419
use syntax::ast;
20+
use syntax::codemap::Span;
1521
use utils::sugg;
1622
use utils::const_to_u64;
1723

@@ -328,6 +334,14 @@ declare_lint! {
328334
"any loop that will always `break` or `return`"
329335
}
330336

337+
/// TODO: add documentation
338+
339+
declare_lint! {
340+
pub MUT_RANGE_BOUND,
341+
Warn,
342+
"for loop over a range where one of the bounds is a mutable variable"
343+
}
344+
331345
#[derive(Copy, Clone)]
332346
pub struct Pass;
333347

@@ -348,7 +362,8 @@ impl LintPass for Pass {
348362
EMPTY_LOOP,
349363
WHILE_LET_ON_ITERATOR,
350364
FOR_KV_MAP,
351-
NEVER_LOOP
365+
NEVER_LOOP,
366+
MUT_RANGE_BOUND
352367
)
353368
}
354369
}
@@ -605,6 +620,7 @@ fn check_for_loop<'a, 'tcx>(
605620
check_for_loop_arg(cx, pat, arg, expr);
606621
check_for_loop_explicit_counter(cx, arg, body, expr);
607622
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
623+
check_for_mut_range_bound(cx, arg, body);
608624
detect_manual_memcpy(cx, pat, arg, body, expr);
609625
}
610626

@@ -1294,6 +1310,102 @@ fn check_for_loop_over_map_kv<'a, 'tcx>(
12941310
}
12951311
}
12961312

1313+
struct MutateDelegate {
1314+
node_id_low: Option<NodeId>,
1315+
node_id_high: Option<NodeId>,
1316+
span_low: Option<Span>,
1317+
span_high: Option<Span>,
1318+
}
1319+
1320+
impl<'tcx> Delegate<'tcx> for MutateDelegate {
1321+
fn consume(&mut self, _: NodeId, _: Span, _: cmt<'tcx>, _: ConsumeMode) {
1322+
}
1323+
1324+
fn matched_pat(&mut self, _: &Pat, _: cmt<'tcx>, _: MatchMode) {
1325+
}
1326+
1327+
fn consume_pat(&mut self, _: &Pat, _: cmt<'tcx>, _: ConsumeMode) {
1328+
}
1329+
1330+
fn borrow(&mut self, _: NodeId, sp: Span, cmt: cmt<'tcx>, _: ty::Region, bk: ty::BorrowKind, _: LoanCause) {
1331+
if let ty::BorrowKind::MutBorrow = bk {
1332+
if let Categorization::Local(id) = cmt.cat {
1333+
if Some(id) == self.node_id_low {
1334+
self.span_low = Some(sp)
1335+
}
1336+
if Some(id) == self.node_id_high {
1337+
self.span_high = Some(sp)
1338+
}
1339+
}
1340+
}
1341+
}
1342+
1343+
fn mutate(&mut self, _: NodeId, sp: Span, cmt: cmt<'tcx>, _: MutateMode) {
1344+
if let Categorization::Local(id) = cmt.cat {
1345+
if Some(id) == self.node_id_low {
1346+
self.span_low = Some(sp)
1347+
}
1348+
if Some(id) == self.node_id_high {
1349+
self.span_high = Some(sp)
1350+
}
1351+
}
1352+
}
1353+
1354+
fn decl_without_init(&mut self, _: NodeId, _: Span) {
1355+
}
1356+
}
1357+
1358+
impl<'tcx> MutateDelegate {
1359+
fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
1360+
(self.span_low, self.span_high)
1361+
}
1362+
}
1363+
1364+
fn check_for_mut_range_bound(cx: &LateContext, arg: &Expr, body: &Expr) {
1365+
if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(arg) {
1366+
let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)];
1367+
if mut_ids[0].is_some() || mut_ids[1].is_some() {
1368+
let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids);
1369+
mut_warn_with_span(cx, span_low);
1370+
mut_warn_with_span(cx, span_high);
1371+
}
1372+
}
1373+
}
1374+
1375+
fn mut_warn_with_span(cx: &LateContext, span: Option<Span>) {
1376+
if let Some(sp) = span {
1377+
span_lint(cx, MUT_RANGE_BOUND, sp, "attempt to mutate range bound within loop; note that the range of the loop is unchanged");
1378+
}
1379+
}
1380+
1381+
fn check_for_mutability(cx: &LateContext, bound: &Expr) -> Option<NodeId> {
1382+
if_let_chain! {[
1383+
let ExprPath(ref qpath) = bound.node,
1384+
let QPath::Resolved(None, _) = *qpath,
1385+
], {
1386+
let def = cx.tables.qpath_def(qpath, bound.hir_id);
1387+
if let Def::Local(node_id) = def {
1388+
let node_str = cx.tcx.hir.get(node_id);
1389+
if_let_chain! {[
1390+
let map::Node::NodeBinding(pat) = node_str,
1391+
let PatKind::Binding(bind_ann, _, _, _) = pat.node,
1392+
let BindingAnnotation::Mutable = bind_ann,
1393+
], {
1394+
return Some(node_id);
1395+
}}
1396+
}
1397+
}}
1398+
None
1399+
}
1400+
1401+
fn check_for_mutation(cx: &LateContext, body: &Expr, bound_ids: &[Option<NodeId>]) -> (Option<Span>, Option<Span>) {
1402+
let mut delegate = MutateDelegate { node_id_low: bound_ids[0], node_id_high: bound_ids[1], span_low: None, span_high: None };
1403+
let def_id = def_id::DefId::local(body.hir_id.owner);
1404+
let region_scope_tree = &cx.tcx.region_scope_tree(def_id);
1405+
ExprUseVisitor::new(&mut delegate, cx.tcx, cx.param_env, region_scope_tree, cx.tables).walk_expr(body);
1406+
delegate.mutation_span()
1407+
}
1408+
12971409
/// Return true if the pattern is a `PatWild` or an ident prefixed with `'_'`.
12981410
fn pat_is_wild<'tcx>(pat: &'tcx PatKind, body: &'tcx Expr) -> bool {
12991411
match *pat {

mut_range_bound

413 KB
Binary file not shown.

tests/ui/mut_range_bound.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![allow(unused)]
5+
6+
fn main() {
7+
mut_range_bound_upper();
8+
mut_range_bound_lower();
9+
mut_range_bound_both();
10+
mut_range_bound_no_mutation();
11+
immut_range_bound();
12+
mut_borrow_range_bound();
13+
immut_borrow_range_bound();
14+
}
15+
16+
fn mut_range_bound_upper() {
17+
let mut m = 4;
18+
for i in 0..m { m = 5; } // warning
19+
}
20+
21+
fn mut_range_bound_lower() {
22+
let mut m = 4;
23+
for i in m..10 { m *= 2; } // warning
24+
}
25+
26+
fn mut_range_bound_both() {
27+
let mut m = 4;
28+
let mut n = 6;
29+
for i in m..n { m = 5; n = 7; } // warning (1 for each mutated bound)
30+
}
31+
32+
fn mut_range_bound_no_mutation() {
33+
let mut m = 4;
34+
for i in 0..m { continue; } // no warning
35+
}
36+
37+
fn mut_borrow_range_bound() {
38+
let mut m = 4;
39+
for i in 0..m {
40+
let n = &mut m; // warning
41+
*n += 1;
42+
}
43+
}
44+
45+
fn immut_borrow_range_bound() {
46+
let mut m = 4;
47+
for i in 0..m {
48+
let n = &m; // should be no warning?
49+
}
50+
}
51+
52+
53+
fn immut_range_bound() {
54+
let m = 4;
55+
for i in 0..m { continue; } // no warning
56+
}

tests/ui/mut_range_bound.stderr

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
2+
--> $DIR/mut_range_bound.rs:18:21
3+
|
4+
18 | for i in 0..m { m = 5; } // warning
5+
| ^^^^^
6+
|
7+
= note: `-D mut-range-bound` implied by `-D warnings`
8+
9+
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
10+
--> $DIR/mut_range_bound.rs:23:22
11+
|
12+
23 | for i in m..10 { m *= 2; } // warning
13+
| ^^^^^^
14+
15+
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
16+
--> $DIR/mut_range_bound.rs:29:21
17+
|
18+
29 | for i in m..n { m = 5; n = 7; } // warning (1 for each mutated bound)
19+
| ^^^^^
20+
21+
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
22+
--> $DIR/mut_range_bound.rs:29:28
23+
|
24+
29 | for i in m..n { m = 5; n = 7; } // warning (1 for each mutated bound)
25+
| ^^^^^
26+
27+
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
28+
--> $DIR/mut_range_bound.rs:40:22
29+
|
30+
40 | let n = &mut m; // warning
31+
| ^
32+
33+
error: aborting due to 5 previous errors
34+

0 commit comments

Comments
 (0)