Skip to content

Commit 4dc5661

Browse files
committed
liveness: Warn about unused captured variables
1 parent 74fcbfb commit 4dc5661

12 files changed

+505
-64
lines changed

src/librustc_passes/liveness.rs

+170-54
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,10 @@
7676
//! is not just used to generate a new value. For example, `x += 1` is
7777
//! a read but not a use. This is used to generate better warnings.
7878
//!
79-
//! ## Special Variables
79+
//! ## Special nodes and variables
8080
//!
81-
//! We generate various special variables for various, well, special purposes.
82-
//! These are described in the `specials` struct:
83-
//!
84-
//! - `exit_ln`: a live node that is generated to represent every 'exit' from
85-
//! the function, whether it be by explicit return, panic, or other means.
81+
//! We generate various special nodes for various, well, special purposes.
82+
//! These are described in the `Specials` struct.
8683
8784
use self::LiveNodeKind::*;
8885
use self::VarKind::*;
@@ -131,6 +128,7 @@ enum LiveNodeKind {
131128
UpvarNode(Span),
132129
ExprNode(Span),
133130
VarDefNode(Span),
131+
ClosureNode,
134132
ExitNode,
135133
}
136134

@@ -140,6 +138,7 @@ fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
140138
UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_string(s)),
141139
ExprNode(s) => format!("Expr node [{}]", sm.span_to_string(s)),
142140
VarDefNode(s) => format!("Var def node [{}]", sm.span_to_string(s)),
141+
ClosureNode => "Closure node".to_owned(),
143142
ExitNode => "Exit node".to_owned(),
144143
}
145144
}
@@ -396,10 +395,12 @@ fn visit_fn<'tcx>(
396395

397396
// compute liveness
398397
let mut lsets = Liveness::new(&mut fn_maps, def_id);
399-
let entry_ln = lsets.compute(&body.value);
398+
let entry_ln = lsets.compute(fk, &body, sp, id);
399+
lsets.log_liveness(entry_ln, id);
400400

401401
// check for various error conditions
402402
lsets.visit_body(body);
403+
lsets.warn_about_unused_upvars(entry_ln);
403404
lsets.warn_about_unused_args(body, entry_ln);
404405
}
405406

@@ -634,6 +635,12 @@ impl RWUTable {
634635

635636
#[derive(Copy, Clone)]
636637
struct Specials {
638+
/// A live node representing a point of execution before closure entry &
639+
/// after closure exit. Used to calculate liveness of captured variables
640+
/// through calls to the same closure. Used for Fn & FnMut closures only.
641+
closure_ln: LiveNode,
642+
/// A live node representing every 'exit' from the function, whether it be
643+
/// by explicit return, panic, or other means.
637644
exit_ln: LiveNode,
638645
}
639646

@@ -658,11 +665,8 @@ struct Liveness<'a, 'tcx> {
658665

659666
impl<'a, 'tcx> Liveness<'a, 'tcx> {
660667
fn new(ir: &'a mut IrMaps<'tcx>, def_id: LocalDefId) -> Liveness<'a, 'tcx> {
661-
// Special nodes and variables:
662-
// - exit_ln represents the end of the fn, either by return or panic
663-
// - implicit_ret_var is a pseudo-variable that represents
664-
// an implicit return
665668
let specials = Specials {
669+
closure_ln: ir.add_live_node(ClosureNode),
666670
exit_ln: ir.add_live_node(ExitNode),
667671
};
668672

@@ -789,6 +793,20 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
789793
String::from_utf8(wr).unwrap()
790794
}
791795

796+
fn log_liveness(&self, entry_ln: LiveNode, hir_id: hir::HirId) {
797+
// hack to skip the loop unless debug! is enabled:
798+
debug!(
799+
"^^ liveness computation results for body {} (entry={:?})",
800+
{
801+
for ln_idx in 0..self.ir.num_live_nodes {
802+
debug!("{:?}", self.ln_str(LiveNode(ln_idx as u32)));
803+
}
804+
hir_id
805+
},
806+
entry_ln
807+
);
808+
}
809+
792810
fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) {
793811
self.successors[ln.get()] = succ_ln;
794812

@@ -889,33 +907,87 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
889907
self.rwu_table.assign_unpacked(idx, rwu);
890908
}
891909

892-
fn compute(&mut self, body: &hir::Expr<'_>) -> LiveNode {
893-
debug!("compute: using id for body, {:?}", body);
910+
fn compute(
911+
&mut self,
912+
fk: FnKind<'_>,
913+
body: &hir::Body<'_>,
914+
span: Span,
915+
id: hir::HirId,
916+
) -> LiveNode {
917+
debug!("compute: using id for body, {:?}", body.value);
894918

895-
let s = self.s;
919+
// # Liveness of captured variables
920+
//
921+
// When computing the liveness for captured variables we take into
922+
// account how variable is captured (ByRef vs ByValue) and what is the
923+
// closure kind (Generator / FnOnce vs Fn / FnMut).
924+
//
925+
// Variables captured by reference are assumed to be used on the exit
926+
// from the closure.
927+
//
928+
// In FnOnce closures, variables captured by value are known to be dead
929+
// on exit since it is impossible to call the closure again.
930+
//
931+
// In Fn / FnMut closures, variables captured by value are live on exit
932+
// if they are live on the entry to the closure, since only the closure
933+
// itself can access them on subsequent calls.
896934

897935
if let Some(upvars) = self.ir.tcx.upvars_mentioned(self.ir.body_owner) {
936+
// Mark upvars captured by reference as used after closure exits.
898937
for (&var_hir_id, upvar) in upvars.iter().rev() {
899-
let var = self.variable(var_hir_id, upvar.span);
900-
self.acc(s.exit_ln, var, ACC_READ | ACC_USE);
938+
let upvar_id = ty::UpvarId {
939+
var_path: ty::UpvarPath { hir_id: var_hir_id },
940+
closure_expr_id: self.ir.body_owner.expect_local(),
941+
};
942+
match self.tables.upvar_capture(upvar_id) {
943+
ty::UpvarCapture::ByRef(_) => {
944+
let var = self.variable(var_hir_id, upvar.span);
945+
self.acc(self.s.exit_ln, var, ACC_READ | ACC_USE);
946+
}
947+
ty::UpvarCapture::ByValue => {}
948+
}
901949
}
902950
}
903951

904-
let entry_ln = self.propagate_through_expr(body, s.exit_ln);
952+
let succ = self.propagate_through_expr(&body.value, self.s.exit_ln);
905953

906-
// hack to skip the loop unless debug! is enabled:
907-
debug!(
908-
"^^ liveness computation results for body {} (entry={:?})",
909-
{
910-
for ln_idx in 0..self.ir.num_live_nodes {
911-
debug!("{:?}", self.ln_str(LiveNode(ln_idx as u32)));
912-
}
913-
body.hir_id
954+
match fk {
955+
FnKind::Method(..) | FnKind::ItemFn(..) => return succ,
956+
FnKind::Closure(..) => {}
957+
}
958+
959+
let ty = self.tables.node_type(id);
960+
match ty.kind {
961+
ty::Closure(_def_id, substs) => match substs.as_closure().kind() {
962+
ty::ClosureKind::Fn => {}
963+
ty::ClosureKind::FnMut => {}
964+
ty::ClosureKind::FnOnce => return succ,
914965
},
915-
entry_ln
916-
);
966+
ty::Generator(..) => return succ,
967+
_ => {
968+
span_bug!(span, "type of closure expr {:?} is not a closure {:?}", id, ty,);
969+
}
970+
};
917971

918-
entry_ln
972+
// Propagate through calls to the closure.
973+
let mut first_merge = true;
974+
loop {
975+
self.init_from_succ(self.s.closure_ln, succ);
976+
for param in body.params {
977+
param.pat.each_binding(|_bm, hir_id, _x, ident| {
978+
let var = self.variable(hir_id, ident.span);
979+
self.define(self.s.closure_ln, var);
980+
})
981+
}
982+
983+
if !self.merge_from_succ(self.s.exit_ln, self.s.closure_ln, first_merge) {
984+
break;
985+
}
986+
first_merge = false;
987+
assert_eq!(succ, self.propagate_through_expr(&body.value, self.s.exit_ln));
988+
}
989+
990+
succ
919991
}
920992

921993
fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode {
@@ -1533,11 +1605,60 @@ impl<'tcx> Liveness<'_, 'tcx> {
15331605
if name.is_empty() || name.as_bytes()[0] == b'_' { None } else { Some(name) }
15341606
}
15351607

1608+
fn warn_about_unused_upvars(&self, entry_ln: LiveNode) {
1609+
let upvars = match self.ir.tcx.upvars_mentioned(self.ir.body_owner) {
1610+
None => return,
1611+
Some(upvars) => upvars,
1612+
};
1613+
for (&var_hir_id, upvar) in upvars.iter() {
1614+
let var = self.variable(var_hir_id, upvar.span);
1615+
let upvar_id = ty::UpvarId {
1616+
var_path: ty::UpvarPath { hir_id: var_hir_id },
1617+
closure_expr_id: self.ir.body_owner.expect_local(),
1618+
};
1619+
match self.tables.upvar_capture(upvar_id) {
1620+
ty::UpvarCapture::ByValue => {}
1621+
ty::UpvarCapture::ByRef(..) => continue,
1622+
};
1623+
if self.used_on_entry(entry_ln, var) {
1624+
if self.live_on_entry(entry_ln, var).is_none() {
1625+
if let Some(name) = self.should_warn(var) {
1626+
self.ir.tcx.struct_span_lint_hir(
1627+
lint::builtin::UNUSED_ASSIGNMENTS,
1628+
var_hir_id,
1629+
vec![upvar.span],
1630+
|lint| {
1631+
lint.build(&format!("value captured by `{}` is never read", name))
1632+
.help("did you mean to capture by reference instead?")
1633+
.emit();
1634+
},
1635+
);
1636+
}
1637+
}
1638+
} else {
1639+
if let Some(name) = self.should_warn(var) {
1640+
self.ir.tcx.struct_span_lint_hir(
1641+
lint::builtin::UNUSED_VARIABLES,
1642+
var_hir_id,
1643+
vec![upvar.span],
1644+
|lint| {
1645+
lint.build(&format!("unused variable: `{}`", name))
1646+
.help("did you mean to capture by reference instead?")
1647+
.emit();
1648+
},
1649+
);
1650+
}
1651+
}
1652+
}
1653+
}
1654+
15361655
fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
15371656
for p in body.params {
15381657
self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| {
15391658
if self.live_on_entry(ln, var).is_none() {
1540-
self.report_dead_assign(hir_id, spans, var, true);
1659+
self.report_unsed_assign(hir_id, spans, var, |name| {
1660+
format!("value passed to `{}` is never read", name)
1661+
});
15411662
}
15421663
});
15431664
}
@@ -1651,35 +1772,30 @@ impl<'tcx> Liveness<'_, 'tcx> {
16511772

16521773
fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
16531774
if self.live_on_exit(ln, var).is_none() {
1654-
self.report_dead_assign(hir_id, spans, var, false);
1775+
self.report_unsed_assign(hir_id, spans, var, |name| {
1776+
format!("value assigned to `{}` is never read", name)
1777+
});
16551778
}
16561779
}
16571780

1658-
fn report_dead_assign(&self, hir_id: HirId, spans: Vec<Span>, var: Variable, is_param: bool) {
1781+
fn report_unsed_assign(
1782+
&self,
1783+
hir_id: HirId,
1784+
spans: Vec<Span>,
1785+
var: Variable,
1786+
message: impl Fn(&str) -> String,
1787+
) {
16591788
if let Some(name) = self.should_warn(var) {
1660-
if is_param {
1661-
self.ir.tcx.struct_span_lint_hir(
1662-
lint::builtin::UNUSED_ASSIGNMENTS,
1663-
hir_id,
1664-
spans,
1665-
|lint| {
1666-
lint.build(&format!("value passed to `{}` is never read", name))
1667-
.help("maybe it is overwritten before being read?")
1668-
.emit();
1669-
},
1670-
)
1671-
} else {
1672-
self.ir.tcx.struct_span_lint_hir(
1673-
lint::builtin::UNUSED_ASSIGNMENTS,
1674-
hir_id,
1675-
spans,
1676-
|lint| {
1677-
lint.build(&format!("value assigned to `{}` is never read", name))
1678-
.help("maybe it is overwritten before being read?")
1679-
.emit();
1680-
},
1681-
)
1682-
}
1789+
self.ir.tcx.struct_span_lint_hir(
1790+
lint::builtin::UNUSED_ASSIGNMENTS,
1791+
hir_id,
1792+
spans,
1793+
|lint| {
1794+
lint.build(&message(&name))
1795+
.help("maybe it is overwritten before being read?")
1796+
.emit();
1797+
},
1798+
)
16831799
}
16841800
}
16851801
}

src/test/ui/closures/closure-immutable-outer-variable.fixed

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ fn foo(mut f: Box<dyn FnMut()>) {
88

99
fn main() {
1010
let mut y = true;
11-
foo(Box::new(move || y = false) as Box<_>);
11+
foo(Box::new(move || y = !y) as Box<_>);
1212
//~^ ERROR cannot assign to `y`, as it is not declared as mutable
1313
}

src/test/ui/closures/closure-immutable-outer-variable.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ fn foo(mut f: Box<dyn FnMut()>) {
88

99
fn main() {
1010
let y = true;
11-
foo(Box::new(move || y = false) as Box<_>);
11+
foo(Box::new(move || y = !y) as Box<_>);
1212
//~^ ERROR cannot assign to `y`, as it is not declared as mutable
1313
}

src/test/ui/closures/closure-immutable-outer-variable.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ error[E0594]: cannot assign to `y`, as it is not declared as mutable
33
|
44
LL | let y = true;
55
| - help: consider changing this to be mutable: `mut y`
6-
LL | foo(Box::new(move || y = false) as Box<_>);
7-
| ^^^^^^^^^ cannot assign
6+
LL | foo(Box::new(move || y = !y) as Box<_>);
7+
| ^^^^^^ cannot assign
88

99
error: aborting due to previous error
1010

src/test/ui/issues/issue-11958.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// run-pass
2-
#![forbid(warnings)]
32

43
// We shouldn't need to rebind a moved upvar as mut if it's already
54
// marked as mut
65

76
pub fn main() {
87
let mut x = 1;
98
let _thunk = Box::new(move|| { x = 2; });
9+
//~^ WARN value assigned to `x` is never read
10+
//~| WARN unused variable: `x`
1011
}

src/test/ui/issues/issue-11958.stderr

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
warning: value assigned to `x` is never read
2+
--> $DIR/issue-11958.rs:8:36
3+
|
4+
LL | let _thunk = Box::new(move|| { x = 2; });
5+
| ^
6+
|
7+
= note: `#[warn(unused_assignments)]` on by default
8+
= help: maybe it is overwritten before being read?
9+
10+
warning: unused variable: `x`
11+
--> $DIR/issue-11958.rs:8:36
12+
|
13+
LL | let _thunk = Box::new(move|| { x = 2; });
14+
| ^
15+
|
16+
= note: `#[warn(unused_variables)]` on by default
17+
= help: did you mean to capture by reference instead?
18+
19+
warning: 2 warnings emitted
20+

0 commit comments

Comments
 (0)