Skip to content

Commit 543d645

Browse files
authored
Rollup merge of rust-lang#55758 - davidtwco:issue-55344, r=pnkfelix
[regression - rust2018]: unused_mut lint false positives on nightly Fixes rust-lang#55344. This commit filters out locals that have never been initialized for consideration in the `unused_mut` lint. This is intended to detect when the statement that would have initialized the local was removed as unreachable code. In these cases, we would not want to lint. This is the same behaviour as the AST borrow checker. This is achieved by taking advantage of an existing pass over the MIR for the `unused_mut` lint and creating a set of those locals that were never initialized. r? @pnkfelix
2 parents e182bff + 299a452 commit 543d645

File tree

4 files changed

+136
-28
lines changed

4 files changed

+136
-28
lines changed

Diff for: src/librustc/mir/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,20 @@ impl<'tcx> Mir<'tcx> {
304304
})
305305
}
306306

307+
/// Returns an iterator over all user-declared mutable locals.
308+
#[inline]
309+
pub fn mut_vars_iter<'a>(&'a self) -> impl Iterator<Item = Local> + 'a {
310+
(self.arg_count + 1..self.local_decls.len()).filter_map(move |index| {
311+
let local = Local::new(index);
312+
let decl = &self.local_decls[local];
313+
if decl.is_user_variable.is_some() && decl.mutability == Mutability::Mut {
314+
Some(local)
315+
} else {
316+
None
317+
}
318+
})
319+
}
320+
307321
/// Returns an iterator over all user-declared mutable arguments and locals.
308322
#[inline]
309323
pub fn mut_vars_and_args_iter<'a>(&'a self) -> impl Iterator<Item = Local> + 'a {

Diff for: src/librustc_mir/borrow_check/mod.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,21 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
281281
// Note that this set is expected to be small - only upvars from closures
282282
// would have a chance of erroneously adding non-user-defined mutable vars
283283
// to the set.
284-
let temporary_used_locals: FxHashSet<Local> = mbcx
285-
.used_mut
286-
.iter()
284+
let temporary_used_locals: FxHashSet<Local> = mbcx.used_mut.iter()
287285
.filter(|&local| mbcx.mir.local_decls[*local].is_user_variable.is_none())
288286
.cloned()
289287
.collect();
290-
mbcx.gather_used_muts(temporary_used_locals);
288+
// For the remaining unused locals that are marked as mutable, we avoid linting any that
289+
// were never initialized. These locals may have been removed as unreachable code; or will be
290+
// linted as unused variables.
291+
let unused_mut_locals = mbcx.mir.mut_vars_iter()
292+
.filter(|local| !mbcx.used_mut.contains(local))
293+
.collect();
294+
mbcx.gather_used_muts(temporary_used_locals, unused_mut_locals);
291295

292296
debug!("mbcx.used_mut: {:?}", mbcx.used_mut);
293-
294297
let used_mut = mbcx.used_mut;
295-
296-
for local in mbcx
297-
.mir
298-
.mut_vars_and_args_iter()
299-
.filter(|local| !used_mut.contains(local))
300-
{
298+
for local in mbcx.mir.mut_vars_and_args_iter().filter(|local| !used_mut.contains(local)) {
301299
if let ClearCrossCrate::Set(ref vsi) = mbcx.mir.source_scope_local_data {
302300
let local_decl = &mbcx.mir.local_decls[local];
303301

Diff for: src/librustc_mir/borrow_check/used_muts.rs

+87-17
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,113 @@
99
// except according to those terms.
1010

1111
use rustc::mir::visit::{PlaceContext, Visitor};
12-
use rustc::mir::{Local, Location, Place};
12+
use rustc::mir::{BasicBlock, Local, Location, Place, Statement, StatementKind, TerminatorKind};
1313

1414
use rustc_data_structures::fx::FxHashSet;
1515

1616
use borrow_check::MirBorrowckCtxt;
1717

1818
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
19-
/// Walks the MIR looking for assignments to a set of locals, as part of the unused mutable
20-
/// local variables lint, to update the context's `used_mut` in a single walk.
21-
crate fn gather_used_muts(&mut self, locals: FxHashSet<Local>) {
22-
let mut visitor = GatherUsedMutsVisitor {
23-
needles: locals,
24-
mbcx: self,
25-
};
26-
visitor.visit_mir(visitor.mbcx.mir);
19+
/// Walks the MIR adding to the set of `used_mut` locals that will be ignored for the purposes
20+
/// of the `unused_mut` lint.
21+
///
22+
/// `temporary_used_locals` should contain locals that were found to be temporary, mutable and
23+
/// used from borrow checking. This function looks for assignments into these locals from
24+
/// user-declared locals and adds those user-defined locals to the `used_mut` set. This can
25+
/// occur due to a rare case involving upvars in closures.
26+
///
27+
/// `never_initialized_mut_locals` should contain the set of user-declared mutable locals
28+
/// (not arguments) that have not already been marked as being used.
29+
/// This function then looks for assignments from statements or the terminator into the locals
30+
/// from this set and removes them from the set. This leaves only those locals that have not
31+
/// been assigned to - this set is used as a proxy for locals that were not initialized due to
32+
/// unreachable code. These locals are then considered "used" to silence the lint for them.
33+
/// See #55344 for context.
34+
crate fn gather_used_muts(
35+
&mut self,
36+
temporary_used_locals: FxHashSet<Local>,
37+
mut never_initialized_mut_locals: FxHashSet<Local>,
38+
) {
39+
{
40+
let mut visitor = GatherUsedMutsVisitor {
41+
temporary_used_locals,
42+
never_initialized_mut_locals: &mut never_initialized_mut_locals,
43+
mbcx: self,
44+
};
45+
visitor.visit_mir(visitor.mbcx.mir);
46+
}
47+
48+
// Take the union of the existed `used_mut` set with those variables we've found were
49+
// never initialized.
50+
debug!("gather_used_muts: never_initialized_mut_locals={:?}", never_initialized_mut_locals);
51+
self.used_mut = self.used_mut.union(&never_initialized_mut_locals).cloned().collect();
2752
}
2853
}
2954

30-
/// MIR visitor gathering the assignments to a set of locals, in a single walk.
31-
/// 'visit = the duration of the MIR walk
55+
/// MIR visitor for collecting used mutable variables.
56+
/// The 'visit lifetime represents the duration of the MIR walk.
3257
struct GatherUsedMutsVisitor<'visit, 'cx: 'visit, 'gcx: 'tcx, 'tcx: 'cx> {
33-
needles: FxHashSet<Local>,
58+
temporary_used_locals: FxHashSet<Local>,
59+
never_initialized_mut_locals: &'visit mut FxHashSet<Local>,
3460
mbcx: &'visit mut MirBorrowckCtxt<'cx, 'gcx, 'tcx>,
3561
}
3662

3763
impl<'visit, 'cx, 'gcx, 'tcx> Visitor<'tcx> for GatherUsedMutsVisitor<'visit, 'cx, 'gcx, 'tcx> {
64+
fn visit_terminator_kind(
65+
&mut self,
66+
_block: BasicBlock,
67+
kind: &TerminatorKind<'tcx>,
68+
_location: Location,
69+
) {
70+
debug!("visit_terminator_kind: kind={:?}", kind);
71+
match &kind {
72+
TerminatorKind::Call { destination: Some((into, _)), .. } => {
73+
if let Some(local) = into.base_local() {
74+
debug!(
75+
"visit_terminator_kind: kind={:?} local={:?} \
76+
never_initialized_mut_locals={:?}",
77+
kind, local, self.never_initialized_mut_locals
78+
);
79+
let _ = self.never_initialized_mut_locals.remove(&local);
80+
}
81+
},
82+
_ => {},
83+
}
84+
}
85+
86+
fn visit_statement(
87+
&mut self,
88+
_block: BasicBlock,
89+
statement: &Statement<'tcx>,
90+
_location: Location,
91+
) {
92+
match &statement.kind {
93+
StatementKind::Assign(into, _) => {
94+
// Remove any locals that we found were initialized from the
95+
// `never_initialized_mut_locals` set. At the end, the only remaining locals will
96+
// be those that were never initialized - we will consider those as being used as
97+
// they will either have been removed by unreachable code optimizations; or linted
98+
// as unused variables.
99+
if let Some(local) = into.base_local() {
100+
debug!(
101+
"visit_statement: statement={:?} local={:?} \
102+
never_initialized_mut_locals={:?}",
103+
statement, local, self.never_initialized_mut_locals
104+
);
105+
let _ = self.never_initialized_mut_locals.remove(&local);
106+
}
107+
},
108+
_ => {},
109+
}
110+
}
111+
38112
fn visit_local(
39113
&mut self,
40114
local: &Local,
41115
place_context: PlaceContext<'tcx>,
42116
location: Location,
43117
) {
44-
if !self.needles.contains(local) {
45-
return;
46-
}
47-
48-
if place_context.is_place_assignment() {
118+
if place_context.is_place_assignment() && self.temporary_used_locals.contains(local) {
49119
// Propagate the Local assigned at this Location as a used mutable local variable
50120
for moi in &self.mbcx.move_data.loc_map[location] {
51121
let mpi = &self.mbcx.move_data.moves[*moi].path;

Diff for: src/test/ui/nll/issue-55344.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// compile-pass
12+
13+
#![feature(nll)]
14+
#![allow(unreachable_code)]
15+
#![deny(unused_mut)]
16+
17+
pub fn foo() {
18+
return;
19+
20+
let mut v = 0;
21+
assert_eq!(v, 0);
22+
v = 1;
23+
assert_eq!(v, 1);
24+
}
25+
26+
fn main() {}

0 commit comments

Comments
 (0)