Skip to content

Commit dcada26

Browse files
committed
MIR-borrowck: Big fix to fn check_if_path_is_moved.
Fix rust-lang#44833 (a very specific instance of a very broad bug). In `check_if_path_is_moved(L)`, check nearest prefix of L with MovePath, and suffixes of L with MovePaths. Over the course of review, ariel pointed out a number of issues that led to this version of the commit: 1. Looking solely at supporting prefixes does not suffice: it overlooks checking if the path was ever actually initialized in the first place. So you need to be willing to consider non-supporting prefixes. Once you are looking at all prefixes, you *could* just look at the local that forms the base of the projection, but to handle partial initialization (which still needs to be formally specified), this code instead looks at the nearest prefix of L that has an associated MovePath (which, in the limit, will end up being a local). 2. You also need to consider the suffixes of the given Lvalue, due to how dataflow is representing partial moves of individual fields out of struct values. 3. (There was originally a third search, but ariel pointed out that the first and third could be folded into one.) Also includes some drive-by refactorings to simplify some method signatures and prefer `for _ in _` over `loop { }` (at least when it comes semi-naturally).
1 parent 5f578df commit dcada26

File tree

1 file changed

+139
-12
lines changed

1 file changed

+139
-12
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)
@@ -1266,6 +1364,35 @@ impl<'b, 'tcx: 'b> InProgress<'b, 'tcx> {
12661364
}
12671365
}
12681366

1367+
impl<'b, 'tcx> FlowInProgress<MaybeUninitializedLvals<'b, 'tcx>> {
1368+
fn has_any_child_of(&self, mpi: MovePathIndex) -> Option<MovePathIndex> {
1369+
let move_data = self.base_results.operator().move_data();
1370+
1371+
let mut todo = vec![mpi];
1372+
let mut push_siblings = false; // don't look at siblings of original `mpi`.
1373+
while let Some(mpi) = todo.pop() {
1374+
if self.curr_state.contains(&mpi) {
1375+
return Some(mpi);
1376+
}
1377+
let move_path = &move_data.move_paths[mpi];
1378+
if let Some(child) = move_path.first_child {
1379+
todo.push(child);
1380+
}
1381+
if push_siblings {
1382+
if let Some(sibling) = move_path.next_sibling {
1383+
todo.push(sibling);
1384+
}
1385+
} else {
1386+
// after we've processed the original `mpi`, we should
1387+
// always traverse the siblings of any of its
1388+
// children.
1389+
push_siblings = true;
1390+
}
1391+
}
1392+
return None;
1393+
}
1394+
}
1395+
12691396
impl<BD> FlowInProgress<BD> where BD: BitDenotation {
12701397
fn each_state_bit<F>(&self, f: F) where F: FnMut(BD::Idx) {
12711398
self.curr_state.each_bit(self.base_results.operator().bits_per_block(), f)

0 commit comments

Comments
 (0)