From 063b998950f9fcf77630fa820b24375d45426469 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Sun, 24 Dec 2017 00:45:53 +0200 Subject: [PATCH 1/3] add pre-statement-effect to dataflow --- src/librustc_mir/dataflow/at_location.rs | 24 ++++++++++++ src/librustc_mir/dataflow/mod.rs | 47 ++++++++++++++++++++++-- src/librustc_mir/transform/generator.rs | 2 +- src/librustc_mir/transform/rustc_peek.rs | 9 ++++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/librustc_mir/dataflow/at_location.rs b/src/librustc_mir/dataflow/at_location.rs index 7f243ad6e26..b1f73bfbe22 100644 --- a/src/librustc_mir/dataflow/at_location.rs +++ b/src/librustc_mir/dataflow/at_location.rs @@ -149,6 +149,18 @@ impl FlowsAtLocation for FlowAtLocation fn reconstruct_statement_effect(&mut self, loc: Location) { self.stmt_gen.reset_to_empty(); self.stmt_kill.reset_to_empty(); + { + let mut sets = BlockSets { + on_entry: &mut self.curr_state, + gen_set: &mut self.stmt_gen, + kill_set: &mut self.stmt_kill, + }; + self.base_results + .operator() + .before_statement_effect(&mut sets, loc); + } + self.apply_local_effect(loc); + let mut sets = BlockSets { on_entry: &mut self.curr_state, gen_set: &mut self.stmt_gen, @@ -162,6 +174,18 @@ impl FlowsAtLocation for FlowAtLocation fn reconstruct_terminator_effect(&mut self, loc: Location) { self.stmt_gen.reset_to_empty(); self.stmt_kill.reset_to_empty(); + { + let mut sets = BlockSets { + on_entry: &mut self.curr_state, + gen_set: &mut self.stmt_gen, + kill_set: &mut self.stmt_kill, + }; + self.base_results + .operator() + .before_terminator_effect(&mut sets, loc); + } + self.apply_local_effect(loc); + let mut sets = BlockSets { on_entry: &mut self.curr_state, gen_set: &mut self.stmt_gen, diff --git a/src/librustc_mir/dataflow/mod.rs b/src/librustc_mir/dataflow/mod.rs index 83c46e0199e..19333dec3bc 100644 --- a/src/librustc_mir/dataflow/mod.rs +++ b/src/librustc_mir/dataflow/mod.rs @@ -214,6 +214,7 @@ impl<'a, 'tcx: 'a, BD> DataflowAnalysis<'a, 'tcx, BD> where BD: BitDenotation } for j_stmt in 0..statements.len() { let location = Location { block: bb, statement_index: j_stmt }; + self.flow_state.operator.before_statement_effect(sets, location); self.flow_state.operator.statement_effect(sets, location); if track_intrablock { sets.apply_local_effect(); @@ -222,6 +223,7 @@ impl<'a, 'tcx: 'a, BD> DataflowAnalysis<'a, 'tcx, BD> where BD: BitDenotation if terminator.is_some() { let location = Location { block: bb, statement_index: statements.len() }; + self.flow_state.operator.before_terminator_effect(sets, location); self.flow_state.operator.terminator_effect(sets, location); if track_intrablock { sets.apply_local_effect(); @@ -365,9 +367,10 @@ pub(crate) trait DataflowResultsConsumer<'a, 'tcx: 'a> { fn mir(&self) -> &'a Mir<'tcx>; } -pub fn state_for_location(loc: Location, - analysis: &T, - result: &DataflowResults) +pub fn state_for_location<'tcx, T: BitDenotation>(loc: Location, + analysis: &T, + result: &DataflowResults, + mir: &Mir<'tcx>) -> IdxSetBuf { let mut entry = result.sets().on_entry_set_for(loc.block.index()).to_owned(); @@ -381,8 +384,16 @@ pub fn state_for_location(loc: Location, for stmt in 0..loc.statement_index { let mut stmt_loc = loc; stmt_loc.statement_index = stmt; + analysis.before_statement_effect(&mut sets, stmt_loc); analysis.statement_effect(&mut sets, stmt_loc); } + + // Apply the pre-statement effect of the statement we're evaluating. + if loc.statement_index == mir[loc.block].statements.len() { + analysis.before_terminator_effect(&mut sets, loc); + } else { + analysis.before_statement_effect(&mut sets, loc); + } } entry @@ -637,6 +648,21 @@ pub trait BitDenotation: BitwiseOperator { /// (For example, establishing the call arguments.) fn start_block_effect(&self, entry_set: &mut IdxSet); + /// Similar to `statement_effect`, except it applies + /// *just before* the statement rather than *just after* it. + /// + /// This matters for "dataflow at location" APIs, because the + /// before-statement effect is visible while visiting the + /// statement, while the after-statement effect only becomes + /// visible at the next statement. + /// + /// Both the before-statement and after-statement effects are + /// applied, in that order, before moving for the next + /// statement. + fn before_statement_effect(&self, + _sets: &mut BlockSets, + _location: Location) {} + /// Mutates the block-sets (the flow sets for the given /// basic block) according to the effects of evaluating statement. /// @@ -651,6 +677,21 @@ pub trait BitDenotation: BitwiseOperator { sets: &mut BlockSets, location: Location); + /// Similar to `terminator_effect`, except it applies + /// *just before* the terminator rather than *just after* it. + /// + /// This matters for "dataflow at location" APIs, because the + /// before-terminator effect is visible while visiting the + /// terminator, while the after-terminator effect only becomes + /// visible at the terminator's successors. + /// + /// Both the before-terminator and after-terminator effects are + /// applied, in that order, before moving for the next + /// terminator. + fn before_terminator_effect(&self, + _sets: &mut BlockSets, + _location: Location) {} + /// Mutates the block-sets (the flow sets for the given /// basic block) according to the effects of evaluating /// the terminator. diff --git a/src/librustc_mir/transform/generator.rs b/src/librustc_mir/transform/generator.rs index 455a07c04cf..b226bb592cb 100644 --- a/src/librustc_mir/transform/generator.rs +++ b/src/librustc_mir/transform/generator.rs @@ -363,7 +363,7 @@ fn locals_live_across_suspend_points<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, statement_index: data.statements.len(), }; - let storage_liveness = state_for_location(loc, &analysis, &storage_live); + let storage_liveness = state_for_location(loc, &analysis, &storage_live, mir); storage_liveness_map.insert(block, storage_liveness.clone()); diff --git a/src/librustc_mir/transform/rustc_peek.rs b/src/librustc_mir/transform/rustc_peek.rs index 6b8e2b073cc..b6153ea1fdb 100644 --- a/src/librustc_mir/transform/rustc_peek.rs +++ b/src/librustc_mir/transform/rustc_peek.rs @@ -203,11 +203,18 @@ fn each_block<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>, // reset GEN and KILL sets before emulating their effect. for e in sets.gen_set.words_mut() { *e = 0; } for e in sets.kill_set.words_mut() { *e = 0; } - results.0.operator.statement_effect(&mut sets, Location { block: bb, statement_index: j }); + results.0.operator.before_statement_effect( + &mut sets, Location { block: bb, statement_index: j }); + results.0.operator.statement_effect( + &mut sets, Location { block: bb, statement_index: j }); sets.on_entry.union(sets.gen_set); sets.on_entry.subtract(sets.kill_set); } + results.0.operator.before_terminator_effect( + &mut sets, + Location { block: bb, statement_index: statements.len() }); + tcx.sess.span_err(span, &format!("rustc_peek: MIR did not match \ anticipated pattern; note that \ rustc_peek expects input of \ From 17d4e9be2afba44c05135752b837bbd1b7b5bee6 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Sun, 24 Dec 2017 13:37:39 +0200 Subject: [PATCH 2/3] Make killing of out-of-scope borrows a pre-statement effect Fixes #46875. Fixes #46917. Fixes #46935. --- src/librustc_mir/borrow_check/flows.rs | 3 +- src/librustc_mir/borrow_check/mod.rs | 2 ++ src/librustc_mir/dataflow/impls/borrows.rs | 28 +++++++++++++++++ ...o-phase-activation-sharing-interference.rs | 1 - src/test/ui/nll/borrow-use-issue-46875.rs | 30 +++++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/nll/borrow-use-issue-46875.rs diff --git a/src/librustc_mir/borrow_check/flows.rs b/src/librustc_mir/borrow_check/flows.rs index 69a08c7a30d..61d6c14d627 100644 --- a/src/librustc_mir/borrow_check/flows.rs +++ b/src/librustc_mir/borrow_check/flows.rs @@ -88,7 +88,8 @@ impl<'b, 'gcx, 'tcx> fmt::Display for Flows<'b, 'gcx, 'tcx> { }; saw_one = true; let borrow_data = &self.borrows.operator().borrows()[borrow.borrow_index()]; - s.push_str(&format!("{}", borrow_data)); + s.push_str(&format!("{}{}", borrow_data, + if borrow.is_activation() { "@active" } else { "" })); }); s.push_str("] "); diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index 1e8a6792963..2fe9bf064e3 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -2011,6 +2011,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { let borrowed = &data[i.borrow_index()]; if self.places_conflict(&borrowed.borrowed_place, place, access) { + debug!("each_borrow_involving_path: {:?} @ {:?} vs. {:?}/{:?}", + i, borrowed, place, access); let ctrl = op(self, i, borrowed); if ctrl == Control::Break { return; diff --git a/src/librustc_mir/dataflow/impls/borrows.rs b/src/librustc_mir/dataflow/impls/borrows.rs index c39ae10371c..44d4fdf250f 100644 --- a/src/librustc_mir/dataflow/impls/borrows.rs +++ b/src/librustc_mir/dataflow/impls/borrows.rs @@ -635,6 +635,13 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Reservations<'a, 'gcx, 'tcx> { // `_sets`. } + fn before_statement_effect(&self, + sets: &mut BlockSets, + location: Location) { + debug!("Reservations::before_statement_effect sets: {:?} location: {:?}", sets, location); + self.0.kill_loans_out_of_scope_at_location(sets, location, false); + } + fn statement_effect(&self, sets: &mut BlockSets, location: Location) { @@ -642,6 +649,13 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Reservations<'a, 'gcx, 'tcx> { self.0.statement_effect_on_borrows(sets, location, false); } + fn before_terminator_effect(&self, + sets: &mut BlockSets, + location: Location) { + debug!("Reservations::before_terminator_effect sets: {:?} location: {:?}", sets, location); + self.0.kill_loans_out_of_scope_at_location(sets, location, false); + } + fn terminator_effect(&self, sets: &mut BlockSets, location: Location) { @@ -682,6 +696,13 @@ impl<'a, 'gcx, 'tcx> BitDenotation for ActiveBorrows<'a, 'gcx, 'tcx> { // `_sets`. } + fn before_statement_effect(&self, + sets: &mut BlockSets, + location: Location) { + debug!("ActiveBorrows::before_statement_effect sets: {:?} location: {:?}", sets, location); + self.0.kill_loans_out_of_scope_at_location(sets, location, true); + } + fn statement_effect(&self, sets: &mut BlockSets, location: Location) { @@ -689,6 +710,13 @@ impl<'a, 'gcx, 'tcx> BitDenotation for ActiveBorrows<'a, 'gcx, 'tcx> { self.0.statement_effect_on_borrows(sets, location, true); } + fn before_terminator_effect(&self, + sets: &mut BlockSets, + location: Location) { + debug!("ActiveBorrows::before_terminator_effect sets: {:?} location: {:?}", sets, location); + self.0.kill_loans_out_of_scope_at_location(sets, location, true); + } + fn terminator_effect(&self, sets: &mut BlockSets, location: Location) { diff --git a/src/test/compile-fail/borrowck/two-phase-activation-sharing-interference.rs b/src/test/compile-fail/borrowck/two-phase-activation-sharing-interference.rs index b6f5e17f1f6..a9797e4d215 100644 --- a/src/test/compile-fail/borrowck/two-phase-activation-sharing-interference.rs +++ b/src/test/compile-fail/borrowck/two-phase-activation-sharing-interference.rs @@ -56,7 +56,6 @@ fn should_also_eventually_be_ok_with_nll() { let _z = &x; *y += 1; //[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable - //[nll]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable } fn main() { } diff --git a/src/test/ui/nll/borrow-use-issue-46875.rs b/src/test/ui/nll/borrow-use-issue-46875.rs new file mode 100644 index 00000000000..47d69fe8e97 --- /dev/null +++ b/src/test/ui/nll/borrow-use-issue-46875.rs @@ -0,0 +1,30 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(nll)] + +// run-pass + +fn vec() { + let mut _x = vec!['c']; + let _y = &_x; + _x = Vec::new(); +} + +fn int() { + let mut _x = 5; + let _y = &_x; + _x = 7; +} + +fn main() { + vec(); + int(); +} From bd1bd76cd83d0a75917842966c52140a44753ed3 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Sun, 24 Dec 2017 14:45:51 +0200 Subject: [PATCH 3/3] fix linking of place projections projections other than dereferences of `&mut` used to do no linking. Fix that. Fixes #46974. --- .../borrow_check/nll/constraint_generation.rs | 91 +++++++++++++++---- src/test/ui/nll/guarantor-issue-46974.rs | 31 +++++++ src/test/ui/nll/guarantor-issue-46974.stderr | 17 ++++ 3 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 src/test/ui/nll/guarantor-issue-46974.rs create mode 100644 src/test/ui/nll/guarantor-issue-46974.stderr diff --git a/src/librustc_mir/borrow_check/nll/constraint_generation.rs b/src/librustc_mir/borrow_check/nll/constraint_generation.rs index bdacd831cb6..7ca4ebd1cb2 100644 --- a/src/librustc_mir/borrow_check/nll/constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/constraint_generation.rs @@ -130,38 +130,91 @@ impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> { }); } + // Add the reborrow constraint at `location` so that `borrowed_place` + // is valid for `borrow_region`. fn add_reborrow_constraint( &mut self, location: Location, borrow_region: ty::Region<'tcx>, borrowed_place: &Place<'tcx>, ) { - if let Projection(ref proj) = *borrowed_place { - let PlaceProjection { ref base, ref elem } = **proj; + let mut borrowed_place = borrowed_place; - if let ProjectionElem::Deref = *elem { - let tcx = self.infcx.tcx; - let base_ty = base.ty(self.mir, tcx).to_ty(tcx); - let base_sty = &base_ty.sty; + debug!("add_reborrow_constraint({:?}, {:?}, {:?})", + location, borrow_region, borrowed_place); + while let Projection(box PlaceProjection { base, elem }) = borrowed_place { + debug!("add_reborrow_constraint - iteration {:?}", borrowed_place); - if let ty::TyRef(base_region, ty::TypeAndMut { ty: _, mutbl }) = *base_sty { - match mutbl { - hir::Mutability::MutImmutable => {} + match *elem { + ProjectionElem::Deref => { + let tcx = self.infcx.tcx; + let base_ty = base.ty(self.mir, tcx).to_ty(tcx); - hir::Mutability::MutMutable => { - self.add_reborrow_constraint(location, borrow_region, base); + debug!("add_reborrow_constraint - base_ty = {:?}", base_ty); + match base_ty.sty { + ty::TyRef(ref_region, ty::TypeAndMut { ty: _, mutbl }) => { + let span = self.mir.source_info(location).span; + self.regioncx.add_outlives( + span, + ref_region.to_region_vid(), + borrow_region.to_region_vid(), + location.successor_within_block(), + ); + + match mutbl { + hir::Mutability::MutImmutable => { + // Immutable reference. We don't need the base + // to be valid for the entire lifetime of + // the borrow. + break + } + hir::Mutability::MutMutable => { + // Mutable reference. We *do* need the base + // to be valid, because after the base becomes + // invalid, someone else can use our mutable deref. + + // This is in order to make the following function + // illegal: + // ``` + // fn unsafe_deref<'a, 'b>(x: &'a &'b mut T) -> &'b mut T { + // &mut *x + // } + // ``` + // + // As otherwise you could clone `&mut T` using the + // following function: + // ``` + // fn bad(x: &mut T) -> (&mut T, &mut T) { + // let my_clone = unsafe_deref(&'a x); + // ENDREGION 'a; + // (my_clone, x) + // } + // ``` + } + } } + ty::TyRawPtr(..) => { + // deref of raw pointer, guaranteed to be valid + break + } + ty::TyAdt(def, _) if def.is_box() => { + // deref of `Box`, need the base to be valid - propagate + } + _ => bug!("unexpected deref ty {:?} in {:?}", base_ty, borrowed_place) } - - let span = self.mir.source_info(location).span; - self.regioncx.add_outlives( - span, - base_region.to_region_vid(), - borrow_region.to_region_vid(), - location.successor_within_block(), - ); + } + ProjectionElem::Field(..) | + ProjectionElem::Downcast(..) | + ProjectionElem::Index(..) | + ProjectionElem::ConstantIndex { .. } | + ProjectionElem::Subslice { .. } => { + // other field access } } + + // The "propagate" case. We need to check that our base is valid + // for the borrow's lifetime. + borrowed_place = base; } } } diff --git a/src/test/ui/nll/guarantor-issue-46974.rs b/src/test/ui/nll/guarantor-issue-46974.rs new file mode 100644 index 00000000000..57ecddb80ab --- /dev/null +++ b/src/test/ui/nll/guarantor-issue-46974.rs @@ -0,0 +1,31 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that NLL analysis propagates lifetimes correctly through +// field accesses, Box accesses, etc. + +#![feature(nll)] + +fn foo(s: &mut (i32,)) -> i32 { + let t = &mut *s; // this borrow should last for the entire function + let x = &t.0; + *s = (2,); //~ ERROR cannot assign to `*s` + *x +} + +fn bar(s: &Box<(i32,)>) -> &'static i32 { + // FIXME(#46983): error message should be better + &s.0 //~ ERROR free region `` does not outlive free region `'static` +} + +fn main() { + foo(&mut (0,)); + bar(&Box::new((1,))); +} diff --git a/src/test/ui/nll/guarantor-issue-46974.stderr b/src/test/ui/nll/guarantor-issue-46974.stderr new file mode 100644 index 00000000000..68cc87ef407 --- /dev/null +++ b/src/test/ui/nll/guarantor-issue-46974.stderr @@ -0,0 +1,17 @@ +error[E0506]: cannot assign to `*s` because it is borrowed + --> $DIR/guarantor-issue-46974.rs:19:5 + | +17 | let t = &mut *s; // this borrow should last for the entire function + | ------- borrow of `*s` occurs here +18 | let x = &t.0; +19 | *s = (2,); //~ ERROR cannot assign to `*s` + | ^^^^^^^^^ assignment to borrowed `*s` occurs here + +error: free region `` does not outlive free region `'static` + --> $DIR/guarantor-issue-46974.rs:25:5 + | +25 | &s.0 //~ ERROR free region `` does not outlive free region `'static` + | ^^^^ + +error: aborting due to 2 previous errors +