Skip to content

Commit 07f807d

Browse files
committed
Auto merge of #24330 - pnkfelix:issue-24267, r=nikomatsakis
Extend rustc::middle::dataflow to allow filtering kills from flow-exits. Fix borrowck analysis so that it will not treat a break that pops through an assignment ```rust x = { ... break; ... } ``` as a kill of the "moved-out" bit for `x`. Fix #24267. [breaking-change], but really, its only breaking code that was already buggy.
2 parents ce27d02 + 77bf827 commit 07f807d

File tree

4 files changed

+108
-23
lines changed

4 files changed

+108
-23
lines changed

src/librustc/middle/dataflow.rs

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ pub struct DataFlowContext<'a, 'tcx: 'a, O> {
6464
/// bits generated as we exit the cfg node. Updated by `add_gen()`.
6565
gens: Vec<usize>,
6666

67-
/// bits killed as we exit the cfg node. Updated by `add_kill()`.
68-
kills: Vec<usize>,
67+
/// bits killed as we exit the cfg node, or non-locally jump over
68+
/// it. Updated by `add_kill(KillFrom::ScopeEnd)`.
69+
scope_kills: Vec<usize>,
70+
71+
/// bits killed as we exit the cfg node directly; if it is jumped
72+
/// over, e.g. via `break`, the kills are not reflected in the
73+
/// jump's effects. Updated by `add_kill(KillFrom::Execution)`.
74+
action_kills: Vec<usize>,
6975

7076
/// bits that are valid on entry to the cfg node. Updated by
7177
/// `propagate()`.
@@ -130,15 +136,23 @@ impl<'a, 'tcx, O:DataFlowOperator> pprust::PpAnn for DataFlowContext<'a, 'tcx, O
130136
"".to_string()
131137
};
132138

133-
let kills = &self.kills[start .. end];
134-
let kills_str = if kills.iter().any(|&u| u != 0) {
135-
format!(" kill: {}", bits_to_string(kills))
139+
let action_kills = &self.action_kills[start .. end];
140+
let action_kills_str = if action_kills.iter().any(|&u| u != 0) {
141+
format!(" action_kill: {}", bits_to_string(action_kills))
142+
} else {
143+
"".to_string()
144+
};
145+
146+
let scope_kills = &self.scope_kills[start .. end];
147+
let scope_kills_str = if scope_kills.iter().any(|&u| u != 0) {
148+
format!(" scope_kill: {}", bits_to_string(scope_kills))
136149
} else {
137150
"".to_string()
138151
};
139152

140-
try!(ps.synth_comment(format!("id {}: {}{}{}", id, entry_str,
141-
gens_str, kills_str)));
153+
try!(ps.synth_comment(
154+
format!("id {}: {}{}{}{}", id, entry_str,
155+
gens_str, action_kills_str, scope_kills_str)));
142156
try!(pp::space(&mut ps.s));
143157
}
144158
Ok(())
@@ -187,6 +201,25 @@ fn build_nodeid_to_index(decl: Option<&ast::FnDecl>,
187201
}
188202
}
189203

204+
/// Flag used by `add_kill` to indicate whether the provided kill
205+
/// takes effect only when control flows directly through the node in
206+
/// question, or if the kill's effect is associated with any
207+
/// control-flow directly through or indirectly over the node.
208+
#[derive(Copy, Clone, PartialEq, Debug)]
209+
pub enum KillFrom {
210+
/// A `ScopeEnd` kill is one that takes effect when any control
211+
/// flow goes over the node. A kill associated with the end of the
212+
/// scope of a variable declaration `let x;` is an example of a
213+
/// `ScopeEnd` kill.
214+
ScopeEnd,
215+
216+
/// An `Execution` kill is one that takes effect only when control
217+
/// flow goes through the node to completion. A kill associated
218+
/// with an assignment statement `x = expr;` is an example of an
219+
/// `Execution` kill.
220+
Execution,
221+
}
222+
190223
impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
191224
pub fn new(tcx: &'a ty::ctxt<'tcx>,
192225
analysis_name: &'static str,
@@ -206,8 +239,10 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
206239

207240
let entry = if oper.initial_value() { usize::MAX } else {0};
208241

209-
let gens: Vec<_> = repeat(0).take(num_nodes * words_per_id).collect();
210-
let kills: Vec<_> = repeat(0).take(num_nodes * words_per_id).collect();
242+
let zeroes: Vec<_> = repeat(0).take(num_nodes * words_per_id).collect();
243+
let gens: Vec<_> = zeroes.clone();
244+
let kills1: Vec<_> = zeroes.clone();
245+
let kills2: Vec<_> = zeroes;
211246
let on_entry: Vec<_> = repeat(entry).take(num_nodes * words_per_id).collect();
212247

213248
let nodeid_to_index = build_nodeid_to_index(decl, cfg);
@@ -220,7 +255,8 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
220255
bits_per_id: bits_per_id,
221256
oper: oper,
222257
gens: gens,
223-
kills: kills,
258+
action_kills: kills1,
259+
scope_kills: kills2,
224260
on_entry: on_entry
225261
}
226262
}
@@ -240,7 +276,7 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
240276
}
241277
}
242278

243-
pub fn add_kill(&mut self, id: ast::NodeId, bit: usize) {
279+
pub fn add_kill(&mut self, kind: KillFrom, id: ast::NodeId, bit: usize) {
244280
//! Indicates that `id` kills `bit`
245281
debug!("{} add_kill(id={}, bit={})",
246282
self.analysis_name, id, bit);
@@ -250,7 +286,10 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
250286
let indices = get_cfg_indices(id, &self.nodeid_to_index);
251287
for &cfgidx in indices {
252288
let (start, end) = self.compute_id_range(cfgidx);
253-
let kills = &mut self.kills[start.. end];
289+
let kills = match kind {
290+
KillFrom::Execution => &mut self.action_kills[start.. end],
291+
KillFrom::ScopeEnd => &mut self.scope_kills[start.. end],
292+
};
254293
set_bit(kills, bit);
255294
}
256295
}
@@ -264,7 +303,9 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
264303
let (start, end) = self.compute_id_range(cfgidx);
265304
let gens = &self.gens[start.. end];
266305
bitwise(bits, gens, &Union);
267-
let kills = &self.kills[start.. end];
306+
let kills = &self.action_kills[start.. end];
307+
bitwise(bits, kills, &Subtract);
308+
let kills = &self.scope_kills[start.. end];
268309
bitwise(bits, kills, &Subtract);
269310

270311
debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [after]",
@@ -278,7 +319,8 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
278319

279320
assert!(start < self.gens.len());
280321
assert!(end <= self.gens.len());
281-
assert!(self.gens.len() == self.kills.len());
322+
assert!(self.gens.len() == self.action_kills.len());
323+
assert!(self.gens.len() == self.scope_kills.len());
282324
assert!(self.gens.len() == self.on_entry.len());
283325

284326
(start, end)
@@ -412,7 +454,7 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
412454
cfg.graph.each_edge(|_edge_index, edge| {
413455
let flow_exit = edge.source();
414456
let (start, end) = self.compute_id_range(flow_exit);
415-
let mut orig_kills = self.kills[start.. end].to_vec();
457+
let mut orig_kills = self.scope_kills[start.. end].to_vec();
416458

417459
let mut changed = false;
418460
for &node_id in &edge.data.exiting_scopes {
@@ -421,8 +463,12 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
421463
Some(indices) => {
422464
for &cfg_idx in indices {
423465
let (start, end) = self.compute_id_range(cfg_idx);
424-
let kills = &self.kills[start.. end];
466+
let kills = &self.scope_kills[start.. end];
425467
if bitwise(&mut orig_kills, kills, &Union) {
468+
debug!("scope exits: scope id={} \
469+
(node={:?} of {:?}) added killset: {}",
470+
node_id, cfg_idx, indices,
471+
bits_to_string(kills));
426472
changed = true;
427473
}
428474
}
@@ -436,7 +482,7 @@ impl<'a, 'tcx, O:DataFlowOperator> DataFlowContext<'a, 'tcx, O> {
436482
}
437483

438484
if changed {
439-
let bits = &mut self.kills[start.. end];
485+
let bits = &mut self.scope_kills[start.. end];
440486
debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [before]",
441487
self.analysis_name, flow_exit, mut_bits_to_string(bits));
442488
bits.clone_from_slice(&orig_kills[..]);

src/librustc_borrowck/borrowck/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc::middle::cfg;
2424
use rustc::middle::dataflow::DataFlowContext;
2525
use rustc::middle::dataflow::BitwiseOperator;
2626
use rustc::middle::dataflow::DataFlowOperator;
27+
use rustc::middle::dataflow::KillFrom;
2728
use rustc::middle::expr_use_visitor as euv;
2829
use rustc::middle::mem_categorization as mc;
2930
use rustc::middle::region;
@@ -167,7 +168,7 @@ fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>,
167168
all_loans.len());
168169
for (loan_idx, loan) in all_loans.iter().enumerate() {
169170
loan_dfcx.add_gen(loan.gen_scope.node_id(), loan_idx);
170-
loan_dfcx.add_kill(loan.kill_scope.node_id(), loan_idx);
171+
loan_dfcx.add_kill(KillFrom::ScopeEnd, loan.kill_scope.node_id(), loan_idx);
171172
}
172173
loan_dfcx.add_kills_from_flow_exits(cfg);
173174
loan_dfcx.propagate(cfg, body);

src/librustc_borrowck/borrowck/move_data.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc::middle::cfg;
1818
use rustc::middle::dataflow::DataFlowContext;
1919
use rustc::middle::dataflow::BitwiseOperator;
2020
use rustc::middle::dataflow::DataFlowOperator;
21+
use rustc::middle::dataflow::KillFrom;
2122
use rustc::middle::expr_use_visitor as euv;
2223
use rustc::middle::ty;
2324
use rustc::util::nodemap::{FnvHashMap, NodeSet};
@@ -473,11 +474,13 @@ impl<'tcx> MoveData<'tcx> {
473474

474475
for (i, assignment) in self.var_assignments.borrow().iter().enumerate() {
475476
dfcx_assign.add_gen(assignment.id, i);
476-
self.kill_moves(assignment.path, assignment.id, dfcx_moves);
477+
self.kill_moves(assignment.path, assignment.id,
478+
KillFrom::Execution, dfcx_moves);
477479
}
478480

479481
for assignment in &*self.path_assignments.borrow() {
480-
self.kill_moves(assignment.path, assignment.id, dfcx_moves);
482+
self.kill_moves(assignment.path, assignment.id,
483+
KillFrom::Execution, dfcx_moves);
481484
}
482485

483486
// Kill all moves related to a variable `x` when
@@ -487,7 +490,8 @@ impl<'tcx> MoveData<'tcx> {
487490
LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
488491
let kill_scope = path.loan_path.kill_scope(tcx);
489492
let path = *self.path_map.borrow().get(&path.loan_path).unwrap();
490-
self.kill_moves(path, kill_scope.node_id(), dfcx_moves);
493+
self.kill_moves(path, kill_scope.node_id(),
494+
KillFrom::ScopeEnd, dfcx_moves);
491495
}
492496
LpExtend(..) => {}
493497
}
@@ -500,7 +504,9 @@ impl<'tcx> MoveData<'tcx> {
500504
match lp.kind {
501505
LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
502506
let kill_scope = lp.kill_scope(tcx);
503-
dfcx_assign.add_kill(kill_scope.node_id(), assignment_index);
507+
dfcx_assign.add_kill(KillFrom::ScopeEnd,
508+
kill_scope.node_id(),
509+
assignment_index);
504510
}
505511
LpExtend(..) => {
506512
tcx.sess.bug("var assignment for non var path");
@@ -568,6 +574,7 @@ impl<'tcx> MoveData<'tcx> {
568574
fn kill_moves(&self,
569575
path: MovePathIndex,
570576
kill_id: ast::NodeId,
577+
kill_kind: KillFrom,
571578
dfcx_moves: &mut MoveDataFlow) {
572579
// We can only perform kills for paths that refer to a unique location,
573580
// since otherwise we may kill a move from one location with an
@@ -576,7 +583,9 @@ impl<'tcx> MoveData<'tcx> {
576583
let loan_path = self.path_loan_path(path);
577584
if loan_path_is_precise(&*loan_path) {
578585
self.each_applicable_move(path, |move_index| {
579-
dfcx_moves.add_kill(kill_id, move_index.get());
586+
debug!("kill_moves add_kill {:?} kill_id={} move_index={}",
587+
kill_kind, kill_id, move_index.get());
588+
dfcx_moves.add_kill(kill_kind, kill_id, move_index.get());
580589
true
581590
});
582591
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2015 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+
// Ensure that we reject code when a nonlocal exit (`break`,
12+
// `continue`) causes us to pop over a needed assignment.
13+
14+
pub fn main() {
15+
foo1();
16+
foo2();
17+
}
18+
19+
pub fn foo1() {
20+
let x: i32;
21+
loop { x = break; }
22+
println!("{}", x); //~ ERROR use of possibly uninitialized variable: `x`
23+
}
24+
25+
pub fn foo2() {
26+
let x: i32;
27+
for _ in 0..10 { x = continue; }
28+
println!("{}", x); //~ ERROR use of possibly uninitialized variable: `x`
29+
}

0 commit comments

Comments
 (0)