Skip to content

Commit 86e5487

Browse files
committed
Auto merge of #45025 - pnkfelix:mir-borrowck-moves-of-supporting-prefixes-invalidate-uses-too, r=arielb1
MIR-borrowck: moves of prefixes invalidate uses too I overlooked the fact that when we check if a path is moved, we need to check for interference between the (shallow) prefixes and the use in question. ~~Long term, we may want to revise how this computation is done. For example, it might be better to represent the set of invalidated prefixes in the dataflow computation (the `maybe_uninitialized` dataflow), and thus avoid one of the loops in the code here.~~ * Update: I was wrong in my original recollection of the dataflow code, which actually does the right thing, in terms of precisely tracking substructure initialization and movement. Fix #44833 ---- Update: The initial version of this PR's description (and the code as well) erroneously focused on supporting prefixes. ~~But the two main cases of interest are: 1. the *shallow* prefixes, and 2. the deref-free prefix built off a local (if the lvalue is indeed built off a local)~~ Update 2: The main cases of interest are in fact: 1. the nearest prefix with a MovePath, and 2. the suffixes.
2 parents 91eb6fe + d6caf73 commit 86e5487

7 files changed

+276
-17
lines changed

src/librustc_mir/borrow_check.rs

+139-12
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx>
586586
context: Context,
587587
(lvalue, span): (&Lvalue<'gcx>, Span),
588588
flow_state: &InProgress<'b, 'gcx>) {
589-
let move_data = flow_state.inits.base_results.operator().move_data();
589+
let move_data = self.move_data;
590590

591591
// determine if this path has a non-mut owner (and thus needs checking).
592592
let mut l = lvalue;
@@ -611,7 +611,7 @@ impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx>
611611
}
612612
}
613613

614-
if let Some(mpi) = self.move_path_for_lvalue(context, move_data, lvalue) {
614+
if let Some(mpi) = self.move_path_for_lvalue(lvalue) {
615615
if flow_state.inits.curr_state.contains(&mpi) {
616616
// may already be assigned before reaching this statement;
617617
// report error.
@@ -642,29 +642,115 @@ impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx>
642642
let lvalue = self.base_path(lvalue_span.0);
643643

644644
let maybe_uninits = &flow_state.uninits;
645-
let move_data = maybe_uninits.base_results.operator().move_data();
646-
if let Some(mpi) = self.move_path_for_lvalue(context, move_data, lvalue) {
647-
if maybe_uninits.curr_state.contains(&mpi) {
648-
// find and report move(s) that could cause this to be uninitialized
645+
646+
// Bad scenarios:
647+
//
648+
// 1. Move of `a.b.c`, use of `a.b.c`
649+
// 2. Move of `a.b.c`, use of `a.b.c.d` (without first reinitializing `a.b.c.d`)
650+
// 3. Move of `a.b.c`, use of `a` or `a.b`
651+
// 4. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`; note that with
652+
// partial initialization support, one might have `a.x`
653+
// initialized but not `a.b`.
654+
//
655+
// OK scenarios:
656+
//
657+
// 5. Move of `a.b.c`, use of `a.b.d`
658+
// 6. Uninitialized `a.x`, initialized `a.b`, use of `a.b`
659+
// 7. Copied `(a.b: &_)`, use of `*(a.b).c`; note that `a.b`
660+
// must have been initialized for the use to be sound.
661+
// 8. Move of `a.b.c` then reinit of `a.b.c.d`, use of `a.b.c.d`
662+
663+
// The dataflow tracks shallow prefixes distinctly (that is,
664+
// field-accesses on P distinctly from P itself), in order to
665+
// track substructure initialization separately from the whole
666+
// structure.
667+
//
668+
// E.g., when looking at (*a.b.c).d, if the closest prefix for
669+
// which we have a MovePath is `a.b`, then that means that the
670+
// initialization state of `a.b` is all we need to inspect to
671+
// know if `a.b.c` is valid (and from that we infer that the
672+
// dereference and `.d` access is also valid, since we assume
673+
// `a.b.c` is assigned a reference to a initialized and
674+
// well-formed record structure.)
675+
676+
// Therefore, if we seek out the *closest* prefix for which we
677+
// have a MovePath, that should capture the initialization
678+
// state for the lvalue scenario.
679+
//
680+
// This code covers scenarios 1, 2, and 4.
681+
682+
debug!("check_if_path_is_moved part1 lvalue: {:?}", lvalue);
683+
match self.move_path_closest_to(lvalue) {
684+
Ok(mpi) => {
685+
if maybe_uninits.curr_state.contains(&mpi) {
686+
self.report_use_of_moved(context, desired_action, lvalue_span);
687+
return; // don't bother finding other problems.
688+
}
689+
}
690+
Err(NoMovePathFound::ReachedStatic) => {
691+
// Okay: we do not build MoveData for static variables
692+
}
693+
694+
// Only query longest prefix with a MovePath, not further
695+
// ancestors; dataflow recurs on children when parents
696+
// move (to support partial (re)inits).
697+
//
698+
// (I.e. querying parents breaks scenario 8; but may want
699+
// to do such a query based on partial-init feature-gate.)
700+
}
701+
702+
// A move of any shallow suffix of `lvalue` also interferes
703+
// with an attempt to use `lvalue`. This is scenario 3 above.
704+
//
705+
// (Distinct from handling of scenarios 1+2+4 above because
706+
// `lvalue` does not interfere with suffixes of its prefixes,
707+
// e.g. `a.b.c` does not interfere with `a.b.d`)
708+
709+
debug!("check_if_path_is_moved part2 lvalue: {:?}", lvalue);
710+
if let Some(mpi) = self.move_path_for_lvalue(lvalue) {
711+
if let Some(_) = maybe_uninits.has_any_child_of(mpi) {
649712
self.report_use_of_moved(context, desired_action, lvalue_span);
650-
} else {
651-
// sanity check: initialized on *some* path, right?
652-
assert!(flow_state.inits.curr_state.contains(&mpi));
713+
return; // don't bother finding other problems.
653714
}
654715
}
655716
}
656717

718+
/// Currently MoveData does not store entries for all lvalues in
719+
/// the input MIR. For example it will currently filter out
720+
/// lvalues that are Copy; thus we do not track lvalues of shared
721+
/// reference type. This routine will walk up an lvalue along its
722+
/// prefixes, searching for a foundational lvalue that *is*
723+
/// tracked in the MoveData.
724+
///
725+
/// An Err result includes a tag indicated why the search failed.
726+
/// Currenly this can only occur if the lvalue is built off of a
727+
/// static variable, as we do not track those in the MoveData.
728+
fn move_path_closest_to(&mut self, lvalue: &Lvalue<'gcx>)
729+
-> Result<MovePathIndex, NoMovePathFound>
730+
{
731+
let mut last_prefix = lvalue;
732+
for prefix in self.prefixes(lvalue, PrefixSet::All) {
733+
if let Some(mpi) = self.move_path_for_lvalue(prefix) {
734+
return Ok(mpi);
735+
}
736+
last_prefix = prefix;
737+
}
738+
match *last_prefix {
739+
Lvalue::Local(_) => panic!("should have move path for every Local"),
740+
Lvalue::Projection(_) => panic!("PrefixSet::All meant dont stop for Projection"),
741+
Lvalue::Static(_) => return Err(NoMovePathFound::ReachedStatic),
742+
}
743+
}
744+
657745
fn move_path_for_lvalue(&mut self,
658-
_context: Context,
659-
move_data: &MoveData<'gcx>,
660746
lvalue: &Lvalue<'gcx>)
661747
-> Option<MovePathIndex>
662748
{
663749
// If returns None, then there is no move path corresponding
664750
// to a direct owner of `lvalue` (which means there is nothing
665751
// that borrowck tracks for its analysis).
666752

667-
match move_data.rev_lookup.find(lvalue) {
753+
match self.move_data.rev_lookup.find(lvalue) {
668754
LookupResult::Parent(_) => None,
669755
LookupResult::Exact(mpi) => Some(mpi),
670756
}
@@ -733,6 +819,11 @@ impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx>
733819
}
734820
}
735821

822+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
823+
enum NoMovePathFound {
824+
ReachedStatic,
825+
}
826+
736827
impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx> {
737828
fn each_borrow_involving_path<F>(&mut self,
738829
_context: Context,
@@ -846,12 +937,19 @@ mod prefixes {
846937

847938
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
848939
pub(super) enum PrefixSet {
940+
/// Doesn't stop until it returns the base case (a Local or
941+
/// Static prefix).
849942
All,
943+
/// Stops at any dereference.
850944
Shallow,
945+
/// Stops at the deref of a shared reference.
851946
Supporting,
852947
}
853948

854949
impl<'c, 'b, 'a: 'b+'c, 'gcx, 'tcx: 'a> MirBorrowckCtxt<'c, 'b, 'a, 'gcx, 'tcx> {
950+
/// Returns an iterator over the prefixes of `lvalue`
951+
/// (inclusive) from longest to smallest, potentially
952+
/// terminating the iteration early based on `kind`.
855953
pub(super) fn prefixes<'d>(&self,
856954
lvalue: &'d Lvalue<'gcx>,
857955
kind: PrefixSet)
@@ -1340,6 +1438,35 @@ impl<'b, 'tcx: 'b> InProgress<'b, 'tcx> {
13401438
}
13411439
}
13421440

1441+
impl<'b, 'tcx> FlowInProgress<MaybeUninitializedLvals<'b, 'tcx>> {
1442+
fn has_any_child_of(&self, mpi: MovePathIndex) -> Option<MovePathIndex> {
1443+
let move_data = self.base_results.operator().move_data();
1444+
1445+
let mut todo = vec![mpi];
1446+
let mut push_siblings = false; // don't look at siblings of original `mpi`.
1447+
while let Some(mpi) = todo.pop() {
1448+
if self.curr_state.contains(&mpi) {
1449+
return Some(mpi);
1450+
}
1451+
let move_path = &move_data.move_paths[mpi];
1452+
if let Some(child) = move_path.first_child {
1453+
todo.push(child);
1454+
}
1455+
if push_siblings {
1456+
if let Some(sibling) = move_path.next_sibling {
1457+
todo.push(sibling);
1458+
}
1459+
} else {
1460+
// after we've processed the original `mpi`, we should
1461+
// always traverse the siblings of any of its
1462+
// children.
1463+
push_siblings = true;
1464+
}
1465+
}
1466+
return None;
1467+
}
1468+
}
1469+
13431470
impl<BD> FlowInProgress<BD> where BD: BitDenotation {
13441471
fn each_state_bit<F>(&self, f: F) where F: FnMut(BD::Idx) {
13451472
self.curr_state.each_bit(self.base_results.operator().bits_per_block(), f)

src/test/compile-fail/borrowck/borrowck-init-in-fru.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
// revisions: ast mir
12+
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
13+
1114
#[derive(Clone)]
1215
struct point {
1316
x: isize,
@@ -16,6 +19,9 @@ struct point {
1619

1720
fn main() {
1821
let mut origin: point;
19-
origin = point {x: 10,.. origin}; //~ ERROR use of possibly uninitialized variable: `origin.y`
22+
origin = point {x: 10,.. origin};
23+
//[ast]~^ ERROR use of possibly uninitialized variable: `origin.y` [E0381]
24+
//[mir]~^^ ERROR (Ast) [E0381]
25+
//[mir]~| ERROR (Mir) [E0381]
2026
origin.clone();
2127
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
// revisions: ast mir
12+
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
13+
14+
// Check that do not allow access to fields of uninitialized or moved
15+
// structs.
16+
17+
#[derive(Default)]
18+
struct Point {
19+
x: isize,
20+
y: isize,
21+
}
22+
23+
#[derive(Default)]
24+
struct Line {
25+
origin: Point,
26+
middle: Point,
27+
target: Point,
28+
}
29+
30+
impl Line { fn consume(self) { } }
31+
32+
fn main() {
33+
let mut a: Point;
34+
let _ = a.x + 1; //[ast]~ ERROR use of possibly uninitialized variable: `a.x`
35+
//[mir]~^ ERROR [E0381]
36+
//[mir]~| ERROR (Mir) [E0381]
37+
38+
let mut line1 = Line::default();
39+
let _moved = line1.origin;
40+
let _ = line1.origin.x + 1; //[ast]~ ERROR use of collaterally moved value: `line1.origin.x`
41+
//[mir]~^ [E0382]
42+
//[mir]~| (Mir) [E0381]
43+
44+
let mut line2 = Line::default();
45+
let _moved = (line2.origin, line2.middle);
46+
line2.consume(); //[ast]~ ERROR use of partially moved value: `line2` [E0382]
47+
//[mir]~^ [E0382]
48+
//[mir]~| (Mir) [E0381]
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
// revisions: ast mir
12+
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
13+
14+
struct S<X, Y> {
15+
x: X,
16+
y: Y,
17+
}
18+
19+
fn main() {
20+
let x: &&Box<i32>;
21+
let _y = &**x; //[ast]~ ERROR use of possibly uninitialized variable: `**x` [E0381]
22+
//[mir]~^ (Ast) [E0381]
23+
//[mir]~| (Mir) [E0381]
24+
25+
let x: &&S<i32, i32>;
26+
let _y = &**x; //[ast]~ ERROR use of possibly uninitialized variable: `**x` [E0381]
27+
//[mir]~^ (Ast) [E0381]
28+
//[mir]~| (Mir) [E0381]
29+
30+
let x: &&i32;
31+
let _y = &**x; //[ast]~ ERROR use of possibly uninitialized variable: `**x` [E0381]
32+
//[mir]~^ (Ast) [E0381]
33+
//[mir]~| (Mir) [E0381]
34+
35+
36+
let mut a: S<i32, i32>;
37+
a.x = 0;
38+
let _b = &a.x; //[ast]~ ERROR use of possibly uninitialized variable: `a.x` [E0381]
39+
//[mir]~^ ERROR (Ast) [E0381]
40+
// (deliberately *not* an error under MIR-borrowck)
41+
42+
let mut a: S<&&i32, &&i32>;
43+
a.x = &&0;
44+
let _b = &**a.x; //[ast]~ ERROR use of possibly uninitialized variable: `**a.x` [E0381]
45+
//[mir]~^ ERROR (Ast) [E0381]
46+
// (deliberately *not* an error under MIR-borrowck)
47+
48+
49+
let mut a: S<i32, i32>;
50+
a.x = 0;
51+
let _b = &a.y; //[ast]~ ERROR use of possibly uninitialized variable: `a.y` [E0381]
52+
//[mir]~^ ERROR (Ast) [E0381]
53+
//[mir]~| ERROR (Mir) [E0381]
54+
55+
let mut a: S<&&i32, &&i32>;
56+
a.x = &&0;
57+
let _b = &**a.y; //[ast]~ ERROR use of possibly uninitialized variable: `**a.y` [E0381]
58+
//[mir]~^ ERROR (Ast) [E0381]
59+
//[mir]~| ERROR (Mir) [E0381]
60+
}

src/test/compile-fail/borrowck/borrowck-use-in-index-lvalue.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
// revisions: ast mir
12+
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
13+
1114
fn test() {
1215
let w: &mut [isize];
13-
w[5] = 0; //~ ERROR use of possibly uninitialized variable: `*w`
16+
w[5] = 0; //[ast]~ ERROR use of possibly uninitialized variable: `*w` [E0381]
17+
//[mir]~^ ERROR (Ast) [E0381]
18+
//[mir]~| ERROR (Mir) [E0381]
1419

1520
let mut w: &mut [isize];
16-
w[5] = 0; //~ ERROR use of possibly uninitialized variable: `*w`
21+
w[5] = 0; //[ast]~ ERROR use of possibly uninitialized variable: `*w` [E0381]
22+
//[mir]~^ ERROR (Ast) [E0381]
23+
//[mir]~| ERROR (Mir) [E0381]
1724
}
1825

1926
fn main() { test(); }

src/test/compile-fail/borrowck/borrowck-use-uninitialized-in-cast-trait.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
// revisions: ast mir
12+
//[mir]compile-flags: -Z emit-end-regions -Z borrowck-mir
13+
1114
// Variation on `borrowck-use-uninitialized-in-cast` in which we do a
1215
// trait cast from an uninitialized source. Issue #20791.
1316

@@ -16,5 +19,7 @@ impl Foo for i32 { }
1619

1720
fn main() {
1821
let x: &i32;
19-
let y = x as *const Foo; //~ ERROR use of possibly uninitialized variable: `*x`
22+
let y = x as *const Foo; //[ast]~ ERROR use of possibly uninitialized variable: `*x`
23+
//[mir]~^ ERROR (Ast) [E0381]
24+
//[mir]~| ERROR (Mir) [E0381]
2025
}

0 commit comments

Comments
 (0)