+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! This query borrow-checks the MIR to (further) ensure it is not broken.
-
-use rustc::hir;
-use rustc::hir::def_id::DefId;
-use rustc::infer::InferCtxt;
-use rustc::ty::{self, ParamEnv, TyCtxt};
-use rustc::ty::maps::Providers;
-use rustc::mir::{AssertMessage, BasicBlock, BorrowKind, Local, Location, Place};
-use rustc::mir::{Mir, Mutability, Operand, Projection, ProjectionElem, Rvalue};
-use rustc::mir::{Field, Statement, StatementKind, Terminator, TerminatorKind};
-use transform::nll;
-
-use rustc_data_structures::fx::FxHashSet;
-use rustc_data_structures::indexed_set::{self, IdxSetBuf};
-use rustc_data_structures::indexed_vec::Idx;
-
-use syntax::ast;
-use syntax_pos::Span;
-
-use dataflow::do_dataflow;
-use dataflow::MoveDataParamEnv;
-use dataflow::{BitDenotation, BlockSets, DataflowResults, DataflowResultsConsumer};
-use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
-use dataflow::{EverInitializedLvals, MovingOutStatements};
-use dataflow::{BorrowData, BorrowIndex, Borrows};
-use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
-use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MoveOutIndex, MovePathIndex};
-use util::borrowck_errors::{BorrowckErrors, Origin};
-
-use self::MutateMode::{JustWrite, WriteAndRead};
-
-
-pub fn provide(providers: &mut Providers) {
- *providers = Providers {
- mir_borrowck,
- ..*providers
- };
-}
-
-fn mir_borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) {
- let input_mir = tcx.mir_validated(def_id);
- debug!("run query mir_borrowck: {}", tcx.item_path_str(def_id));
-
- if {
- !tcx.has_attr(def_id, "rustc_mir_borrowck") && !tcx.sess.opts.borrowck_mode.use_mir()
- && !tcx.sess.opts.debugging_opts.nll
- } {
- return;
- }
-
- tcx.infer_ctxt().enter(|infcx| {
- let input_mir: &Mir = &input_mir.borrow();
- do_mir_borrowck(&infcx, input_mir, def_id);
- });
- debug!("mir_borrowck done");
-}
-
-fn do_mir_borrowck<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- input_mir: &Mir<'gcx>,
- def_id: DefId,
-) {
- let tcx = infcx.tcx;
- let attributes = tcx.get_attrs(def_id);
- let param_env = tcx.param_env(def_id);
- let id = tcx.hir
- .as_local_node_id(def_id)
- .expect("do_mir_borrowck: non-local DefId");
-
- let move_data: MoveData<'tcx> = match MoveData::gather_moves(input_mir, tcx) {
- Ok(move_data) => move_data,
- Err((move_data, move_errors)) => {
- for move_error in move_errors {
- let (span, kind): (Span, IllegalMoveOriginKind) = match move_error {
- MoveError::UnionMove { .. } => {
- unimplemented!("dont know how to report union move errors yet.")
- }
- MoveError::IllegalMove {
- cannot_move_out_of: o,
- } => (o.span, o.kind),
- };
- let origin = Origin::Mir;
- let mut err = match kind {
- IllegalMoveOriginKind::Static => {
- tcx.cannot_move_out_of(span, "static item", origin)
- }
- IllegalMoveOriginKind::BorrowedContent => {
- tcx.cannot_move_out_of(span, "borrowed content", origin)
- }
- IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
- tcx.cannot_move_out_of_interior_of_drop(span, ty, origin)
- }
- IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
- tcx.cannot_move_out_of_interior_noncopy(span, ty, is_index, origin)
- }
- };
- err.emit();
- }
- move_data
- }
- };
-
- // Make our own copy of the MIR. This copy will be modified (in place) to
- // contain non-lexical lifetimes. It will have a lifetime tied
- // to the inference context.
- let mut mir: Mir<'tcx> = input_mir.clone();
- let mir = &mut mir;
-
- // If we are in non-lexical mode, compute the non-lexical lifetimes.
- let opt_regioncx = if !tcx.sess.opts.debugging_opts.nll {
- None
- } else {
- Some(nll::compute_regions(infcx, def_id, param_env, mir))
- };
-
- let mdpe = MoveDataParamEnv {
- move_data: move_data,
- param_env: param_env,
- };
- let dead_unwinds = IdxSetBuf::new_empty(mir.basic_blocks().len());
- let flow_borrows = do_dataflow(
- tcx,
- mir,
- id,
- &attributes,
- &dead_unwinds,
- Borrows::new(tcx, mir, opt_regioncx.as_ref()),
- |bd, i| bd.location(i),
- );
- let flow_inits = do_dataflow(
- tcx,
- mir,
- id,
- &attributes,
- &dead_unwinds,
- MaybeInitializedLvals::new(tcx, mir, &mdpe),
- |bd, i| &bd.move_data().move_paths[i],
- );
- let flow_uninits = do_dataflow(
- tcx,
- mir,
- id,
- &attributes,
- &dead_unwinds,
- MaybeUninitializedLvals::new(tcx, mir, &mdpe),
- |bd, i| &bd.move_data().move_paths[i],
- );
- let flow_move_outs = do_dataflow(
- tcx,
- mir,
- id,
- &attributes,
- &dead_unwinds,
- MovingOutStatements::new(tcx, mir, &mdpe),
- |bd, i| &bd.move_data().moves[i],
- );
- let flow_ever_inits = do_dataflow(
- tcx,
- mir,
- id,
- &attributes,
- &dead_unwinds,
- EverInitializedLvals::new(tcx, mir, &mdpe),
- |bd, i| &bd.move_data().inits[i],
- );
-
- let mut mbcx = MirBorrowckCtxt {
- tcx: tcx,
- mir: mir,
- node_id: id,
- move_data: &mdpe.move_data,
- param_env: param_env,
- storage_dead_or_drop_error_reported: FxHashSet(),
- };
-
- let mut state = InProgress::new(
- flow_borrows,
- flow_inits,
- flow_uninits,
- flow_move_outs,
- flow_ever_inits,
- );
-
- mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer
-}
-
-#[allow(dead_code)]
-pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
- tcx: TyCtxt<'cx, 'gcx, 'tcx>,
- mir: &'cx Mir<'tcx>,
- node_id: ast::NodeId,
- move_data: &'cx MoveData<'tcx>,
- param_env: ParamEnv<'gcx>,
- /// This field keeps track of when storage dead or drop errors are reported
- /// in order to stop duplicate error reporting and identify the conditions required
- /// for a "temporary value dropped here while still borrowed" error. See #45360.
- storage_dead_or_drop_error_reported: FxHashSet<Local>,
-}
-
-// (forced to be `pub` due to its use as an associated type below.)
-pub struct InProgress<'b, 'gcx: 'tcx, 'tcx: 'b> {
- borrows: FlowInProgress<Borrows<'b, 'gcx, 'tcx>>,
- inits: FlowInProgress<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
- uninits: FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
- move_outs: FlowInProgress<MovingOutStatements<'b, 'gcx, 'tcx>>,
- ever_inits: FlowInProgress<EverInitializedLvals<'b, 'gcx, 'tcx>>,
-}
-
-struct FlowInProgress<BD>
-where
- BD: BitDenotation,
-{
- base_results: DataflowResults<BD>,
- curr_state: IdxSetBuf<BD::Idx>,
- stmt_gen: IdxSetBuf<BD::Idx>,
- stmt_kill: IdxSetBuf<BD::Idx>,
-}
-
-// Check that:
-// 1. assignments are always made to mutable locations (FIXME: does that still really go here?)
-// 2. loans made in overlapping scopes do not conflict
-// 3. assignments do not affect things loaned out as immutable
-// 4. moves do not affect things loaned out in any way
-impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- type FlowState = InProgress<'cx, 'gcx, 'tcx>;
-
- fn mir(&self) -> &'cx Mir<'tcx> {
- self.mir
- }
-
- fn reset_to_entry_of(&mut self, bb: BasicBlock, flow_state: &mut Self::FlowState) {
- flow_state.each_flow(
- |b| b.reset_to_entry_of(bb),
- |i| i.reset_to_entry_of(bb),
- |u| u.reset_to_entry_of(bb),
- |m| m.reset_to_entry_of(bb),
- |e| e.reset_to_entry_of(bb),
- );
- }
-
- fn reconstruct_statement_effect(
- &mut self,
- location: Location,
- flow_state: &mut Self::FlowState,
- ) {
- flow_state.each_flow(
- |b| b.reconstruct_statement_effect(location),
- |i| i.reconstruct_statement_effect(location),
- |u| u.reconstruct_statement_effect(location),
- |m| m.reconstruct_statement_effect(location),
- |e| e.reconstruct_statement_effect(location),
- );
- }
-
- fn apply_local_effect(&mut self, _location: Location, flow_state: &mut Self::FlowState) {
- flow_state.each_flow(
- |b| b.apply_local_effect(),
- |i| i.apply_local_effect(),
- |u| u.apply_local_effect(),
- |m| m.apply_local_effect(),
- |e| e.apply_local_effect(),
- );
- }
-
- fn reconstruct_terminator_effect(
- &mut self,
- location: Location,
- flow_state: &mut Self::FlowState,
- ) {
- flow_state.each_flow(
- |b| b.reconstruct_terminator_effect(location),
- |i| i.reconstruct_terminator_effect(location),
- |u| u.reconstruct_terminator_effect(location),
- |m| m.reconstruct_terminator_effect(location),
- |e| e.reconstruct_terminator_effect(location),
- );
- }
-
- fn visit_block_entry(&mut self, bb: BasicBlock, flow_state: &Self::FlowState) {
- let summary = flow_state.summary();
- debug!("MirBorrowckCtxt::process_block({:?}): {}", bb, summary);
- }
-
- fn visit_statement_entry(
- &mut self,
- location: Location,
- stmt: &Statement<'tcx>,
- flow_state: &Self::FlowState,
- ) {
- let summary = flow_state.summary();
- debug!(
- "MirBorrowckCtxt::process_statement({:?}, {:?}): {}",
- location,
- stmt,
- summary
- );
- let span = stmt.source_info.span;
- match stmt.kind {
- StatementKind::Assign(ref lhs, ref rhs) => {
- // NOTE: NLL RFC calls for *shallow* write; using Deep
- // for short-term compat w/ AST-borrowck. Also, switch
- // to shallow requires to dataflow: "if this is an
- // assignment `place = <rvalue>`, then any loan for some
- // path P of which `place` is a prefix is killed."
- self.mutate_place(
- ContextKind::AssignLhs.new(location),
- (lhs, span),
- Deep,
- JustWrite,
- flow_state,
- );
-
- self.consume_rvalue(
- ContextKind::AssignRhs.new(location),
- (rhs, span),
- location,
- flow_state,
- );
- }
- StatementKind::SetDiscriminant {
- ref place,
- variant_index: _,
- } => {
- self.mutate_place(
- ContextKind::SetDiscrim.new(location),
- (place, span),
- Shallow(Some(ArtificialField::Discriminant)),
- JustWrite,
- flow_state,
- );
- }
- StatementKind::InlineAsm {
- ref asm,
- ref outputs,
- ref inputs,
- } => {
- let context = ContextKind::InlineAsm.new(location);
- for (o, output) in asm.outputs.iter().zip(outputs) {
- if o.is_indirect {
- // FIXME(eddyb) indirect inline asm outputs should
- // be encoeded through MIR place derefs instead.
- self.access_place(
- context,
- (output, span),
- (Deep, Read(ReadKind::Copy)),
- LocalMutationIsAllowed::No,
- flow_state,
- );
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Use,
- (output, span),
- flow_state,
- );
- } else {
- self.mutate_place(
- context,
- (output, span),
- Deep,
- if o.is_rw { WriteAndRead } else { JustWrite },
- flow_state,
- );
- }
- }
- for input in inputs {
- self.consume_operand(context, (input, span), flow_state);
- }
- }
- StatementKind::EndRegion(ref _rgn) => {
- // ignored when consuming results (update to
- // flow_state already handled).
- }
- StatementKind::Nop | StatementKind::Validate(..) | StatementKind::StorageLive(..) => {
- // `Nop`, `Validate`, and `StorageLive` are irrelevant
- // to borrow check.
- }
-
- StatementKind::StorageDead(local) => {
- self.access_place(
- ContextKind::StorageDead.new(location),
- (&Place::Local(local), span),
- (Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
- LocalMutationIsAllowed::Yes,
- flow_state,
- );
- }
- }
- }
-
- fn visit_terminator_entry(
- &mut self,
- location: Location,
- term: &Terminator<'tcx>,
- flow_state: &Self::FlowState,
- ) {
- let loc = location;
- let summary = flow_state.summary();
- debug!(
- "MirBorrowckCtxt::process_terminator({:?}, {:?}): {}",
- location,
- term,
- summary
- );
- let span = term.source_info.span;
- match term.kind {
- TerminatorKind::SwitchInt {
- ref discr,
- switch_ty: _,
- values: _,
- targets: _,
- } => {
- self.consume_operand(ContextKind::SwitchInt.new(loc), (discr, span), flow_state);
- }
- TerminatorKind::Drop {
- location: ref drop_place,
- target: _,
- unwind: _,
- } => {
- self.access_place(
- ContextKind::Drop.new(loc),
- (drop_place, span),
- (Deep, Write(WriteKind::StorageDeadOrDrop)),
- LocalMutationIsAllowed::Yes,
- flow_state,
- );
- }
- TerminatorKind::DropAndReplace {
- location: ref drop_place,
- value: ref new_value,
- target: _,
- unwind: _,
- } => {
- self.mutate_place(
- ContextKind::DropAndReplace.new(loc),
- (drop_place, span),
- Deep,
- JustWrite,
- flow_state,
- );
- self.consume_operand(
- ContextKind::DropAndReplace.new(loc),
- (new_value, span),
- flow_state,
- );
- }
- TerminatorKind::Call {
- ref func,
- ref args,
- ref destination,
- cleanup: _,
- } => {
- self.consume_operand(ContextKind::CallOperator.new(loc), (func, span), flow_state);
- for arg in args {
- self.consume_operand(
- ContextKind::CallOperand.new(loc),
- (arg, span),
- flow_state,
- );
- }
- if let Some((ref dest, _ /*bb*/)) = *destination {
- self.mutate_place(
- ContextKind::CallDest.new(loc),
- (dest, span),
- Deep,
- JustWrite,
- flow_state,
- );
- }
- }
- TerminatorKind::Assert {
- ref cond,
- expected: _,
- ref msg,
- target: _,
- cleanup: _,
- } => {
- self.consume_operand(ContextKind::Assert.new(loc), (cond, span), flow_state);
- match *msg {
- AssertMessage::BoundsCheck { ref len, ref index } => {
- self.consume_operand(ContextKind::Assert.new(loc), (len, span), flow_state);
- self.consume_operand(
- ContextKind::Assert.new(loc),
- (index, span),
- flow_state,
- );
- }
- AssertMessage::Math(_ /*const_math_err*/) => {}
- AssertMessage::GeneratorResumedAfterReturn => {}
- AssertMessage::GeneratorResumedAfterPanic => {}
- }
- }
-
- TerminatorKind::Yield {
- ref value,
- resume: _,
- drop: _,
- } => {
- self.consume_operand(ContextKind::Yield.new(loc), (value, span), flow_state);
- }
-
- TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
- // Returning from the function implicitly kills storage for all locals and statics.
- // Often, the storage will already have been killed by an explicit
- // StorageDead, but we don't always emit those (notably on unwind paths),
- // so this "extra check" serves as a kind of backup.
- let domain = flow_state.borrows.base_results.operator();
- let data = domain.borrows();
- flow_state.borrows.with_elems_outgoing(|borrows| {
- for i in borrows {
- let borrow = &data[i];
-
- if self.place_is_invalidated_at_exit(&borrow.place) {
- debug!("borrow conflicts at exit {:?}", borrow);
- let borrow_span = self.mir.source_info(borrow.location).span;
- // FIXME: should be talking about the region lifetime instead
- // of just a span here.
- let end_span = domain.opt_region_end_span(&borrow.region);
-
- self.report_borrowed_value_does_not_live_long_enough(
- ContextKind::StorageDead.new(loc),
- (&borrow.place, borrow_span),
- end_span,
- )
- }
- }
- });
- }
- TerminatorKind::Goto { target: _ } |
- TerminatorKind::Unreachable |
- TerminatorKind::FalseEdges { .. } => {
- // no data used, thus irrelevant to borrowck
- }
- }
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum MutateMode {
- JustWrite,
- WriteAndRead,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum Control {
- Continue,
- Break,
-}
-
-use self::ShallowOrDeep::{Deep, Shallow};
-use self::ReadOrWrite::{Read, Write};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum ArtificialField {
- Discriminant,
- ArrayLength,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum ShallowOrDeep {
- /// From the RFC: "A *shallow* access means that the immediate
- /// fields reached at LV are accessed, but references or pointers
- /// found within are not dereferenced. Right now, the only access
- /// that is shallow is an assignment like `x = ...;`, which would
- /// be a *shallow write* of `x`."
- Shallow(Option<ArtificialField>),
-
- /// From the RFC: "A *deep* access means that all data reachable
- /// through the given place may be invalidated or accesses by
- /// this action."
- Deep,
-}
-
-/// Kind of access to a value: read or write
-/// (For informational purposes only)
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum ReadOrWrite {
- /// From the RFC: "A *read* means that the existing data may be
- /// read, but will not be changed."
- Read(ReadKind),
-
- /// From the RFC: "A *write* means that the data may be mutated to
- /// new values or otherwise invalidated (for example, it could be
- /// de-initialized, as in a move operation).
- Write(WriteKind),
-}
-
-/// Kind of read access to a value
-/// (For informational purposes only)
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum ReadKind {
- Borrow(BorrowKind),
- Copy,
-}
-
-/// Kind of write access to a value
-/// (For informational purposes only)
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum WriteKind {
- StorageDeadOrDrop,
- MutableBorrow(BorrowKind),
- Mutate,
- Move,
-}
-
-/// When checking permissions for a place access, this flag is used to indicate that an immutable
-/// local place can be mutated.
-///
-/// FIXME: @nikomatsakis suggested that this flag could be removed with the following modifications:
-/// - Merge `check_access_permissions()` and `check_if_reassignment_to_immutable_state()`
-/// - Split `is_mutable()` into `is_assignable()` (can be directly assigned) and
-/// `is_declared_mutable()`
-/// - Take flow state into consideration in `is_assignable()` for local variables
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum LocalMutationIsAllowed {
- Yes,
- No,
-}
-
-#[derive(Copy, Clone)]
-enum InitializationRequiringAction {
- Update,
- Borrow,
- Use,
- Assignment,
-}
-
-impl InitializationRequiringAction {
- fn as_noun(self) -> &'static str {
- match self {
- InitializationRequiringAction::Update => "update",
- InitializationRequiringAction::Borrow => "borrow",
- InitializationRequiringAction::Use => "use",
- InitializationRequiringAction::Assignment => "assign",
- }
- }
-
- fn as_verb_in_past_tense(self) -> &'static str {
- match self {
- InitializationRequiringAction::Update => "updated",
- InitializationRequiringAction::Borrow => "borrowed",
- InitializationRequiringAction::Use => "used",
- InitializationRequiringAction::Assignment => "assigned",
- }
- }
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- /// Checks an access to the given place to see if it is allowed. Examines the set of borrows
- /// that are in scope, as well as which paths have been initialized, to ensure that (a) the
- /// place is initialized and (b) it is not borrowed in some way that would prevent this
- /// access.
- ///
- /// Returns true if an error is reported, false otherwise.
- fn access_place(
- &mut self,
- context: Context,
- place_span: (&Place<'tcx>, Span),
- kind: (ShallowOrDeep, ReadOrWrite),
- is_local_mutation_allowed: LocalMutationIsAllowed,
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- let (sd, rw) = kind;
-
- let storage_dead_or_drop_local = match (place_span.0, rw) {
- (&Place::Local(local), Write(WriteKind::StorageDeadOrDrop)) => Some(local),
- _ => None,
- };
-
- // Check if error has already been reported to stop duplicate reporting.
- if let Some(local) = storage_dead_or_drop_local {
- if self.storage_dead_or_drop_error_reported.contains(&local) {
- return;
- }
- }
-
- // Check permissions
- let mut error_reported =
- self.check_access_permissions(place_span, rw, is_local_mutation_allowed);
-
- self.each_borrow_involving_path(
- context,
- (sd, place_span.0),
- flow_state,
- |this, _index, borrow, common_prefix| match (rw, borrow.kind) {
- (Read(_), BorrowKind::Shared) => Control::Continue,
- (Read(kind), BorrowKind::Unique) | (Read(kind), BorrowKind::Mut) => {
- match kind {
- ReadKind::Copy => {
- error_reported = true;
- this.report_use_while_mutably_borrowed(context, place_span, borrow)
- }
- ReadKind::Borrow(bk) => {
- let end_issued_loan_span = flow_state
- .borrows
- .base_results
- .operator()
- .opt_region_end_span(&borrow.region);
- error_reported = true;
- this.report_conflicting_borrow(
- context,
- common_prefix,
- place_span,
- bk,
- &borrow,
- end_issued_loan_span,
- )
- }
- }
- Control::Break
- }
- (Write(kind), _) => {
- match kind {
- WriteKind::MutableBorrow(bk) => {
- let end_issued_loan_span = flow_state
- .borrows
- .base_results
- .operator()
- .opt_region_end_span(&borrow.region);
- error_reported = true;
- this.report_conflicting_borrow(
- context,
- common_prefix,
- place_span,
- bk,
- &borrow,
- end_issued_loan_span,
- )
- }
- WriteKind::StorageDeadOrDrop => {
- let end_span = flow_state
- .borrows
- .base_results
- .operator()
- .opt_region_end_span(&borrow.region);
- error_reported = true;
- this.report_borrowed_value_does_not_live_long_enough(
- context,
- place_span,
- end_span,
- )
- }
- WriteKind::Mutate => {
- error_reported = true;
- this.report_illegal_mutation_of_borrowed(context, place_span, borrow)
- }
- WriteKind::Move => {
- error_reported = true;
- this.report_move_out_while_borrowed(context, place_span, &borrow)
- }
- }
- Control::Break
- }
- },
- );
-
- if error_reported {
- if let Some(local) = storage_dead_or_drop_local {
- self.storage_dead_or_drop_error_reported.insert(local);
- }
- }
- }
-
- fn mutate_place(
- &mut self,
- context: Context,
- place_span: (&Place<'tcx>, Span),
- kind: ShallowOrDeep,
- mode: MutateMode,
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- // Write of P[i] or *P, or WriteAndRead of any P, requires P init'd.
- match mode {
- MutateMode::WriteAndRead => {
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Update,
- place_span,
- flow_state,
- );
- }
- MutateMode::JustWrite => {
- self.check_if_assigned_path_is_moved(context, place_span, flow_state);
- }
- }
-
- self.access_place(
- context,
- place_span,
- (kind, Write(WriteKind::Mutate)),
- LocalMutationIsAllowed::Yes,
- flow_state,
- );
-
- // check for reassignments to immutable local variables
- self.check_if_reassignment_to_immutable_state(context, place_span, flow_state);
- }
-
- fn consume_rvalue(
- &mut self,
- context: Context,
- (rvalue, span): (&Rvalue<'tcx>, Span),
- _location: Location,
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- match *rvalue {
- Rvalue::Ref(_ /*rgn*/, bk, ref place) => {
- let access_kind = match bk {
- BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
- BorrowKind::Unique | BorrowKind::Mut => {
- (Deep, Write(WriteKind::MutableBorrow(bk)))
- }
- };
- self.access_place(
- context,
- (place, span),
- access_kind,
- LocalMutationIsAllowed::No,
- flow_state,
- );
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Borrow,
- (place, span),
- flow_state,
- );
- }
-
- Rvalue::Use(ref operand) |
- Rvalue::Repeat(ref operand, _) |
- Rvalue::UnaryOp(_ /*un_op*/, ref operand) |
- Rvalue::Cast(_ /*cast_kind*/, ref operand, _ /*ty*/) => {
- self.consume_operand(context, (operand, span), flow_state)
- }
-
- Rvalue::Len(ref place) | Rvalue::Discriminant(ref place) => {
- let af = match *rvalue {
- Rvalue::Len(..) => ArtificialField::ArrayLength,
- Rvalue::Discriminant(..) => ArtificialField::Discriminant,
- _ => unreachable!(),
- };
- self.access_place(
- context,
- (place, span),
- (Shallow(Some(af)), Read(ReadKind::Copy)),
- LocalMutationIsAllowed::No,
- flow_state,
- );
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Use,
- (place, span),
- flow_state,
- );
- }
-
- Rvalue::BinaryOp(_bin_op, ref operand1, ref operand2) |
- Rvalue::CheckedBinaryOp(_bin_op, ref operand1, ref operand2) => {
- self.consume_operand(context, (operand1, span), flow_state);
- self.consume_operand(context, (operand2, span), flow_state);
- }
-
- Rvalue::NullaryOp(_op, _ty) => {
- // nullary ops take no dynamic input; no borrowck effect.
- //
- // FIXME: is above actually true? Do we want to track
- // the fact that uninitialized data can be created via
- // `NullOp::Box`?
- }
-
- Rvalue::Aggregate(ref _aggregate_kind, ref operands) => for operand in operands {
- self.consume_operand(context, (operand, span), flow_state);
- },
- }
- }
-
- fn consume_operand(
- &mut self,
- context: Context,
- (operand, span): (&Operand<'tcx>, Span),
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- match *operand {
- Operand::Copy(ref place) => {
- // copy of place: check if this is "copy of frozen path"
- // (FIXME: see check_loans.rs)
- self.access_place(
- context,
- (place, span),
- (Deep, Read(ReadKind::Copy)),
- LocalMutationIsAllowed::No,
- flow_state,
- );
-
- // Finally, check if path was already moved.
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Use,
- (place, span),
- flow_state,
- );
- }
- Operand::Move(ref place) => {
- // move of place: check if this is move of already borrowed path
- self.access_place(
- context,
- (place, span),
- (Deep, Write(WriteKind::Move)),
- LocalMutationIsAllowed::Yes,
- flow_state,
- );
-
- // Finally, check if path was already moved.
- self.check_if_path_is_moved(
- context,
- InitializationRequiringAction::Use,
- (place, span),
- flow_state,
- );
- }
- Operand::Constant(_) => {}
- }
- }
-
- /// Returns whether a borrow of this place is invalidated when the function
- /// exits
- fn place_is_invalidated_at_exit(&self, place: &Place<'tcx>) -> bool {
- debug!("place_is_invalidated_at_exit({:?})", place);
- let root_place = self.prefixes(place, PrefixSet::All).last().unwrap();
-
- // FIXME(nll-rfc#40): do more precise destructor tracking here. For now
- // we just know that all locals are dropped at function exit (otherwise
- // we'll have a memory leak) and assume that all statics have a destructor.
- let (might_be_alive, will_be_dropped) = match root_place {
- Place::Static(statik) => {
- // Thread-locals might be dropped after the function exits, but
- // "true" statics will never be.
- let is_thread_local = self.tcx
- .get_attrs(statik.def_id)
- .iter()
- .any(|attr| attr.check_name("thread_local"));
-
- (true, is_thread_local)
- }
- Place::Local(_) => {
- // Locals are always dropped at function exit, and if they
- // have a destructor it would've been called already.
- (false, true)
- }
- Place::Projection(..) => {
- bug!("root of {:?} is a projection ({:?})?", place, root_place)
- }
- };
-
- if !will_be_dropped {
- debug!(
- "place_is_invalidated_at_exit({:?}) - won't be dropped",
- place
- );
- return false;
- }
-
- // FIXME: replace this with a proper borrow_conflicts_with_place when
- // that is merged.
- let prefix_set = if might_be_alive {
- PrefixSet::Supporting
- } else {
- PrefixSet::Shallow
- };
-
- self.prefixes(place, prefix_set)
- .any(|prefix| prefix == root_place)
- }
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- fn check_if_reassignment_to_immutable_state(
- &mut self,
- context: Context,
- (place, span): (&Place<'tcx>, Span),
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- let move_data = self.move_data;
-
- // determine if this path has a non-mut owner (and thus needs checking).
- if let Ok(()) = self.is_mutable(place, LocalMutationIsAllowed::No) {
- return;
- }
-
- if let Err(_) = self.is_mutable(place, LocalMutationIsAllowed::Yes) {
- return;
- }
-
- match self.move_path_closest_to(place) {
- Ok(mpi) => for ii in &move_data.init_path_map[mpi] {
- if flow_state.ever_inits.curr_state.contains(ii) {
- let first_assign_span = self.move_data.inits[*ii].span;
- self.report_illegal_reassignment(context, (place, span), first_assign_span);
- break;
- }
- },
- Err(NoMovePathFound::ReachedStatic) => {
- let item_msg = match self.describe_place(place) {
- Some(name) => format!("immutable static item `{}`", name),
- None => "immutable static item".to_owned(),
- };
- self.tcx.sess.delay_span_bug(
- span,
- &format!(
- "cannot assign to {}, should have been caught by \
- `check_access_permissions()`",
- item_msg
- ),
- );
- }
- }
- }
-
- fn check_if_path_is_moved(
- &mut self,
- context: Context,
- desired_action: InitializationRequiringAction,
- place_span: (&Place<'tcx>, Span),
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- // FIXME: analogous code in check_loans first maps `place` to
- // its base_path ... but is that what we want here?
- let place = self.base_path(place_span.0);
-
- let maybe_uninits = &flow_state.uninits;
- let curr_move_outs = &flow_state.move_outs.curr_state;
-
- // Bad scenarios:
- //
- // 1. Move of `a.b.c`, use of `a.b.c`
- // 2. Move of `a.b.c`, use of `a.b.c.d` (without first reinitializing `a.b.c.d`)
- // 3. Move of `a.b.c`, use of `a` or `a.b`
- // 4. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`; note that with
- // partial initialization support, one might have `a.x`
- // initialized but not `a.b`.
- //
- // OK scenarios:
- //
- // 5. Move of `a.b.c`, use of `a.b.d`
- // 6. Uninitialized `a.x`, initialized `a.b`, use of `a.b`
- // 7. Copied `(a.b: &_)`, use of `*(a.b).c`; note that `a.b`
- // must have been initialized for the use to be sound.
- // 8. Move of `a.b.c` then reinit of `a.b.c.d`, use of `a.b.c.d`
-
- // The dataflow tracks shallow prefixes distinctly (that is,
- // field-accesses on P distinctly from P itself), in order to
- // track substructure initialization separately from the whole
- // structure.
- //
- // E.g., when looking at (*a.b.c).d, if the closest prefix for
- // which we have a MovePath is `a.b`, then that means that the
- // initialization state of `a.b` is all we need to inspect to
- // know if `a.b.c` is valid (and from that we infer that the
- // dereference and `.d` access is also valid, since we assume
- // `a.b.c` is assigned a reference to a initialized and
- // well-formed record structure.)
-
- // Therefore, if we seek out the *closest* prefix for which we
- // have a MovePath, that should capture the initialization
- // state for the place scenario.
- //
- // This code covers scenarios 1, 2, and 4.
-
- debug!("check_if_path_is_moved part1 place: {:?}", place);
- match self.move_path_closest_to(place) {
- Ok(mpi) => {
- if maybe_uninits.curr_state.contains(&mpi) {
- self.report_use_of_moved_or_uninitialized(
- context,
- desired_action,
- place_span,
- mpi,
- curr_move_outs,
- );
- return; // don't bother finding other problems.
- }
- }
- Err(NoMovePathFound::ReachedStatic) => {
- // Okay: we do not build MoveData for static variables
- } // Only query longest prefix with a MovePath, not further
- // ancestors; dataflow recurs on children when parents
- // move (to support partial (re)inits).
- //
- // (I.e. querying parents breaks scenario 8; but may want
- // to do such a query based on partial-init feature-gate.)
- }
-
- // A move of any shallow suffix of `place` also interferes
- // with an attempt to use `place`. This is scenario 3 above.
- //
- // (Distinct from handling of scenarios 1+2+4 above because
- // `place` does not interfere with suffixes of its prefixes,
- // e.g. `a.b.c` does not interfere with `a.b.d`)
-
- debug!("check_if_path_is_moved part2 place: {:?}", place);
- if let Some(mpi) = self.move_path_for_place(place) {
- if let Some(child_mpi) = maybe_uninits.has_any_child_of(mpi) {
- self.report_use_of_moved_or_uninitialized(
- context,
- desired_action,
- place_span,
- child_mpi,
- curr_move_outs,
- );
- return; // don't bother finding other problems.
- }
- }
- }
-
- /// Currently MoveData does not store entries for all places in
- /// the input MIR. For example it will currently filter out
- /// places that are Copy; thus we do not track places of shared
- /// reference type. This routine will walk up a place along its
- /// prefixes, searching for a foundational place that *is*
- /// tracked in the MoveData.
- ///
- /// An Err result includes a tag indicated why the search failed.
- /// Currenly this can only occur if the place is built off of a
- /// static variable, as we do not track those in the MoveData.
- fn move_path_closest_to(
- &mut self,
- place: &Place<'tcx>,
- ) -> Result<MovePathIndex, NoMovePathFound> {
- let mut last_prefix = place;
- for prefix in self.prefixes(place, PrefixSet::All) {
- if let Some(mpi) = self.move_path_for_place(prefix) {
- return Ok(mpi);
- }
- last_prefix = prefix;
- }
- match *last_prefix {
- Place::Local(_) => panic!("should have move path for every Local"),
- Place::Projection(_) => panic!("PrefixSet::All meant dont stop for Projection"),
- Place::Static(_) => return Err(NoMovePathFound::ReachedStatic),
- }
- }
-
- fn move_path_for_place(&mut self, place: &Place<'tcx>) -> Option<MovePathIndex> {
- // If returns None, then there is no move path corresponding
- // to a direct owner of `place` (which means there is nothing
- // that borrowck tracks for its analysis).
-
- match self.move_data.rev_lookup.find(place) {
- LookupResult::Parent(_) => None,
- LookupResult::Exact(mpi) => Some(mpi),
- }
- }
-
- fn check_if_assigned_path_is_moved(
- &mut self,
- context: Context,
- (place, span): (&Place<'tcx>, Span),
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- ) {
- // recur down place; dispatch to check_if_path_is_moved when necessary
- let mut place = place;
- loop {
- match *place {
- Place::Local(_) | Place::Static(_) => {
- // assigning to `x` does not require `x` be initialized.
- break;
- }
- Place::Projection(ref proj) => {
- let Projection { ref base, ref elem } = **proj;
- match *elem {
- ProjectionElem::Deref |
- // assigning to *P requires `P` initialized.
- ProjectionElem::Index(_/*operand*/) |
- ProjectionElem::ConstantIndex { .. } |
- // assigning to P[i] requires `P` initialized.
- ProjectionElem::Downcast(_/*adt_def*/, _/*variant_idx*/) =>
- // assigning to (P->variant) is okay if assigning to `P` is okay
- //
- // FIXME: is this true even if P is a adt with a dtor?
- { }
-
- ProjectionElem::Subslice { .. } => {
- panic!("we dont allow assignments to subslices, context: {:?}",
- context);
- }
-
- ProjectionElem::Field(..) => {
- // if type of `P` has a dtor, then
- // assigning to `P.f` requires `P` itself
- // be already initialized
- let tcx = self.tcx;
- match base.ty(self.mir, tcx).to_ty(tcx).sty {
- ty::TyAdt(def, _) if def.has_dtor(tcx) => {
-
- // FIXME: analogous code in
- // check_loans.rs first maps
- // `base` to its base_path.
-
- self.check_if_path_is_moved(
- context, InitializationRequiringAction::Assignment,
- (base, span), flow_state);
-
- // (base initialized; no need to
- // recur further)
- break;
- }
- _ => {}
- }
- }
- }
-
- place = base;
- continue;
- }
- }
- }
- }
-
- /// Check the permissions for the given place and read or write kind
- ///
- /// Returns true if an error is reported, false otherwise.
- fn check_access_permissions(
- &self,
- (place, span): (&Place<'tcx>, Span),
- kind: ReadOrWrite,
- is_local_mutation_allowed: LocalMutationIsAllowed,
- ) -> bool {
- debug!(
- "check_access_permissions({:?}, {:?}, {:?})",
- place,
- kind,
- is_local_mutation_allowed
- );
- let mut error_reported = false;
- match kind {
- Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => {
- if let Err(_place_err) = self.is_unique(place) {
- span_bug!(span, "&unique borrow for {:?} should not fail", place);
- }
- }
- Write(WriteKind::MutableBorrow(BorrowKind::Mut)) => if let Err(place_err) =
- self.is_mutable(place, is_local_mutation_allowed)
- {
- error_reported = true;
-
- let item_msg = match self.describe_place(place) {
- Some(name) => format!("immutable item `{}`", name),
- None => "immutable item".to_owned(),
- };
-
- let mut err = self.tcx
- .cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
- err.span_label(span, "cannot borrow as mutable");
-
- if place != place_err {
- if let Some(name) = self.describe_place(place_err) {
- err.note(&format!("Value not mutable causing this error: `{}`", name));
- }
- }
-
- err.emit();
- },
- Write(WriteKind::Mutate) => {
- if let Err(place_err) = self.is_mutable(place, is_local_mutation_allowed) {
- error_reported = true;
-
- let item_msg = match self.describe_place(place) {
- Some(name) => format!("immutable item `{}`", name),
- None => "immutable item".to_owned(),
- };
-
- let mut err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
- err.span_label(span, "cannot mutate");
-
- if place != place_err {
- if let Some(name) = self.describe_place(place_err) {
- err.note(&format!("Value not mutable causing this error: `{}`", name));
- }
- }
-
- err.emit();
- }
- }
- Write(WriteKind::Move) |
- Write(WriteKind::StorageDeadOrDrop) |
- Write(WriteKind::MutableBorrow(BorrowKind::Shared)) => {
- if let Err(_place_err) = self.is_mutable(place, is_local_mutation_allowed) {
- self.tcx.sess.delay_span_bug(
- span,
- &format!(
- "Accessing `{:?}` with the kind `{:?}` shouldn't be possible",
- place,
- kind
- ),
- );
- }
- }
- Read(ReadKind::Borrow(BorrowKind::Unique)) |
- Read(ReadKind::Borrow(BorrowKind::Mut)) |
- Read(ReadKind::Borrow(BorrowKind::Shared)) |
- Read(ReadKind::Copy) => {} // Access authorized
- }
-
- error_reported
- }
-
- /// Can this value be written or borrowed mutably
- fn is_mutable<'d>(
- &self,
- place: &'d Place<'tcx>,
- is_local_mutation_allowed: LocalMutationIsAllowed,
- ) -> Result<(), &'d Place<'tcx>> {
- match *place {
- Place::Local(local) => {
- let local = &self.mir.local_decls[local];
- match local.mutability {
- Mutability::Not => match is_local_mutation_allowed {
- LocalMutationIsAllowed::Yes => Ok(()),
- LocalMutationIsAllowed::No => Err(place),
- },
- Mutability::Mut => Ok(()),
- }
- }
- Place::Static(ref static_) => if !self.tcx.is_static_mut(static_.def_id) {
- Err(place)
- } else {
- Ok(())
- },
- Place::Projection(ref proj) => {
- match proj.elem {
- ProjectionElem::Deref => {
- let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
-
- // Check the kind of deref to decide
- match base_ty.sty {
- ty::TyRef(_, tnm) => {
- match tnm.mutbl {
- // Shared borrowed data is never mutable
- hir::MutImmutable => Err(place),
- // Mutably borrowed data is mutable, but only if we have a
- // unique path to the `&mut`
- hir::MutMutable => {
- if self.is_upvar_field_projection(&proj.base).is_some() {
- self.is_mutable(&proj.base, is_local_mutation_allowed)
- } else {
- self.is_unique(&proj.base)
- }
- }
- }
- }
- ty::TyRawPtr(tnm) => {
- match tnm.mutbl {
- // `*const` raw pointers are not mutable
- hir::MutImmutable => Err(place),
- // `*mut` raw pointers are always mutable, regardless of context
- // The users have to check by themselve.
- hir::MutMutable => Ok(()),
- }
- }
- // `Box<T>` owns its content, so mutable if its location is mutable
- _ if base_ty.is_box() => {
- self.is_mutable(&proj.base, LocalMutationIsAllowed::No)
- }
- // Deref should only be for reference, pointers or boxes
- _ => bug!("Deref of unexpected type: {:?}", base_ty),
- }
- }
- // All other projections are owned by their base path, so mutable if
- // base path is mutable
- ProjectionElem::Field(..) |
- ProjectionElem::Index(..) |
- ProjectionElem::ConstantIndex { .. } |
- ProjectionElem::Subslice { .. } |
- ProjectionElem::Downcast(..) => {
- let field_projection = self.is_upvar_field_projection(place);
-
- if let Some(field) = field_projection {
- let decl = &self.mir.upvar_decls[field.index()];
-
- return match decl.mutability {
- Mutability::Mut => self.is_unique(&proj.base),
- Mutability::Not => Err(place),
- };
- }
-
- self.is_mutable(&proj.base, LocalMutationIsAllowed::No)
- }
- }
- }
- }
- }
-
- /// Does this place have a unique path
- fn is_unique<'d>(&self, place: &'d Place<'tcx>) -> Result<(), &'d Place<'tcx>> {
- match *place {
- Place::Local(..) => {
- // Local variables are unique
- Ok(())
- }
- Place::Static(..) => {
- // Static variables are not
- Err(place)
- }
- Place::Projection(ref proj) => {
- match proj.elem {
- ProjectionElem::Deref => {
- let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
-
- // `Box<T>` referent is unique if box is a unique spot
- if base_ty.is_box() {
- return self.is_unique(&proj.base);
- }
-
- // Otherwise we check the kind of deref to decide
- match base_ty.sty {
- ty::TyRef(_, tnm) => {
- match tnm.mutbl {
- // place represent an aliased location
- hir::MutImmutable => Err(place),
- // `&mut T` is as unique as the context in which it is found
- hir::MutMutable => self.is_unique(&proj.base),
- }
- }
- ty::TyRawPtr(tnm) => {
- match tnm.mutbl {
- // `*mut` can be aliased, but we leave it to user
- hir::MutMutable => Ok(()),
- // `*const` is treated the same as `*mut`
- hir::MutImmutable => Ok(()),
- }
- }
- // Deref should only be for reference, pointers or boxes
- _ => bug!("Deref of unexpected type: {:?}", base_ty),
- }
- }
- // Other projections are unique if the base is unique
- ProjectionElem::Field(..) |
- ProjectionElem::Index(..) |
- ProjectionElem::ConstantIndex { .. } |
- ProjectionElem::Subslice { .. } |
- ProjectionElem::Downcast(..) => self.is_unique(&proj.base),
- }
- }
- }
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum NoMovePathFound {
- ReachedStatic,
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- fn each_borrow_involving_path<F>(
- &mut self,
- _context: Context,
- access_place: (ShallowOrDeep, &Place<'tcx>),
- flow_state: &InProgress<'cx, 'gcx, 'tcx>,
- mut op: F,
- ) where
- F: FnMut(&mut Self, BorrowIndex, &BorrowData<'tcx>, &Place<'tcx>) -> Control,
- {
- let (access, place) = access_place;
-
- // FIXME: analogous code in check_loans first maps `place` to
- // its base_path.
-
- let domain = flow_state.borrows.base_results.operator();
- let data = domain.borrows();
-
- // check for loan restricting path P being used. Accounts for
- // borrows of P, P.a.b, etc.
- 'next_borrow: for i in flow_state.borrows.elems_incoming() {
- let borrowed = &data[i];
-
- // Is `place` (or a prefix of it) already borrowed? If
- // so, that's relevant.
- //
- // FIXME: Differs from AST-borrowck; includes drive-by fix
- // to #38899. Will probably need back-compat mode flag.
- for accessed_prefix in self.prefixes(place, PrefixSet::All) {
- if *accessed_prefix == borrowed.place {
- // FIXME: pass in enum describing case we are in?
- let ctrl = op(self, i, borrowed, accessed_prefix);
- if ctrl == Control::Break {
- return;
- }
- }
- }
-
- // Is `place` a prefix (modulo access type) of the
- // `borrowed.place`? If so, that's relevant.
-
- let prefix_kind = match access {
- Shallow(Some(ArtificialField::Discriminant)) |
- Shallow(Some(ArtificialField::ArrayLength)) => {
- // The discriminant and array length are like
- // additional fields on the type; they do not
- // overlap any existing data there. Furthermore,
- // they cannot actually be a prefix of any
- // borrowed place (at least in MIR as it is
- // currently.)
- continue 'next_borrow;
- }
- Shallow(None) => PrefixSet::Shallow,
- Deep => PrefixSet::Supporting,
- };
-
- for borrowed_prefix in self.prefixes(&borrowed.place, prefix_kind) {
- if borrowed_prefix == place {
- // FIXME: pass in enum describing case we are in?
- let ctrl = op(self, i, borrowed, borrowed_prefix);
- if ctrl == Control::Break {
- return;
- }
- }
- }
- }
- }
-}
-
-use self::prefixes::PrefixSet;
-
-/// From the NLL RFC: "The deep [aka 'supporting'] prefixes for an
-/// place are formed by stripping away fields and derefs, except that
-/// we stop when we reach the deref of a shared reference. [...] "
-///
-/// "Shallow prefixes are found by stripping away fields, but stop at
-/// any dereference. So: writing a path like `a` is illegal if `a.b`
-/// is borrowed. But: writing `a` is legal if `*a` is borrowed,
-/// whether or not `a` is a shared or mutable reference. [...] "
-mod prefixes {
- use super::MirBorrowckCtxt;
-
- use rustc::hir;
- use rustc::ty::{self, TyCtxt};
- use rustc::mir::{Mir, Place, ProjectionElem};
-
- pub trait IsPrefixOf<'tcx> {
- fn is_prefix_of(&self, other: &Place<'tcx>) -> bool;
- }
-
- impl<'tcx> IsPrefixOf<'tcx> for Place<'tcx> {
- fn is_prefix_of(&self, other: &Place<'tcx>) -> bool {
- let mut cursor = other;
- loop {
- if self == cursor {
- return true;
- }
-
- match *cursor {
- Place::Local(_) | Place::Static(_) => return false,
- Place::Projection(ref proj) => {
- cursor = &proj.base;
- }
- }
- }
- }
- }
-
-
- pub(super) struct Prefixes<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
- mir: &'cx Mir<'tcx>,
- tcx: TyCtxt<'cx, 'gcx, 'tcx>,
- kind: PrefixSet,
- next: Option<&'cx Place<'tcx>>,
- }
-
- #[derive(Copy, Clone, PartialEq, Eq, Debug)]
- pub(super) enum PrefixSet {
- /// Doesn't stop until it returns the base case (a Local or
- /// Static prefix).
- All,
- /// Stops at any dereference.
- Shallow,
- /// Stops at the deref of a shared reference.
- Supporting,
- }
-
- impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- /// Returns an iterator over the prefixes of `place`
- /// (inclusive) from longest to smallest, potentially
- /// terminating the iteration early based on `kind`.
- pub(super) fn prefixes(
- &self,
- place: &'cx Place<'tcx>,
- kind: PrefixSet,
- ) -> Prefixes<'cx, 'gcx, 'tcx> {
- Prefixes {
- next: Some(place),
- kind,
- mir: self.mir,
- tcx: self.tcx,
- }
- }
- }
-
- impl<'cx, 'gcx, 'tcx> Iterator for Prefixes<'cx, 'gcx, 'tcx> {
- type Item = &'cx Place<'tcx>;
- fn next(&mut self) -> Option<Self::Item> {
- let mut cursor = match self.next {
- None => return None,
- Some(place) => place,
- };
-
- // Post-processing `place`: Enqueue any remaining
- // work. Also, `place` may not be a prefix itself, but
- // may hold one further down (e.g. we never return
- // downcasts here, but may return a base of a downcast).
-
- 'cursor: loop {
- let proj = match *cursor {
- Place::Local(_) | // search yielded this leaf
- Place::Static(_) => {
- self.next = None;
- return Some(cursor);
- }
-
- Place::Projection(ref proj) => proj,
- };
-
- match proj.elem {
- ProjectionElem::Field(_ /*field*/, _ /*ty*/) => {
- // FIXME: add union handling
- self.next = Some(&proj.base);
- return Some(cursor);
- }
- ProjectionElem::Downcast(..) |
- ProjectionElem::Subslice { .. } |
- ProjectionElem::ConstantIndex { .. } |
- ProjectionElem::Index(_) => {
- cursor = &proj.base;
- continue 'cursor;
- }
- ProjectionElem::Deref => {
- // (handled below)
- }
- }
-
- assert_eq!(proj.elem, ProjectionElem::Deref);
-
- match self.kind {
- PrefixSet::Shallow => {
- // shallow prefixes are found by stripping away
- // fields, but stop at *any* dereference.
- // So we can just stop the traversal now.
- self.next = None;
- return Some(cursor);
- }
- PrefixSet::All => {
- // all prefixes: just blindly enqueue the base
- // of the projection
- self.next = Some(&proj.base);
- return Some(cursor);
- }
- PrefixSet::Supporting => {
- // fall through!
- }
- }
-
- assert_eq!(self.kind, PrefixSet::Supporting);
- // supporting prefixes: strip away fields and
- // derefs, except we stop at the deref of a shared
- // reference.
-
- let ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
- match ty.sty {
- ty::TyRawPtr(_) |
- ty::TyRef(
- _, /*rgn*/
- ty::TypeAndMut {
- ty: _,
- mutbl: hir::MutImmutable,
- },
- ) => {
- // don't continue traversing over derefs of raw pointers or shared borrows.
- self.next = None;
- return Some(cursor);
- }
-
- ty::TyRef(
- _, /*rgn*/
- ty::TypeAndMut {
- ty: _,
- mutbl: hir::MutMutable,
- },
- ) => {
- self.next = Some(&proj.base);
- return Some(cursor);
- }
-
- ty::TyAdt(..) if ty.is_box() => {
- self.next = Some(&proj.base);
- return Some(cursor);
- }
-
- _ => panic!("unknown type fed to Projection Deref."),
- }
- }
- }
- }
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- fn report_use_of_moved_or_uninitialized(
- &mut self,
- _context: Context,
- desired_action: InitializationRequiringAction,
- (place, span): (&Place<'tcx>, Span),
- mpi: MovePathIndex,
- curr_move_out: &IdxSetBuf<MoveOutIndex>,
- ) {
- let mois = self.move_data.path_map[mpi]
- .iter()
- .filter(|moi| curr_move_out.contains(moi))
- .collect::<Vec<_>>();
-
- if mois.is_empty() {
- let item_msg = match self.describe_place(place) {
- Some(name) => format!("`{}`", name),
- None => "value".to_owned(),
- };
- self.tcx
- .cannot_act_on_uninitialized_variable(
- span,
- desired_action.as_noun(),
- &self.describe_place(place).unwrap_or("_".to_owned()),
- Origin::Mir,
- )
- .span_label(span, format!("use of possibly uninitialized {}", item_msg))
- .emit();
- } else {
- let msg = ""; //FIXME: add "partially " or "collaterally "
-
- let mut err = self.tcx.cannot_act_on_moved_value(
- span,
- desired_action.as_noun(),
- msg,
- &self.describe_place(place).unwrap_or("_".to_owned()),
- Origin::Mir,
- );
-
- err.span_label(
- span,
- format!(
- "value {} here after move",
- desired_action.as_verb_in_past_tense()
- ),
- );
- for moi in mois {
- let move_msg = ""; //FIXME: add " (into closure)"
- let move_span = self.mir.source_info(self.move_data.moves[*moi].source).span;
- if span == move_span {
- err.span_label(
- span,
- format!("value moved{} here in previous iteration of loop", move_msg),
- );
- } else {
- err.span_label(move_span, format!("value moved{} here", move_msg));
- };
- }
- //FIXME: add note for closure
- err.emit();
- }
- }
-
- fn report_move_out_while_borrowed(
- &mut self,
- _context: Context,
- (place, span): (&Place<'tcx>, Span),
- borrow: &BorrowData<'tcx>,
- ) {
- let value_msg = match self.describe_place(place) {
- Some(name) => format!("`{}`", name),
- None => "value".to_owned(),
- };
- let borrow_msg = match self.describe_place(&borrow.place) {
- Some(name) => format!("`{}`", name),
- None => "value".to_owned(),
- };
- self.tcx
- .cannot_move_when_borrowed(
- span,
- &self.describe_place(place).unwrap_or("_".to_owned()),
- Origin::Mir,
- )
- .span_label(
- self.retrieve_borrow_span(borrow),
- format!("borrow of {} occurs here", borrow_msg),
- )
- .span_label(span, format!("move out of {} occurs here", value_msg))
- .emit();
- }
-
- fn report_use_while_mutably_borrowed(
- &mut self,
- _context: Context,
- (place, span): (&Place<'tcx>, Span),
- borrow: &BorrowData<'tcx>,
- ) {
- let mut err = self.tcx.cannot_use_when_mutably_borrowed(
- span,
- &self.describe_place(place).unwrap_or("_".to_owned()),
- self.retrieve_borrow_span(borrow),
- &self.describe_place(&borrow.place).unwrap_or("_".to_owned()),
- Origin::Mir,
- );
-
- err.emit();
- }
-
- /// Finds the span of arguments of a closure (within `maybe_closure_span`) and its usage of
- /// the local assigned at `location`.
- /// This is done by searching in statements succeeding `location`
- /// and originating from `maybe_closure_span`.
- fn find_closure_span(
- &self,
- maybe_closure_span: Span,
- location: Location,
- ) -> Option<(Span, Span)> {
- use rustc::hir::ExprClosure;
- use rustc::mir::AggregateKind;
-
- let local = if let StatementKind::Assign(Place::Local(local), _) =
- self.mir[location.block].statements[location.statement_index].kind
- {
- local
- } else {
- return None;
- };
-
- for stmt in &self.mir[location.block].statements[location.statement_index + 1..] {
- if maybe_closure_span != stmt.source_info.span {
- break;
- }
-
- if let StatementKind::Assign(_, Rvalue::Aggregate(ref kind, ref places)) = stmt.kind {
- if let AggregateKind::Closure(def_id, _) = **kind {
- debug!("find_closure_span: found closure {:?}", places);
-
- return if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
- let args_span = if let ExprClosure(_, _, _, span, _) =
- self.tcx.hir.expect_expr(node_id).node
- {
- span
- } else {
- return None;
- };
-
- self.tcx
- .with_freevars(node_id, |freevars| {
- for (v, place) in freevars.iter().zip(places) {
- match *place {
- Operand::Copy(Place::Local(l)) |
- Operand::Move(Place::Local(l)) if local == l =>
- {
- debug!(
- "find_closure_span: found captured local {:?}",
- l
- );
- return Some(v.span);
- }
- _ => {}
- }
- }
- None
- })
- .map(|var_span| (args_span, var_span))
- } else {
- None
- };
- }
- }
- }
-
- None
- }
-
- fn report_conflicting_borrow(
- &mut self,
- context: Context,
- common_prefix: &Place<'tcx>,
- (place, span): (&Place<'tcx>, Span),
- gen_borrow_kind: BorrowKind,
- issued_borrow: &BorrowData,
- end_issued_loan_span: Option<Span>,
- ) {
- use self::prefixes::IsPrefixOf;
-
- assert!(common_prefix.is_prefix_of(place));
- assert!(common_prefix.is_prefix_of(&issued_borrow.place));
-
- let issued_span = self.retrieve_borrow_span(issued_borrow);
-
- let new_closure_span = self.find_closure_span(span, context.loc);
- let span = new_closure_span.map(|(args, _)| args).unwrap_or(span);
- let old_closure_span = self.find_closure_span(issued_span, issued_borrow.location);
- let issued_span = old_closure_span
- .map(|(args, _)| args)
- .unwrap_or(issued_span);
-
- let desc_place = self.describe_place(place).unwrap_or("_".to_owned());
-
- // FIXME: supply non-"" `opt_via` when appropriate
- let mut err = match (
- gen_borrow_kind,
- "immutable",
- "mutable",
- issued_borrow.kind,
- "immutable",
- "mutable",
- ) {
- (BorrowKind::Shared, lft, _, BorrowKind::Mut, _, rgt) |
- (BorrowKind::Mut, _, lft, BorrowKind::Shared, rgt, _) => self.tcx
- .cannot_reborrow_already_borrowed(
- span,
- &desc_place,
- "",
- lft,
- issued_span,
- "it",
- rgt,
- "",
- end_issued_loan_span,
- Origin::Mir,
- ),
-
- (BorrowKind::Mut, _, _, BorrowKind::Mut, _, _) => self.tcx
- .cannot_mutably_borrow_multiply(
- span,
- &desc_place,
- "",
- issued_span,
- "",
- end_issued_loan_span,
- Origin::Mir,
- ),
-
- (BorrowKind::Unique, _, _, BorrowKind::Unique, _, _) => self.tcx
- .cannot_uniquely_borrow_by_two_closures(
- span,
- &desc_place,
- issued_span,
- end_issued_loan_span,
- Origin::Mir,
- ),
-
- (BorrowKind::Unique, _, _, _, _, _) => self.tcx.cannot_uniquely_borrow_by_one_closure(
- span,
- &desc_place,
- "",
- issued_span,
- "it",
- "",
- end_issued_loan_span,
- Origin::Mir,
- ),
-
- (_, _, _, BorrowKind::Unique, _, _) => self.tcx
- .cannot_reborrow_already_uniquely_borrowed(
- span,
- &desc_place,
- "it",
- "",
- issued_span,
- "",
- end_issued_loan_span,
- Origin::Mir,
- ),
-
- (BorrowKind::Shared, _, _, BorrowKind::Shared, _, _) => unreachable!(),
- };
-
- if let Some((_, var_span)) = old_closure_span {
- err.span_label(
- var_span,
- format!(
- "previous borrow occurs due to use of `{}` in closure",
- desc_place
- ),
- );
- }
-
- if let Some((_, var_span)) = new_closure_span {
- err.span_label(
- var_span,
- format!("borrow occurs due to use of `{}` in closure", desc_place),
- );
- }
-
- err.emit();
- }
-
- fn report_borrowed_value_does_not_live_long_enough(
- &mut self,
- _: Context,
- (place, span): (&Place<'tcx>, Span),
- end_span: Option<Span>,
- ) {
- let root_place = self.prefixes(place, PrefixSet::All).last().unwrap();
- let proper_span = match *root_place {
- Place::Local(local) => self.mir.local_decls[local].source_info.span,
- _ => span,
- };
- let mut err = self.tcx
- .path_does_not_live_long_enough(span, "borrowed value", Origin::Mir);
- err.span_label(proper_span, "temporary value created here");
- err.span_label(span, "temporary value dropped here while still borrowed");
- err.note("consider using a `let` binding to increase its lifetime");
-
- if let Some(end) = end_span {
- err.span_label(end, "temporary value needs to live until here");
- }
-
- err.emit();
- }
-
- fn report_illegal_mutation_of_borrowed(
- &mut self,
- _: Context,
- (place, span): (&Place<'tcx>, Span),
- loan: &BorrowData,
- ) {
- let mut err = self.tcx.cannot_assign_to_borrowed(
- span,
- self.retrieve_borrow_span(loan),
- &self.describe_place(place).unwrap_or("_".to_owned()),
- Origin::Mir,
- );
-
- err.emit();
- }
-
- fn report_illegal_reassignment(
- &mut self,
- _context: Context,
- (place, span): (&Place<'tcx>, Span),
- assigned_span: Span,
- ) {
- let mut err = self.tcx.cannot_reassign_immutable(
- span,
- &self.describe_place(place).unwrap_or("_".to_owned()),
- Origin::Mir,
- );
- err.span_label(span, "cannot assign twice to immutable variable");
- if span != assigned_span {
- let value_msg = match self.describe_place(place) {
- Some(name) => format!("`{}`", name),
- None => "value".to_owned(),
- };
- err.span_label(assigned_span, format!("first assignment to {}", value_msg));
- }
- err.emit();
- }
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- // End-user visible description of `place` if one can be found. If the
- // place is a temporary for instance, None will be returned.
- fn describe_place(&self, place: &Place<'tcx>) -> Option<String> {
- let mut buf = String::new();
- match self.append_place_to_string(place, &mut buf, false) {
- Ok(()) => Some(buf),
- Err(()) => None,
- }
- }
-
- /// If this is a field projection, and the field is being projected from a closure type,
- /// then returns the index of the field being projected. Note that this closure will always
- /// be `self` in the current MIR, because that is the only time we directly access the fields
- /// of a closure type.
- fn is_upvar_field_projection(&self, place: &Place<'tcx>) -> Option<Field> {
- match *place {
- Place::Projection(ref proj) => match proj.elem {
- ProjectionElem::Field(field, _ty) => {
- let is_projection_from_ty_closure = proj.base
- .ty(self.mir, self.tcx)
- .to_ty(self.tcx)
- .is_closure();
-
- if is_projection_from_ty_closure {
- Some(field)
- } else {
- None
- }
- }
- _ => None,
- },
- _ => None,
- }
- }
-
- // Appends end-user visible description of `place` to `buf`.
- fn append_place_to_string(
- &self,
- place: &Place<'tcx>,
- buf: &mut String,
- mut autoderef: bool,
- ) -> Result<(), ()> {
- match *place {
- Place::Local(local) => {
- self.append_local_to_string(local, buf)?;
- }
- Place::Static(ref static_) => {
- buf.push_str(&format!("{}", &self.tcx.item_name(static_.def_id)));
- }
- Place::Projection(ref proj) => {
- match proj.elem {
- ProjectionElem::Deref => {
- if let Some(field) = self.is_upvar_field_projection(&proj.base) {
- let var_index = field.index();
- let name = self.mir.upvar_decls[var_index].debug_name.to_string();
- if self.mir.upvar_decls[var_index].by_ref {
- buf.push_str(&name);
- } else {
- buf.push_str(&format!("*{}", &name));
- }
- } else {
- if autoderef {
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- } else {
- buf.push_str(&"*");
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- }
- }
- }
- ProjectionElem::Downcast(..) => {
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- }
- ProjectionElem::Field(field, _ty) => {
- autoderef = true;
-
- if let Some(field) = self.is_upvar_field_projection(place) {
- let var_index = field.index();
- let name = self.mir.upvar_decls[var_index].debug_name.to_string();
- buf.push_str(&name);
- } else {
- let field_name = self.describe_field(&proj.base, field);
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- buf.push_str(&format!(".{}", field_name));
- }
- }
- ProjectionElem::Index(index) => {
- autoderef = true;
-
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- buf.push_str("[");
- if let Err(_) = self.append_local_to_string(index, buf) {
- buf.push_str("..");
- }
- buf.push_str("]");
- }
- ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
- autoderef = true;
- // Since it isn't possible to borrow an element on a particular index and
- // then use another while the borrow is held, don't output indices details
- // to avoid confusing the end-user
- self.append_place_to_string(&proj.base, buf, autoderef)?;
- buf.push_str(&"[..]");
- }
- };
- }
- }
-
- Ok(())
- }
-
- // Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have
- // a name, then `Err` is returned
- fn append_local_to_string(&self, local_index: Local, buf: &mut String) -> Result<(), ()> {
- let local = &self.mir.local_decls[local_index];
- match local.name {
- Some(name) => {
- buf.push_str(&format!("{}", name));
- Ok(())
- }
- None => Err(()),
- }
- }
-
- // End-user visible description of the `field`nth field of `base`
- fn describe_field(&self, base: &Place, field: Field) -> String {
- match *base {
- Place::Local(local) => {
- let local = &self.mir.local_decls[local];
- self.describe_field_from_ty(&local.ty, field)
- }
- Place::Static(ref static_) => self.describe_field_from_ty(&static_.ty, field),
- Place::Projection(ref proj) => match proj.elem {
- ProjectionElem::Deref => self.describe_field(&proj.base, field),
- ProjectionElem::Downcast(def, variant_index) => {
- format!("{}", def.variants[variant_index].fields[field.index()].name)
- }
- ProjectionElem::Field(_, field_type) => {
- self.describe_field_from_ty(&field_type, field)
- }
- ProjectionElem::Index(..) |
- ProjectionElem::ConstantIndex { .. } |
- ProjectionElem::Subslice { .. } => {
- format!("{}", self.describe_field(&proj.base, field))
- }
- },
- }
- }
-
- // End-user visible description of the `field_index`nth field of `ty`
- fn describe_field_from_ty(&self, ty: &ty::Ty, field: Field) -> String {
- if ty.is_box() {
- // If the type is a box, the field is described from the boxed type
- self.describe_field_from_ty(&ty.boxed_ty(), field)
- } else {
- match ty.sty {
- ty::TyAdt(def, _) => if def.is_enum() {
- format!("{}", field.index())
- } else {
- format!("{}", def.struct_variant().fields[field.index()].name)
- },
- ty::TyTuple(_, _) => format!("{}", field.index()),
- ty::TyRef(_, tnm) | ty::TyRawPtr(tnm) => {
- self.describe_field_from_ty(&tnm.ty, field)
- }
- ty::TyArray(ty, _) | ty::TySlice(ty) => self.describe_field_from_ty(&ty, field),
- ty::TyClosure(closure_def_id, _) => {
- // Convert the def-id into a node-id. node-ids are only valid for
- // the local code in the current crate, so this returns an `Option` in case
- // the closure comes from another crate. But in that case we wouldn't
- // be borrowck'ing it, so we can just unwrap:
- let node_id = self.tcx.hir.as_local_node_id(closure_def_id).unwrap();
- let freevar = self.tcx.with_freevars(node_id, |fv| fv[field.index()]);
-
- self.tcx.hir.name(freevar.var_id()).to_string()
- }
- _ => {
- // Might need a revision when the fields in trait RFC is implemented
- // (https://github.com/rust-lang/rfcs/pull/1546)
- bug!(
- "End-user description not implemented for field access on `{:?}`",
- ty.sty
- );
- }
- }
- }
- }
-
- // Retrieve span of given borrow from the current MIR representation
- fn retrieve_borrow_span(&self, borrow: &BorrowData) -> Span {
- self.mir.source_info(borrow.location).span
- }
-}
-
-impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
- // FIXME (#16118): function intended to allow the borrow checker
- // to be less precise in its handling of Box while still allowing
- // moves out of a Box. They should be removed when/if we stop
- // treating Box specially (e.g. when/if DerefMove is added...)
-
- fn base_path<'d>(&self, place: &'d Place<'tcx>) -> &'d Place<'tcx> {
- //! Returns the base of the leftmost (deepest) dereference of an
- //! Box in `place`. If there is no dereference of an Box
- //! in `place`, then it just returns `place` itself.
-
- let mut cursor = place;
- let mut deepest = place;
- loop {
- let proj = match *cursor {
- Place::Local(..) | Place::Static(..) => return deepest,
- Place::Projection(ref proj) => proj,
- };
- if proj.elem == ProjectionElem::Deref
- && place.ty(self.mir, self.tcx).to_ty(self.tcx).is_box()
- {
- deepest = &proj.base;
- }
- cursor = &proj.base;
- }
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-struct Context {
- kind: ContextKind,
- loc: Location,
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum ContextKind {
- AssignLhs,
- AssignRhs,
- SetDiscrim,
- InlineAsm,
- SwitchInt,
- Drop,
- DropAndReplace,
- CallOperator,
- CallOperand,
- CallDest,
- Assert,
- Yield,
- StorageDead,
-}
-
-impl ContextKind {
- fn new(self, loc: Location) -> Context {
- Context {
- kind: self,
- loc: loc,
- }
- }
-}
-
-impl<'b, 'gcx, 'tcx> InProgress<'b, 'gcx, 'tcx> {
- pub(super) fn new(
- borrows: DataflowResults<Borrows<'b, 'gcx, 'tcx>>,
- inits: DataflowResults<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
- uninits: DataflowResults<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
- move_out: DataflowResults<MovingOutStatements<'b, 'gcx, 'tcx>>,
- ever_inits: DataflowResults<EverInitializedLvals<'b, 'gcx, 'tcx>>,
- ) -> Self {
- InProgress {
- borrows: FlowInProgress::new(borrows),
- inits: FlowInProgress::new(inits),
- uninits: FlowInProgress::new(uninits),
- move_outs: FlowInProgress::new(move_out),
- ever_inits: FlowInProgress::new(ever_inits),
- }
- }
-
- fn each_flow<XB, XI, XU, XM, XE>(
- &mut self,
- mut xform_borrows: XB,
- mut xform_inits: XI,
- mut xform_uninits: XU,
- mut xform_move_outs: XM,
- mut xform_ever_inits: XE,
- ) where
- XB: FnMut(&mut FlowInProgress<Borrows<'b, 'gcx, 'tcx>>),
- XI: FnMut(&mut FlowInProgress<MaybeInitializedLvals<'b, 'gcx, 'tcx>>),
- XU: FnMut(&mut FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>),
- XM: FnMut(&mut FlowInProgress<MovingOutStatements<'b, 'gcx, 'tcx>>),
- XE: FnMut(&mut FlowInProgress<EverInitializedLvals<'b, 'gcx, 'tcx>>),
- {
- xform_borrows(&mut self.borrows);
- xform_inits(&mut self.inits);
- xform_uninits(&mut self.uninits);
- xform_move_outs(&mut self.move_outs);
- xform_ever_inits(&mut self.ever_inits);
- }
-
- fn summary(&self) -> String {
- let mut s = String::new();
-
- s.push_str("borrows in effect: [");
- let mut saw_one = false;
- self.borrows.each_state_bit(|borrow| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let borrow_data = &self.borrows.base_results.operator().borrows()[borrow];
- s.push_str(&format!("{}", borrow_data));
- });
- s.push_str("] ");
-
- s.push_str("borrows generated: [");
- let mut saw_one = false;
- self.borrows.each_gen_bit(|borrow| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let borrow_data = &self.borrows.base_results.operator().borrows()[borrow];
- s.push_str(&format!("{}", borrow_data));
- });
- s.push_str("] ");
-
- s.push_str("inits: [");
- let mut saw_one = false;
- self.inits.each_state_bit(|mpi_init| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let move_path = &self.inits.base_results.operator().move_data().move_paths[mpi_init];
- s.push_str(&format!("{}", move_path));
- });
- s.push_str("] ");
-
- s.push_str("uninits: [");
- let mut saw_one = false;
- self.uninits.each_state_bit(|mpi_uninit| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let move_path =
- &self.uninits.base_results.operator().move_data().move_paths[mpi_uninit];
- s.push_str(&format!("{}", move_path));
- });
- s.push_str("] ");
-
- s.push_str("move_out: [");
- let mut saw_one = false;
- self.move_outs.each_state_bit(|mpi_move_out| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let move_out = &self.move_outs.base_results.operator().move_data().moves[mpi_move_out];
- s.push_str(&format!("{:?}", move_out));
- });
- s.push_str("] ");
-
- s.push_str("ever_init: [");
- let mut saw_one = false;
- self.ever_inits.each_state_bit(|mpi_ever_init| {
- if saw_one {
- s.push_str(", ");
- };
- saw_one = true;
- let ever_init =
- &self.ever_inits.base_results.operator().move_data().inits[mpi_ever_init];
- s.push_str(&format!("{:?}", ever_init));
- });
- s.push_str("]");
-
- return s;
- }
-}
-
-impl<'b, 'gcx, 'tcx> FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>> {
- fn has_any_child_of(&self, mpi: MovePathIndex) -> Option<MovePathIndex> {
- let move_data = self.base_results.operator().move_data();
-
- let mut todo = vec![mpi];
- let mut push_siblings = false; // don't look at siblings of original `mpi`.
- while let Some(mpi) = todo.pop() {
- if self.curr_state.contains(&mpi) {
- return Some(mpi);
- }
- let move_path = &move_data.move_paths[mpi];
- if let Some(child) = move_path.first_child {
- todo.push(child);
- }
- if push_siblings {
- if let Some(sibling) = move_path.next_sibling {
- todo.push(sibling);
- }
- } else {
- // after we've processed the original `mpi`, we should
- // always traverse the siblings of any of its
- // children.
- push_siblings = true;
- }
- }
- return None;
- }
-}
-
-impl<BD> FlowInProgress<BD>
-where
- BD: BitDenotation,
-{
- fn each_state_bit<F>(&self, f: F)
- where
- F: FnMut(BD::Idx),
- {
- self.curr_state
- .each_bit(self.base_results.operator().bits_per_block(), f)
- }
-
- fn each_gen_bit<F>(&self, f: F)
- where
- F: FnMut(BD::Idx),
- {
- self.stmt_gen
- .each_bit(self.base_results.operator().bits_per_block(), f)
- }
-
- fn new(results: DataflowResults<BD>) -> Self {
- let bits_per_block = results.sets().bits_per_block();
- let curr_state = IdxSetBuf::new_empty(bits_per_block);
- let stmt_gen = IdxSetBuf::new_empty(bits_per_block);
- let stmt_kill = IdxSetBuf::new_empty(bits_per_block);
- FlowInProgress {
- base_results: results,
- curr_state: curr_state,
- stmt_gen: stmt_gen,
- stmt_kill: stmt_kill,
- }
- }
-
- fn reset_to_entry_of(&mut self, bb: BasicBlock) {
- (*self.curr_state).clone_from(self.base_results.sets().on_entry_set_for(bb.index()));
- }
-
- fn reconstruct_statement_effect(&mut self, loc: Location) {
- self.stmt_gen.reset_to_empty();
- self.stmt_kill.reset_to_empty();
- let mut ignored = IdxSetBuf::new_empty(0);
- let mut sets = BlockSets {
- on_entry: &mut ignored,
- gen_set: &mut self.stmt_gen,
- kill_set: &mut self.stmt_kill,
- };
- self.base_results
- .operator()
- .statement_effect(&mut sets, loc);
- }
-
- fn reconstruct_terminator_effect(&mut self, loc: Location) {
- self.stmt_gen.reset_to_empty();
- self.stmt_kill.reset_to_empty();
- let mut ignored = IdxSetBuf::new_empty(0);
- let mut sets = BlockSets {
- on_entry: &mut ignored,
- gen_set: &mut self.stmt_gen,
- kill_set: &mut self.stmt_kill,
- };
- self.base_results
- .operator()
- .terminator_effect(&mut sets, loc);
- }
-
- fn apply_local_effect(&mut self) {
- self.curr_state.union(&self.stmt_gen);
- self.curr_state.subtract(&self.stmt_kill);
- }
-
- fn elems_incoming(&self) -> indexed_set::Elems<BD::Idx> {
- let univ = self.base_results.sets().bits_per_block();
- self.curr_state.elems(univ)
- }
-
- fn with_elems_outgoing<F>(&self, f: F)
- where
- F: FnOnce(indexed_set::Elems<BD::Idx>),
- {
- let mut curr_state = self.curr_state.clone();
- curr_state.union(&self.stmt_gen);
- curr_state.subtract(&self.stmt_kill);
- let univ = self.base_results.sets().bits_per_block();
- f(curr_state.elems(univ));
- }
-}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! This query borrow-checks the MIR to (further) ensure it is not broken.
+
+use rustc::hir;
+use rustc::hir::def_id::DefId;
+use rustc::infer::InferCtxt;
+use rustc::ty::{self, ParamEnv, TyCtxt};
+use rustc::ty::maps::Providers;
+use rustc::mir::{AssertMessage, BasicBlock, BorrowKind, Local, Location, Place};
+use rustc::mir::{Mir, Mutability, Operand, Projection, ProjectionElem, Rvalue};
+use rustc::mir::{Field, Statement, StatementKind, Terminator, TerminatorKind};
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::indexed_set::{self, IdxSetBuf};
+use rustc_data_structures::indexed_vec::Idx;
+
+use syntax::ast;
+use syntax_pos::Span;
+
+use dataflow::do_dataflow;
+use dataflow::MoveDataParamEnv;
+use dataflow::{BitDenotation, BlockSets, DataflowResults, DataflowResultsConsumer};
+use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
+use dataflow::{EverInitializedLvals, MovingOutStatements};
+use dataflow::{BorrowData, BorrowIndex, Borrows};
+use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
+use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MoveOutIndex, MovePathIndex};
+use util::borrowck_errors::{BorrowckErrors, Origin};
+
+use self::MutateMode::{JustWrite, WriteAndRead};
+
+pub(crate) mod nll;
+
+pub fn provide(providers: &mut Providers) {
+ *providers = Providers {
+ mir_borrowck,
+ ..*providers
+ };
+}
+
+fn mir_borrowck<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) {
+ let input_mir = tcx.mir_validated(def_id);
+ debug!("run query mir_borrowck: {}", tcx.item_path_str(def_id));
+
+ if {
+ !tcx.has_attr(def_id, "rustc_mir_borrowck") && !tcx.sess.opts.borrowck_mode.use_mir()
+ && !tcx.sess.opts.debugging_opts.nll
+ } {
+ return;
+ }
+
+ tcx.infer_ctxt().enter(|infcx| {
+ let input_mir: &Mir = &input_mir.borrow();
+ do_mir_borrowck(&infcx, input_mir, def_id);
+ });
+ debug!("mir_borrowck done");
+}
+
+fn do_mir_borrowck<'a, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'a, 'gcx, 'tcx>,
+ input_mir: &Mir<'gcx>,
+ def_id: DefId,
+) {
+ let tcx = infcx.tcx;
+ let attributes = tcx.get_attrs(def_id);
+ let param_env = tcx.param_env(def_id);
+ let id = tcx.hir
+ .as_local_node_id(def_id)
+ .expect("do_mir_borrowck: non-local DefId");
+
+ // Make our own copy of the MIR. This copy will be modified (in place) to
+ // contain non-lexical lifetimes. It will have a lifetime tied
+ // to the inference context.
+ let mut mir: Mir<'tcx> = input_mir.clone();
+ let free_regions = if !tcx.sess.opts.debugging_opts.nll {
+ None
+ } else {
+ let mir = &mut mir;
+
+ // Replace all regions with fresh inference variables.
+ Some(nll::replace_regions_in_mir(infcx, def_id, mir))
+ };
+ let mir = &mir;
+
+ let move_data: MoveData<'tcx> = match MoveData::gather_moves(mir, tcx) {
+ Ok(move_data) => move_data,
+ Err((move_data, move_errors)) => {
+ for move_error in move_errors {
+ let (span, kind): (Span, IllegalMoveOriginKind) = match move_error {
+ MoveError::UnionMove { .. } => {
+ unimplemented!("dont know how to report union move errors yet.")
+ }
+ MoveError::IllegalMove {
+ cannot_move_out_of: o,
+ } => (o.span, o.kind),
+ };
+ let origin = Origin::Mir;
+ let mut err = match kind {
+ IllegalMoveOriginKind::Static => {
+ tcx.cannot_move_out_of(span, "static item", origin)
+ }
+ IllegalMoveOriginKind::BorrowedContent => {
+ tcx.cannot_move_out_of(span, "borrowed content", origin)
+ }
+ IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
+ tcx.cannot_move_out_of_interior_of_drop(span, ty, origin)
+ }
+ IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
+ tcx.cannot_move_out_of_interior_noncopy(span, ty, is_index, origin)
+ }
+ };
+ err.emit();
+ }
+ move_data
+ }
+ };
+
+ let mdpe = MoveDataParamEnv {
+ move_data: move_data,
+ param_env: param_env,
+ };
+ let dead_unwinds = IdxSetBuf::new_empty(mir.basic_blocks().len());
+ let mut flow_inits = FlowInProgress::new(do_dataflow(
+ tcx,
+ mir,
+ id,
+ &attributes,
+ &dead_unwinds,
+ MaybeInitializedLvals::new(tcx, mir, &mdpe),
+ |bd, i| &bd.move_data().move_paths[i],
+ ));
+ let flow_uninits = FlowInProgress::new(do_dataflow(
+ tcx,
+ mir,
+ id,
+ &attributes,
+ &dead_unwinds,
+ MaybeUninitializedLvals::new(tcx, mir, &mdpe),
+ |bd, i| &bd.move_data().move_paths[i],
+ ));
+ let flow_move_outs = FlowInProgress::new(do_dataflow(
+ tcx,
+ mir,
+ id,
+ &attributes,
+ &dead_unwinds,
+ MovingOutStatements::new(tcx, mir, &mdpe),
+ |bd, i| &bd.move_data().moves[i],
+ ));
+ let flow_ever_inits = FlowInProgress::new(do_dataflow(
+ tcx,
+ mir,
+ id,
+ &attributes,
+ &dead_unwinds,
+ EverInitializedLvals::new(tcx, mir, &mdpe),
+ |bd, i| &bd.move_data().inits[i],
+ ));
+
+ // If we are in non-lexical mode, compute the non-lexical lifetimes.
+ let opt_regioncx = if let Some(free_regions) = free_regions {
+ Some(nll::compute_regions(
+ infcx,
+ def_id,
+ free_regions,
+ mir,
+ param_env,
+ &mut flow_inits,
+ &mdpe.move_data,
+ ))
+ } else {
+ assert!(!tcx.sess.opts.debugging_opts.nll);
+ None
+ };
+ let flow_inits = flow_inits; // remove mut
+
+ let mut mbcx = MirBorrowckCtxt {
+ tcx: tcx,
+ mir: mir,
+ node_id: id,
+ move_data: &mdpe.move_data,
+ param_env: param_env,
+ storage_dead_or_drop_error_reported: FxHashSet(),
+ };
+
+ let flow_borrows = FlowInProgress::new(do_dataflow(
+ tcx,
+ mir,
+ id,
+ &attributes,
+ &dead_unwinds,
+ Borrows::new(tcx, mir, opt_regioncx),
+ |bd, i| bd.location(i),
+ ));
+
+ let mut state = InProgress::new(
+ flow_borrows,
+ flow_inits,
+ flow_uninits,
+ flow_move_outs,
+ flow_ever_inits,
+ );
+
+ mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer
+}
+
+#[allow(dead_code)]
+pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
+ tcx: TyCtxt<'cx, 'gcx, 'tcx>,
+ mir: &'cx Mir<'tcx>,
+ node_id: ast::NodeId,
+ move_data: &'cx MoveData<'tcx>,
+ param_env: ParamEnv<'gcx>,
+ /// This field keeps track of when storage dead or drop errors are reported
+ /// in order to stop duplicate error reporting and identify the conditions required
+ /// for a "temporary value dropped here while still borrowed" error. See #45360.
+ storage_dead_or_drop_error_reported: FxHashSet<Local>,
+}
+
+// (forced to be `pub` due to its use as an associated type below.)
+pub struct InProgress<'b, 'gcx: 'tcx, 'tcx: 'b> {
+ borrows: FlowInProgress<Borrows<'b, 'gcx, 'tcx>>,
+ inits: FlowInProgress<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
+ uninits: FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
+ move_outs: FlowInProgress<MovingOutStatements<'b, 'gcx, 'tcx>>,
+ ever_inits: FlowInProgress<EverInitializedLvals<'b, 'gcx, 'tcx>>,
+}
+
+struct FlowInProgress<BD>
+where
+ BD: BitDenotation,
+{
+ base_results: DataflowResults<BD>,
+ curr_state: IdxSetBuf<BD::Idx>,
+ stmt_gen: IdxSetBuf<BD::Idx>,
+ stmt_kill: IdxSetBuf<BD::Idx>,
+}
+
+// Check that:
+// 1. assignments are always made to mutable locations (FIXME: does that still really go here?)
+// 2. loans made in overlapping scopes do not conflict
+// 3. assignments do not affect things loaned out as immutable
+// 4. moves do not affect things loaned out in any way
+impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ type FlowState = InProgress<'cx, 'gcx, 'tcx>;
+
+ fn mir(&self) -> &'cx Mir<'tcx> {
+ self.mir
+ }
+
+ fn reset_to_entry_of(&mut self, bb: BasicBlock, flow_state: &mut Self::FlowState) {
+ flow_state.each_flow(
+ |b| b.reset_to_entry_of(bb),
+ |i| i.reset_to_entry_of(bb),
+ |u| u.reset_to_entry_of(bb),
+ |m| m.reset_to_entry_of(bb),
+ |e| e.reset_to_entry_of(bb),
+ );
+ }
+
+ fn reconstruct_statement_effect(
+ &mut self,
+ location: Location,
+ flow_state: &mut Self::FlowState,
+ ) {
+ flow_state.each_flow(
+ |b| b.reconstruct_statement_effect(location),
+ |i| i.reconstruct_statement_effect(location),
+ |u| u.reconstruct_statement_effect(location),
+ |m| m.reconstruct_statement_effect(location),
+ |e| e.reconstruct_statement_effect(location),
+ );
+ }
+
+ fn apply_local_effect(&mut self, _location: Location, flow_state: &mut Self::FlowState) {
+ flow_state.each_flow(
+ |b| b.apply_local_effect(),
+ |i| i.apply_local_effect(),
+ |u| u.apply_local_effect(),
+ |m| m.apply_local_effect(),
+ |e| e.apply_local_effect(),
+ );
+ }
+
+ fn reconstruct_terminator_effect(
+ &mut self,
+ location: Location,
+ flow_state: &mut Self::FlowState,
+ ) {
+ flow_state.each_flow(
+ |b| b.reconstruct_terminator_effect(location),
+ |i| i.reconstruct_terminator_effect(location),
+ |u| u.reconstruct_terminator_effect(location),
+ |m| m.reconstruct_terminator_effect(location),
+ |e| e.reconstruct_terminator_effect(location),
+ );
+ }
+
+ fn visit_block_entry(&mut self, bb: BasicBlock, flow_state: &Self::FlowState) {
+ let summary = flow_state.summary();
+ debug!("MirBorrowckCtxt::process_block({:?}): {}", bb, summary);
+ }
+
+ fn visit_statement_entry(
+ &mut self,
+ location: Location,
+ stmt: &Statement<'tcx>,
+ flow_state: &Self::FlowState,
+ ) {
+ let summary = flow_state.summary();
+ debug!(
+ "MirBorrowckCtxt::process_statement({:?}, {:?}): {}",
+ location,
+ stmt,
+ summary
+ );
+ let span = stmt.source_info.span;
+ match stmt.kind {
+ StatementKind::Assign(ref lhs, ref rhs) => {
+ // NOTE: NLL RFC calls for *shallow* write; using Deep
+ // for short-term compat w/ AST-borrowck. Also, switch
+ // to shallow requires to dataflow: "if this is an
+ // assignment `place = <rvalue>`, then any loan for some
+ // path P of which `place` is a prefix is killed."
+ self.mutate_place(
+ ContextKind::AssignLhs.new(location),
+ (lhs, span),
+ Deep,
+ JustWrite,
+ flow_state,
+ );
+
+ self.consume_rvalue(
+ ContextKind::AssignRhs.new(location),
+ (rhs, span),
+ location,
+ flow_state,
+ );
+ }
+ StatementKind::SetDiscriminant {
+ ref place,
+ variant_index: _,
+ } => {
+ self.mutate_place(
+ ContextKind::SetDiscrim.new(location),
+ (place, span),
+ Shallow(Some(ArtificialField::Discriminant)),
+ JustWrite,
+ flow_state,
+ );
+ }
+ StatementKind::InlineAsm {
+ ref asm,
+ ref outputs,
+ ref inputs,
+ } => {
+ let context = ContextKind::InlineAsm.new(location);
+ for (o, output) in asm.outputs.iter().zip(outputs) {
+ if o.is_indirect {
+ // FIXME(eddyb) indirect inline asm outputs should
+ // be encoeded through MIR place derefs instead.
+ self.access_place(
+ context,
+ (output, span),
+ (Deep, Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Use,
+ (output, span),
+ flow_state,
+ );
+ } else {
+ self.mutate_place(
+ context,
+ (output, span),
+ Deep,
+ if o.is_rw { WriteAndRead } else { JustWrite },
+ flow_state,
+ );
+ }
+ }
+ for input in inputs {
+ self.consume_operand(context, (input, span), flow_state);
+ }
+ }
+ StatementKind::EndRegion(ref _rgn) => {
+ // ignored when consuming results (update to
+ // flow_state already handled).
+ }
+ StatementKind::Nop | StatementKind::Validate(..) | StatementKind::StorageLive(..) => {
+ // `Nop`, `Validate`, and `StorageLive` are irrelevant
+ // to borrow check.
+ }
+
+ StatementKind::StorageDead(local) => {
+ self.access_place(
+ ContextKind::StorageDead.new(location),
+ (&Place::Local(local), span),
+ (Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+ }
+ }
+ }
+
+ fn visit_terminator_entry(
+ &mut self,
+ location: Location,
+ term: &Terminator<'tcx>,
+ flow_state: &Self::FlowState,
+ ) {
+ let loc = location;
+ let summary = flow_state.summary();
+ debug!(
+ "MirBorrowckCtxt::process_terminator({:?}, {:?}): {}",
+ location,
+ term,
+ summary
+ );
+ let span = term.source_info.span;
+ match term.kind {
+ TerminatorKind::SwitchInt {
+ ref discr,
+ switch_ty: _,
+ values: _,
+ targets: _,
+ } => {
+ self.consume_operand(ContextKind::SwitchInt.new(loc), (discr, span), flow_state);
+ }
+ TerminatorKind::Drop {
+ location: ref drop_place,
+ target: _,
+ unwind: _,
+ } => {
+ self.access_place(
+ ContextKind::Drop.new(loc),
+ (drop_place, span),
+ (Deep, Write(WriteKind::StorageDeadOrDrop)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+ }
+ TerminatorKind::DropAndReplace {
+ location: ref drop_place,
+ value: ref new_value,
+ target: _,
+ unwind: _,
+ } => {
+ self.mutate_place(
+ ContextKind::DropAndReplace.new(loc),
+ (drop_place, span),
+ Deep,
+ JustWrite,
+ flow_state,
+ );
+ self.consume_operand(
+ ContextKind::DropAndReplace.new(loc),
+ (new_value, span),
+ flow_state,
+ );
+ }
+ TerminatorKind::Call {
+ ref func,
+ ref args,
+ ref destination,
+ cleanup: _,
+ } => {
+ self.consume_operand(ContextKind::CallOperator.new(loc), (func, span), flow_state);
+ for arg in args {
+ self.consume_operand(
+ ContextKind::CallOperand.new(loc),
+ (arg, span),
+ flow_state,
+ );
+ }
+ if let Some((ref dest, _ /*bb*/)) = *destination {
+ self.mutate_place(
+ ContextKind::CallDest.new(loc),
+ (dest, span),
+ Deep,
+ JustWrite,
+ flow_state,
+ );
+ }
+ }
+ TerminatorKind::Assert {
+ ref cond,
+ expected: _,
+ ref msg,
+ target: _,
+ cleanup: _,
+ } => {
+ self.consume_operand(ContextKind::Assert.new(loc), (cond, span), flow_state);
+ match *msg {
+ AssertMessage::BoundsCheck { ref len, ref index } => {
+ self.consume_operand(ContextKind::Assert.new(loc), (len, span), flow_state);
+ self.consume_operand(
+ ContextKind::Assert.new(loc),
+ (index, span),
+ flow_state,
+ );
+ }
+ AssertMessage::Math(_ /*const_math_err*/) => {}
+ AssertMessage::GeneratorResumedAfterReturn => {}
+ AssertMessage::GeneratorResumedAfterPanic => {}
+ }
+ }
+
+ TerminatorKind::Yield {
+ ref value,
+ resume: _,
+ drop: _,
+ } => {
+ self.consume_operand(ContextKind::Yield.new(loc), (value, span), flow_state);
+ }
+
+ TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
+ // Returning from the function implicitly kills storage for all locals and statics.
+ // Often, the storage will already have been killed by an explicit
+ // StorageDead, but we don't always emit those (notably on unwind paths),
+ // so this "extra check" serves as a kind of backup.
+ let domain = flow_state.borrows.base_results.operator();
+ let data = domain.borrows();
+ flow_state.borrows.with_elems_outgoing(|borrows| {
+ for i in borrows {
+ let borrow = &data[i];
+
+ if self.place_is_invalidated_at_exit(&borrow.place) {
+ debug!("borrow conflicts at exit {:?}", borrow);
+ let borrow_span = self.mir.source_info(borrow.location).span;
+ // FIXME: should be talking about the region lifetime instead
+ // of just a span here.
+ let end_span = domain.opt_region_end_span(&borrow.region);
+
+ self.report_borrowed_value_does_not_live_long_enough(
+ ContextKind::StorageDead.new(loc),
+ (&borrow.place, borrow_span),
+ end_span,
+ )
+ }
+ }
+ });
+ }
+ TerminatorKind::Goto { target: _ } |
+ TerminatorKind::Unreachable |
+ TerminatorKind::FalseEdges { .. } => {
+ // no data used, thus irrelevant to borrowck
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum MutateMode {
+ JustWrite,
+ WriteAndRead,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum Control {
+ Continue,
+ Break,
+}
+
+use self::ShallowOrDeep::{Deep, Shallow};
+use self::ReadOrWrite::{Read, Write};
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ArtificialField {
+ Discriminant,
+ ArrayLength,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ShallowOrDeep {
+ /// From the RFC: "A *shallow* access means that the immediate
+ /// fields reached at LV are accessed, but references or pointers
+ /// found within are not dereferenced. Right now, the only access
+ /// that is shallow is an assignment like `x = ...;`, which would
+ /// be a *shallow write* of `x`."
+ Shallow(Option<ArtificialField>),
+
+ /// From the RFC: "A *deep* access means that all data reachable
+ /// through the given place may be invalidated or accesses by
+ /// this action."
+ Deep,
+}
+
+/// Kind of access to a value: read or write
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ReadOrWrite {
+ /// From the RFC: "A *read* means that the existing data may be
+ /// read, but will not be changed."
+ Read(ReadKind),
+
+ /// From the RFC: "A *write* means that the data may be mutated to
+ /// new values or otherwise invalidated (for example, it could be
+ /// de-initialized, as in a move operation).
+ Write(WriteKind),
+}
+
+/// Kind of read access to a value
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ReadKind {
+ Borrow(BorrowKind),
+ Copy,
+}
+
+/// Kind of write access to a value
+/// (For informational purposes only)
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum WriteKind {
+ StorageDeadOrDrop,
+ MutableBorrow(BorrowKind),
+ Mutate,
+ Move,
+}
+
+/// When checking permissions for a place access, this flag is used to indicate that an immutable
+/// local place can be mutated.
+///
+/// FIXME: @nikomatsakis suggested that this flag could be removed with the following modifications:
+/// - Merge `check_access_permissions()` and `check_if_reassignment_to_immutable_state()`
+/// - Split `is_mutable()` into `is_assignable()` (can be directly assigned) and
+/// `is_declared_mutable()`
+/// - Take flow state into consideration in `is_assignable()` for local variables
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum LocalMutationIsAllowed {
+ Yes,
+ No,
+}
+
+#[derive(Copy, Clone)]
+enum InitializationRequiringAction {
+ Update,
+ Borrow,
+ Use,
+ Assignment,
+}
+
+impl InitializationRequiringAction {
+ fn as_noun(self) -> &'static str {
+ match self {
+ InitializationRequiringAction::Update => "update",
+ InitializationRequiringAction::Borrow => "borrow",
+ InitializationRequiringAction::Use => "use",
+ InitializationRequiringAction::Assignment => "assign",
+ }
+ }
+
+ fn as_verb_in_past_tense(self) -> &'static str {
+ match self {
+ InitializationRequiringAction::Update => "updated",
+ InitializationRequiringAction::Borrow => "borrowed",
+ InitializationRequiringAction::Use => "used",
+ InitializationRequiringAction::Assignment => "assigned",
+ }
+ }
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ /// Checks an access to the given place to see if it is allowed. Examines the set of borrows
+ /// that are in scope, as well as which paths have been initialized, to ensure that (a) the
+ /// place is initialized and (b) it is not borrowed in some way that would prevent this
+ /// access.
+ ///
+ /// Returns true if an error is reported, false otherwise.
+ fn access_place(
+ &mut self,
+ context: Context,
+ place_span: (&Place<'tcx>, Span),
+ kind: (ShallowOrDeep, ReadOrWrite),
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ let (sd, rw) = kind;
+
+ let storage_dead_or_drop_local = match (place_span.0, rw) {
+ (&Place::Local(local), Write(WriteKind::StorageDeadOrDrop)) => Some(local),
+ _ => None,
+ };
+
+ // Check if error has already been reported to stop duplicate reporting.
+ if let Some(local) = storage_dead_or_drop_local {
+ if self.storage_dead_or_drop_error_reported.contains(&local) {
+ return;
+ }
+ }
+
+ // Check permissions
+ let mut error_reported =
+ self.check_access_permissions(place_span, rw, is_local_mutation_allowed);
+
+ self.each_borrow_involving_path(
+ context,
+ (sd, place_span.0),
+ flow_state,
+ |this, _index, borrow, common_prefix| match (rw, borrow.kind) {
+ (Read(_), BorrowKind::Shared) => Control::Continue,
+ (Read(kind), BorrowKind::Unique) | (Read(kind), BorrowKind::Mut) => {
+ match kind {
+ ReadKind::Copy => {
+ error_reported = true;
+ this.report_use_while_mutably_borrowed(context, place_span, borrow)
+ }
+ ReadKind::Borrow(bk) => {
+ let end_issued_loan_span = flow_state
+ .borrows
+ .base_results
+ .operator()
+ .opt_region_end_span(&borrow.region);
+ error_reported = true;
+ this.report_conflicting_borrow(
+ context,
+ common_prefix,
+ place_span,
+ bk,
+ &borrow,
+ end_issued_loan_span,
+ )
+ }
+ }
+ Control::Break
+ }
+ (Write(kind), _) => {
+ match kind {
+ WriteKind::MutableBorrow(bk) => {
+ let end_issued_loan_span = flow_state
+ .borrows
+ .base_results
+ .operator()
+ .opt_region_end_span(&borrow.region);
+ error_reported = true;
+ this.report_conflicting_borrow(
+ context,
+ common_prefix,
+ place_span,
+ bk,
+ &borrow,
+ end_issued_loan_span,
+ )
+ }
+ WriteKind::StorageDeadOrDrop => {
+ let end_span = flow_state
+ .borrows
+ .base_results
+ .operator()
+ .opt_region_end_span(&borrow.region);
+ error_reported = true;
+ this.report_borrowed_value_does_not_live_long_enough(
+ context,
+ place_span,
+ end_span,
+ )
+ }
+ WriteKind::Mutate => {
+ error_reported = true;
+ this.report_illegal_mutation_of_borrowed(context, place_span, borrow)
+ }
+ WriteKind::Move => {
+ error_reported = true;
+ this.report_move_out_while_borrowed(context, place_span, &borrow)
+ }
+ }
+ Control::Break
+ }
+ },
+ );
+
+ if error_reported {
+ if let Some(local) = storage_dead_or_drop_local {
+ self.storage_dead_or_drop_error_reported.insert(local);
+ }
+ }
+ }
+
+ fn mutate_place(
+ &mut self,
+ context: Context,
+ place_span: (&Place<'tcx>, Span),
+ kind: ShallowOrDeep,
+ mode: MutateMode,
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ // Write of P[i] or *P, or WriteAndRead of any P, requires P init'd.
+ match mode {
+ MutateMode::WriteAndRead => {
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Update,
+ place_span,
+ flow_state,
+ );
+ }
+ MutateMode::JustWrite => {
+ self.check_if_assigned_path_is_moved(context, place_span, flow_state);
+ }
+ }
+
+ self.access_place(
+ context,
+ place_span,
+ (kind, Write(WriteKind::Mutate)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+
+ // check for reassignments to immutable local variables
+ self.check_if_reassignment_to_immutable_state(context, place_span, flow_state);
+ }
+
+ fn consume_rvalue(
+ &mut self,
+ context: Context,
+ (rvalue, span): (&Rvalue<'tcx>, Span),
+ _location: Location,
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ match *rvalue {
+ Rvalue::Ref(_ /*rgn*/, bk, ref place) => {
+ let access_kind = match bk {
+ BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
+ BorrowKind::Unique | BorrowKind::Mut => {
+ (Deep, Write(WriteKind::MutableBorrow(bk)))
+ }
+ };
+ self.access_place(
+ context,
+ (place, span),
+ access_kind,
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Borrow,
+ (place, span),
+ flow_state,
+ );
+ }
+
+ Rvalue::Use(ref operand) |
+ Rvalue::Repeat(ref operand, _) |
+ Rvalue::UnaryOp(_ /*un_op*/, ref operand) |
+ Rvalue::Cast(_ /*cast_kind*/, ref operand, _ /*ty*/) => {
+ self.consume_operand(context, (operand, span), flow_state)
+ }
+
+ Rvalue::Len(ref place) | Rvalue::Discriminant(ref place) => {
+ let af = match *rvalue {
+ Rvalue::Len(..) => ArtificialField::ArrayLength,
+ Rvalue::Discriminant(..) => ArtificialField::Discriminant,
+ _ => unreachable!(),
+ };
+ self.access_place(
+ context,
+ (place, span),
+ (Shallow(Some(af)), Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Use,
+ (place, span),
+ flow_state,
+ );
+ }
+
+ Rvalue::BinaryOp(_bin_op, ref operand1, ref operand2) |
+ Rvalue::CheckedBinaryOp(_bin_op, ref operand1, ref operand2) => {
+ self.consume_operand(context, (operand1, span), flow_state);
+ self.consume_operand(context, (operand2, span), flow_state);
+ }
+
+ Rvalue::NullaryOp(_op, _ty) => {
+ // nullary ops take no dynamic input; no borrowck effect.
+ //
+ // FIXME: is above actually true? Do we want to track
+ // the fact that uninitialized data can be created via
+ // `NullOp::Box`?
+ }
+
+ Rvalue::Aggregate(ref _aggregate_kind, ref operands) => for operand in operands {
+ self.consume_operand(context, (operand, span), flow_state);
+ },
+ }
+ }
+
+ fn consume_operand(
+ &mut self,
+ context: Context,
+ (operand, span): (&Operand<'tcx>, Span),
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ match *operand {
+ Operand::Copy(ref place) => {
+ // copy of place: check if this is "copy of frozen path"
+ // (FIXME: see check_loans.rs)
+ self.access_place(
+ context,
+ (place, span),
+ (Deep, Read(ReadKind::Copy)),
+ LocalMutationIsAllowed::No,
+ flow_state,
+ );
+
+ // Finally, check if path was already moved.
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Use,
+ (place, span),
+ flow_state,
+ );
+ }
+ Operand::Move(ref place) => {
+ // move of place: check if this is move of already borrowed path
+ self.access_place(
+ context,
+ (place, span),
+ (Deep, Write(WriteKind::Move)),
+ LocalMutationIsAllowed::Yes,
+ flow_state,
+ );
+
+ // Finally, check if path was already moved.
+ self.check_if_path_is_moved(
+ context,
+ InitializationRequiringAction::Use,
+ (place, span),
+ flow_state,
+ );
+ }
+ Operand::Constant(_) => {}
+ }
+ }
+
+ /// Returns whether a borrow of this place is invalidated when the function
+ /// exits
+ fn place_is_invalidated_at_exit(&self, place: &Place<'tcx>) -> bool {
+ debug!("place_is_invalidated_at_exit({:?})", place);
+ let root_place = self.prefixes(place, PrefixSet::All).last().unwrap();
+
+ // FIXME(nll-rfc#40): do more precise destructor tracking here. For now
+ // we just know that all locals are dropped at function exit (otherwise
+ // we'll have a memory leak) and assume that all statics have a destructor.
+ let (might_be_alive, will_be_dropped) = match root_place {
+ Place::Static(statik) => {
+ // Thread-locals might be dropped after the function exits, but
+ // "true" statics will never be.
+ let is_thread_local = self.tcx
+ .get_attrs(statik.def_id)
+ .iter()
+ .any(|attr| attr.check_name("thread_local"));
+
+ (true, is_thread_local)
+ }
+ Place::Local(_) => {
+ // Locals are always dropped at function exit, and if they
+ // have a destructor it would've been called already.
+ (false, true)
+ }
+ Place::Projection(..) => {
+ bug!("root of {:?} is a projection ({:?})?", place, root_place)
+ }
+ };
+
+ if !will_be_dropped {
+ debug!(
+ "place_is_invalidated_at_exit({:?}) - won't be dropped",
+ place
+ );
+ return false;
+ }
+
+ // FIXME: replace this with a proper borrow_conflicts_with_place when
+ // that is merged.
+ let prefix_set = if might_be_alive {
+ PrefixSet::Supporting
+ } else {
+ PrefixSet::Shallow
+ };
+
+ self.prefixes(place, prefix_set)
+ .any(|prefix| prefix == root_place)
+ }
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ fn check_if_reassignment_to_immutable_state(
+ &mut self,
+ context: Context,
+ (place, span): (&Place<'tcx>, Span),
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ let move_data = self.move_data;
+
+ // determine if this path has a non-mut owner (and thus needs checking).
+ if let Ok(()) = self.is_mutable(place, LocalMutationIsAllowed::No) {
+ return;
+ }
+
+ if let Err(_) = self.is_mutable(place, LocalMutationIsAllowed::Yes) {
+ return;
+ }
+
+ match self.move_path_closest_to(place) {
+ Ok(mpi) => for ii in &move_data.init_path_map[mpi] {
+ if flow_state.ever_inits.curr_state.contains(ii) {
+ let first_assign_span = self.move_data.inits[*ii].span;
+ self.report_illegal_reassignment(context, (place, span), first_assign_span);
+ break;
+ }
+ },
+ Err(NoMovePathFound::ReachedStatic) => {
+ let item_msg = match self.describe_place(place) {
+ Some(name) => format!("immutable static item `{}`", name),
+ None => "immutable static item".to_owned(),
+ };
+ self.tcx.sess.delay_span_bug(
+ span,
+ &format!(
+ "cannot assign to {}, should have been caught by \
+ `check_access_permissions()`",
+ item_msg
+ ),
+ );
+ }
+ }
+ }
+
+ fn check_if_path_is_moved(
+ &mut self,
+ context: Context,
+ desired_action: InitializationRequiringAction,
+ place_span: (&Place<'tcx>, Span),
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ // FIXME: analogous code in check_loans first maps `place` to
+ // its base_path ... but is that what we want here?
+ let place = self.base_path(place_span.0);
+
+ let maybe_uninits = &flow_state.uninits;
+ let curr_move_outs = &flow_state.move_outs.curr_state;
+
+ // Bad scenarios:
+ //
+ // 1. Move of `a.b.c`, use of `a.b.c`
+ // 2. Move of `a.b.c`, use of `a.b.c.d` (without first reinitializing `a.b.c.d`)
+ // 3. Move of `a.b.c`, use of `a` or `a.b`
+ // 4. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`; note that with
+ // partial initialization support, one might have `a.x`
+ // initialized but not `a.b`.
+ //
+ // OK scenarios:
+ //
+ // 5. Move of `a.b.c`, use of `a.b.d`
+ // 6. Uninitialized `a.x`, initialized `a.b`, use of `a.b`
+ // 7. Copied `(a.b: &_)`, use of `*(a.b).c`; note that `a.b`
+ // must have been initialized for the use to be sound.
+ // 8. Move of `a.b.c` then reinit of `a.b.c.d`, use of `a.b.c.d`
+
+ // The dataflow tracks shallow prefixes distinctly (that is,
+ // field-accesses on P distinctly from P itself), in order to
+ // track substructure initialization separately from the whole
+ // structure.
+ //
+ // E.g., when looking at (*a.b.c).d, if the closest prefix for
+ // which we have a MovePath is `a.b`, then that means that the
+ // initialization state of `a.b` is all we need to inspect to
+ // know if `a.b.c` is valid (and from that we infer that the
+ // dereference and `.d` access is also valid, since we assume
+ // `a.b.c` is assigned a reference to a initialized and
+ // well-formed record structure.)
+
+ // Therefore, if we seek out the *closest* prefix for which we
+ // have a MovePath, that should capture the initialization
+ // state for the place scenario.
+ //
+ // This code covers scenarios 1, 2, and 4.
+
+ debug!("check_if_path_is_moved part1 place: {:?}", place);
+ match self.move_path_closest_to(place) {
+ Ok(mpi) => {
+ if maybe_uninits.curr_state.contains(&mpi) {
+ self.report_use_of_moved_or_uninitialized(
+ context,
+ desired_action,
+ place_span,
+ mpi,
+ curr_move_outs,
+ );
+ return; // don't bother finding other problems.
+ }
+ }
+ Err(NoMovePathFound::ReachedStatic) => {
+ // Okay: we do not build MoveData for static variables
+ } // Only query longest prefix with a MovePath, not further
+ // ancestors; dataflow recurs on children when parents
+ // move (to support partial (re)inits).
+ //
+ // (I.e. querying parents breaks scenario 8; but may want
+ // to do such a query based on partial-init feature-gate.)
+ }
+
+ // A move of any shallow suffix of `place` also interferes
+ // with an attempt to use `place`. This is scenario 3 above.
+ //
+ // (Distinct from handling of scenarios 1+2+4 above because
+ // `place` does not interfere with suffixes of its prefixes,
+ // e.g. `a.b.c` does not interfere with `a.b.d`)
+
+ debug!("check_if_path_is_moved part2 place: {:?}", place);
+ if let Some(mpi) = self.move_path_for_place(place) {
+ if let Some(child_mpi) = maybe_uninits.has_any_child_of(mpi) {
+ self.report_use_of_moved_or_uninitialized(
+ context,
+ desired_action,
+ place_span,
+ child_mpi,
+ curr_move_outs,
+ );
+ return; // don't bother finding other problems.
+ }
+ }
+ }
+
+ /// Currently MoveData does not store entries for all places in
+ /// the input MIR. For example it will currently filter out
+ /// places that are Copy; thus we do not track places of shared
+ /// reference type. This routine will walk up a place along its
+ /// prefixes, searching for a foundational place that *is*
+ /// tracked in the MoveData.
+ ///
+ /// An Err result includes a tag indicated why the search failed.
+ /// Currenly this can only occur if the place is built off of a
+ /// static variable, as we do not track those in the MoveData.
+ fn move_path_closest_to(
+ &mut self,
+ place: &Place<'tcx>,
+ ) -> Result<MovePathIndex, NoMovePathFound> {
+ let mut last_prefix = place;
+ for prefix in self.prefixes(place, PrefixSet::All) {
+ if let Some(mpi) = self.move_path_for_place(prefix) {
+ return Ok(mpi);
+ }
+ last_prefix = prefix;
+ }
+ match *last_prefix {
+ Place::Local(_) => panic!("should have move path for every Local"),
+ Place::Projection(_) => panic!("PrefixSet::All meant dont stop for Projection"),
+ Place::Static(_) => return Err(NoMovePathFound::ReachedStatic),
+ }
+ }
+
+ fn move_path_for_place(&mut self, place: &Place<'tcx>) -> Option<MovePathIndex> {
+ // If returns None, then there is no move path corresponding
+ // to a direct owner of `place` (which means there is nothing
+ // that borrowck tracks for its analysis).
+
+ match self.move_data.rev_lookup.find(place) {
+ LookupResult::Parent(_) => None,
+ LookupResult::Exact(mpi) => Some(mpi),
+ }
+ }
+
+ fn check_if_assigned_path_is_moved(
+ &mut self,
+ context: Context,
+ (place, span): (&Place<'tcx>, Span),
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ ) {
+ // recur down place; dispatch to check_if_path_is_moved when necessary
+ let mut place = place;
+ loop {
+ match *place {
+ Place::Local(_) | Place::Static(_) => {
+ // assigning to `x` does not require `x` be initialized.
+ break;
+ }
+ Place::Projection(ref proj) => {
+ let Projection { ref base, ref elem } = **proj;
+ match *elem {
+ ProjectionElem::Deref |
+ // assigning to *P requires `P` initialized.
+ ProjectionElem::Index(_/*operand*/) |
+ ProjectionElem::ConstantIndex { .. } |
+ // assigning to P[i] requires `P` initialized.
+ ProjectionElem::Downcast(_/*adt_def*/, _/*variant_idx*/) =>
+ // assigning to (P->variant) is okay if assigning to `P` is okay
+ //
+ // FIXME: is this true even if P is a adt with a dtor?
+ { }
+
+ ProjectionElem::Subslice { .. } => {
+ panic!("we dont allow assignments to subslices, context: {:?}",
+ context);
+ }
+
+ ProjectionElem::Field(..) => {
+ // if type of `P` has a dtor, then
+ // assigning to `P.f` requires `P` itself
+ // be already initialized
+ let tcx = self.tcx;
+ match base.ty(self.mir, tcx).to_ty(tcx).sty {
+ ty::TyAdt(def, _) if def.has_dtor(tcx) => {
+
+ // FIXME: analogous code in
+ // check_loans.rs first maps
+ // `base` to its base_path.
+
+ self.check_if_path_is_moved(
+ context, InitializationRequiringAction::Assignment,
+ (base, span), flow_state);
+
+ // (base initialized; no need to
+ // recur further)
+ break;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ place = base;
+ continue;
+ }
+ }
+ }
+ }
+
+ /// Check the permissions for the given place and read or write kind
+ ///
+ /// Returns true if an error is reported, false otherwise.
+ fn check_access_permissions(
+ &self,
+ (place, span): (&Place<'tcx>, Span),
+ kind: ReadOrWrite,
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ ) -> bool {
+ debug!(
+ "check_access_permissions({:?}, {:?}, {:?})",
+ place,
+ kind,
+ is_local_mutation_allowed
+ );
+ let mut error_reported = false;
+ match kind {
+ Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => {
+ if let Err(_place_err) = self.is_unique(place) {
+ span_bug!(span, "&unique borrow for {:?} should not fail", place);
+ }
+ }
+ Write(WriteKind::MutableBorrow(BorrowKind::Mut)) => if let Err(place_err) =
+ self.is_mutable(place, is_local_mutation_allowed)
+ {
+ error_reported = true;
+
+ let item_msg = match self.describe_place(place) {
+ Some(name) => format!("immutable item `{}`", name),
+ None => "immutable item".to_owned(),
+ };
+
+ let mut err = self.tcx
+ .cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
+ err.span_label(span, "cannot borrow as mutable");
+
+ if place != place_err {
+ if let Some(name) = self.describe_place(place_err) {
+ err.note(&format!("Value not mutable causing this error: `{}`", name));
+ }
+ }
+
+ err.emit();
+ },
+ Write(WriteKind::Mutate) => {
+ if let Err(place_err) = self.is_mutable(place, is_local_mutation_allowed) {
+ error_reported = true;
+
+ let item_msg = match self.describe_place(place) {
+ Some(name) => format!("immutable item `{}`", name),
+ None => "immutable item".to_owned(),
+ };
+
+ let mut err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
+ err.span_label(span, "cannot mutate");
+
+ if place != place_err {
+ if let Some(name) = self.describe_place(place_err) {
+ err.note(&format!("Value not mutable causing this error: `{}`", name));
+ }
+ }
+
+ err.emit();
+ }
+ }
+ Write(WriteKind::Move) |
+ Write(WriteKind::StorageDeadOrDrop) |
+ Write(WriteKind::MutableBorrow(BorrowKind::Shared)) => {
+ if let Err(_place_err) = self.is_mutable(place, is_local_mutation_allowed) {
+ self.tcx.sess.delay_span_bug(
+ span,
+ &format!(
+ "Accessing `{:?}` with the kind `{:?}` shouldn't be possible",
+ place,
+ kind
+ ),
+ );
+ }
+ }
+ Read(ReadKind::Borrow(BorrowKind::Unique)) |
+ Read(ReadKind::Borrow(BorrowKind::Mut)) |
+ Read(ReadKind::Borrow(BorrowKind::Shared)) |
+ Read(ReadKind::Copy) => {} // Access authorized
+ }
+
+ error_reported
+ }
+
+ /// Can this value be written or borrowed mutably
+ fn is_mutable<'d>(
+ &self,
+ place: &'d Place<'tcx>,
+ is_local_mutation_allowed: LocalMutationIsAllowed,
+ ) -> Result<(), &'d Place<'tcx>> {
+ match *place {
+ Place::Local(local) => {
+ let local = &self.mir.local_decls[local];
+ match local.mutability {
+ Mutability::Not => match is_local_mutation_allowed {
+ LocalMutationIsAllowed::Yes => Ok(()),
+ LocalMutationIsAllowed::No => Err(place),
+ },
+ Mutability::Mut => Ok(()),
+ }
+ }
+ Place::Static(ref static_) => if !self.tcx.is_static_mut(static_.def_id) {
+ Err(place)
+ } else {
+ Ok(())
+ },
+ Place::Projection(ref proj) => {
+ match proj.elem {
+ ProjectionElem::Deref => {
+ let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
+
+ // Check the kind of deref to decide
+ match base_ty.sty {
+ ty::TyRef(_, tnm) => {
+ match tnm.mutbl {
+ // Shared borrowed data is never mutable
+ hir::MutImmutable => Err(place),
+ // Mutably borrowed data is mutable, but only if we have a
+ // unique path to the `&mut`
+ hir::MutMutable => {
+ if self.is_upvar_field_projection(&proj.base).is_some() {
+ self.is_mutable(&proj.base, is_local_mutation_allowed)
+ } else {
+ self.is_unique(&proj.base)
+ }
+ }
+ }
+ }
+ ty::TyRawPtr(tnm) => {
+ match tnm.mutbl {
+ // `*const` raw pointers are not mutable
+ hir::MutImmutable => Err(place),
+ // `*mut` raw pointers are always mutable, regardless of context
+ // The users have to check by themselve.
+ hir::MutMutable => Ok(()),
+ }
+ }
+ // `Box<T>` owns its content, so mutable if its location is mutable
+ _ if base_ty.is_box() => {
+ self.is_mutable(&proj.base, LocalMutationIsAllowed::No)
+ }
+ // Deref should only be for reference, pointers or boxes
+ _ => bug!("Deref of unexpected type: {:?}", base_ty),
+ }
+ }
+ // All other projections are owned by their base path, so mutable if
+ // base path is mutable
+ ProjectionElem::Field(..) |
+ ProjectionElem::Index(..) |
+ ProjectionElem::ConstantIndex { .. } |
+ ProjectionElem::Subslice { .. } |
+ ProjectionElem::Downcast(..) => {
+ let field_projection = self.is_upvar_field_projection(place);
+
+ if let Some(field) = field_projection {
+ let decl = &self.mir.upvar_decls[field.index()];
+
+ return match decl.mutability {
+ Mutability::Mut => self.is_unique(&proj.base),
+ Mutability::Not => Err(place),
+ };
+ }
+
+ self.is_mutable(&proj.base, LocalMutationIsAllowed::No)
+ }
+ }
+ }
+ }
+ }
+
+ /// Does this place have a unique path
+ fn is_unique<'d>(&self, place: &'d Place<'tcx>) -> Result<(), &'d Place<'tcx>> {
+ match *place {
+ Place::Local(..) => {
+ // Local variables are unique
+ Ok(())
+ }
+ Place::Static(..) => {
+ // Static variables are not
+ Err(place)
+ }
+ Place::Projection(ref proj) => {
+ match proj.elem {
+ ProjectionElem::Deref => {
+ let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
+
+ // `Box<T>` referent is unique if box is a unique spot
+ if base_ty.is_box() {
+ return self.is_unique(&proj.base);
+ }
+
+ // Otherwise we check the kind of deref to decide
+ match base_ty.sty {
+ ty::TyRef(_, tnm) => {
+ match tnm.mutbl {
+ // place represent an aliased location
+ hir::MutImmutable => Err(place),
+ // `&mut T` is as unique as the context in which it is found
+ hir::MutMutable => self.is_unique(&proj.base),
+ }
+ }
+ ty::TyRawPtr(tnm) => {
+ match tnm.mutbl {
+ // `*mut` can be aliased, but we leave it to user
+ hir::MutMutable => Ok(()),
+ // `*const` is treated the same as `*mut`
+ hir::MutImmutable => Ok(()),
+ }
+ }
+ // Deref should only be for reference, pointers or boxes
+ _ => bug!("Deref of unexpected type: {:?}", base_ty),
+ }
+ }
+ // Other projections are unique if the base is unique
+ ProjectionElem::Field(..) |
+ ProjectionElem::Index(..) |
+ ProjectionElem::ConstantIndex { .. } |
+ ProjectionElem::Subslice { .. } |
+ ProjectionElem::Downcast(..) => self.is_unique(&proj.base),
+ }
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum NoMovePathFound {
+ ReachedStatic,
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ fn each_borrow_involving_path<F>(
+ &mut self,
+ _context: Context,
+ access_place: (ShallowOrDeep, &Place<'tcx>),
+ flow_state: &InProgress<'cx, 'gcx, 'tcx>,
+ mut op: F,
+ ) where
+ F: FnMut(&mut Self, BorrowIndex, &BorrowData<'tcx>, &Place<'tcx>) -> Control,
+ {
+ let (access, place) = access_place;
+
+ // FIXME: analogous code in check_loans first maps `place` to
+ // its base_path.
+
+ let domain = flow_state.borrows.base_results.operator();
+ let data = domain.borrows();
+
+ // check for loan restricting path P being used. Accounts for
+ // borrows of P, P.a.b, etc.
+ 'next_borrow: for i in flow_state.borrows.elems_incoming() {
+ let borrowed = &data[i];
+
+ // Is `place` (or a prefix of it) already borrowed? If
+ // so, that's relevant.
+ //
+ // FIXME: Differs from AST-borrowck; includes drive-by fix
+ // to #38899. Will probably need back-compat mode flag.
+ for accessed_prefix in self.prefixes(place, PrefixSet::All) {
+ if *accessed_prefix == borrowed.place {
+ // FIXME: pass in enum describing case we are in?
+ let ctrl = op(self, i, borrowed, accessed_prefix);
+ if ctrl == Control::Break {
+ return;
+ }
+ }
+ }
+
+ // Is `place` a prefix (modulo access type) of the
+ // `borrowed.place`? If so, that's relevant.
+
+ let prefix_kind = match access {
+ Shallow(Some(ArtificialField::Discriminant)) |
+ Shallow(Some(ArtificialField::ArrayLength)) => {
+ // The discriminant and array length are like
+ // additional fields on the type; they do not
+ // overlap any existing data there. Furthermore,
+ // they cannot actually be a prefix of any
+ // borrowed place (at least in MIR as it is
+ // currently.)
+ continue 'next_borrow;
+ }
+ Shallow(None) => PrefixSet::Shallow,
+ Deep => PrefixSet::Supporting,
+ };
+
+ for borrowed_prefix in self.prefixes(&borrowed.place, prefix_kind) {
+ if borrowed_prefix == place {
+ // FIXME: pass in enum describing case we are in?
+ let ctrl = op(self, i, borrowed, borrowed_prefix);
+ if ctrl == Control::Break {
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+use self::prefixes::PrefixSet;
+
+/// From the NLL RFC: "The deep [aka 'supporting'] prefixes for an
+/// place are formed by stripping away fields and derefs, except that
+/// we stop when we reach the deref of a shared reference. [...] "
+///
+/// "Shallow prefixes are found by stripping away fields, but stop at
+/// any dereference. So: writing a path like `a` is illegal if `a.b`
+/// is borrowed. But: writing `a` is legal if `*a` is borrowed,
+/// whether or not `a` is a shared or mutable reference. [...] "
+mod prefixes {
+ use super::MirBorrowckCtxt;
+
+ use rustc::hir;
+ use rustc::ty::{self, TyCtxt};
+ use rustc::mir::{Mir, Place, ProjectionElem};
+
+ pub trait IsPrefixOf<'tcx> {
+ fn is_prefix_of(&self, other: &Place<'tcx>) -> bool;
+ }
+
+ impl<'tcx> IsPrefixOf<'tcx> for Place<'tcx> {
+ fn is_prefix_of(&self, other: &Place<'tcx>) -> bool {
+ let mut cursor = other;
+ loop {
+ if self == cursor {
+ return true;
+ }
+
+ match *cursor {
+ Place::Local(_) | Place::Static(_) => return false,
+ Place::Projection(ref proj) => {
+ cursor = &proj.base;
+ }
+ }
+ }
+ }
+ }
+
+
+ pub(super) struct Prefixes<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
+ mir: &'cx Mir<'tcx>,
+ tcx: TyCtxt<'cx, 'gcx, 'tcx>,
+ kind: PrefixSet,
+ next: Option<&'cx Place<'tcx>>,
+ }
+
+ #[derive(Copy, Clone, PartialEq, Eq, Debug)]
+ pub(super) enum PrefixSet {
+ /// Doesn't stop until it returns the base case (a Local or
+ /// Static prefix).
+ All,
+ /// Stops at any dereference.
+ Shallow,
+ /// Stops at the deref of a shared reference.
+ Supporting,
+ }
+
+ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ /// Returns an iterator over the prefixes of `place`
+ /// (inclusive) from longest to smallest, potentially
+ /// terminating the iteration early based on `kind`.
+ pub(super) fn prefixes(
+ &self,
+ place: &'cx Place<'tcx>,
+ kind: PrefixSet,
+ ) -> Prefixes<'cx, 'gcx, 'tcx> {
+ Prefixes {
+ next: Some(place),
+ kind,
+ mir: self.mir,
+ tcx: self.tcx,
+ }
+ }
+ }
+
+ impl<'cx, 'gcx, 'tcx> Iterator for Prefixes<'cx, 'gcx, 'tcx> {
+ type Item = &'cx Place<'tcx>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut cursor = match self.next {
+ None => return None,
+ Some(place) => place,
+ };
+
+ // Post-processing `place`: Enqueue any remaining
+ // work. Also, `place` may not be a prefix itself, but
+ // may hold one further down (e.g. we never return
+ // downcasts here, but may return a base of a downcast).
+
+ 'cursor: loop {
+ let proj = match *cursor {
+ Place::Local(_) | // search yielded this leaf
+ Place::Static(_) => {
+ self.next = None;
+ return Some(cursor);
+ }
+
+ Place::Projection(ref proj) => proj,
+ };
+
+ match proj.elem {
+ ProjectionElem::Field(_ /*field*/, _ /*ty*/) => {
+ // FIXME: add union handling
+ self.next = Some(&proj.base);
+ return Some(cursor);
+ }
+ ProjectionElem::Downcast(..) |
+ ProjectionElem::Subslice { .. } |
+ ProjectionElem::ConstantIndex { .. } |
+ ProjectionElem::Index(_) => {
+ cursor = &proj.base;
+ continue 'cursor;
+ }
+ ProjectionElem::Deref => {
+ // (handled below)
+ }
+ }
+
+ assert_eq!(proj.elem, ProjectionElem::Deref);
+
+ match self.kind {
+ PrefixSet::Shallow => {
+ // shallow prefixes are found by stripping away
+ // fields, but stop at *any* dereference.
+ // So we can just stop the traversal now.
+ self.next = None;
+ return Some(cursor);
+ }
+ PrefixSet::All => {
+ // all prefixes: just blindly enqueue the base
+ // of the projection
+ self.next = Some(&proj.base);
+ return Some(cursor);
+ }
+ PrefixSet::Supporting => {
+ // fall through!
+ }
+ }
+
+ assert_eq!(self.kind, PrefixSet::Supporting);
+ // supporting prefixes: strip away fields and
+ // derefs, except we stop at the deref of a shared
+ // reference.
+
+ let ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
+ match ty.sty {
+ ty::TyRawPtr(_) |
+ ty::TyRef(
+ _, /*rgn*/
+ ty::TypeAndMut {
+ ty: _,
+ mutbl: hir::MutImmutable,
+ },
+ ) => {
+ // don't continue traversing over derefs of raw pointers or shared borrows.
+ self.next = None;
+ return Some(cursor);
+ }
+
+ ty::TyRef(
+ _, /*rgn*/
+ ty::TypeAndMut {
+ ty: _,
+ mutbl: hir::MutMutable,
+ },
+ ) => {
+ self.next = Some(&proj.base);
+ return Some(cursor);
+ }
+
+ ty::TyAdt(..) if ty.is_box() => {
+ self.next = Some(&proj.base);
+ return Some(cursor);
+ }
+
+ _ => panic!("unknown type fed to Projection Deref."),
+ }
+ }
+ }
+ }
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ fn report_use_of_moved_or_uninitialized(
+ &mut self,
+ _context: Context,
+ desired_action: InitializationRequiringAction,
+ (place, span): (&Place<'tcx>, Span),
+ mpi: MovePathIndex,
+ curr_move_out: &IdxSetBuf<MoveOutIndex>,
+ ) {
+ let mois = self.move_data.path_map[mpi]
+ .iter()
+ .filter(|moi| curr_move_out.contains(moi))
+ .collect::<Vec<_>>();
+
+ if mois.is_empty() {
+ let item_msg = match self.describe_place(place) {
+ Some(name) => format!("`{}`", name),
+ None => "value".to_owned(),
+ };
+ self.tcx
+ .cannot_act_on_uninitialized_variable(
+ span,
+ desired_action.as_noun(),
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ )
+ .span_label(span, format!("use of possibly uninitialized {}", item_msg))
+ .emit();
+ } else {
+ let msg = ""; //FIXME: add "partially " or "collaterally "
+
+ let mut err = self.tcx.cannot_act_on_moved_value(
+ span,
+ desired_action.as_noun(),
+ msg,
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ );
+
+ err.span_label(
+ span,
+ format!(
+ "value {} here after move",
+ desired_action.as_verb_in_past_tense()
+ ),
+ );
+ for moi in mois {
+ let move_msg = ""; //FIXME: add " (into closure)"
+ let move_span = self.mir.source_info(self.move_data.moves[*moi].source).span;
+ if span == move_span {
+ err.span_label(
+ span,
+ format!("value moved{} here in previous iteration of loop", move_msg),
+ );
+ } else {
+ err.span_label(move_span, format!("value moved{} here", move_msg));
+ };
+ }
+ //FIXME: add note for closure
+ err.emit();
+ }
+ }
+
+ fn report_move_out_while_borrowed(
+ &mut self,
+ _context: Context,
+ (place, span): (&Place<'tcx>, Span),
+ borrow: &BorrowData<'tcx>,
+ ) {
+ let value_msg = match self.describe_place(place) {
+ Some(name) => format!("`{}`", name),
+ None => "value".to_owned(),
+ };
+ let borrow_msg = match self.describe_place(&borrow.place) {
+ Some(name) => format!("`{}`", name),
+ None => "value".to_owned(),
+ };
+ self.tcx
+ .cannot_move_when_borrowed(
+ span,
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ )
+ .span_label(
+ self.retrieve_borrow_span(borrow),
+ format!("borrow of {} occurs here", borrow_msg),
+ )
+ .span_label(span, format!("move out of {} occurs here", value_msg))
+ .emit();
+ }
+
+ fn report_use_while_mutably_borrowed(
+ &mut self,
+ _context: Context,
+ (place, span): (&Place<'tcx>, Span),
+ borrow: &BorrowData<'tcx>,
+ ) {
+ let mut err = self.tcx.cannot_use_when_mutably_borrowed(
+ span,
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ self.retrieve_borrow_span(borrow),
+ &self.describe_place(&borrow.place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ );
+
+ err.emit();
+ }
+
+ /// Finds the span of arguments of a closure (within `maybe_closure_span`) and its usage of
+ /// the local assigned at `location`.
+ /// This is done by searching in statements succeeding `location`
+ /// and originating from `maybe_closure_span`.
+ fn find_closure_span(
+ &self,
+ maybe_closure_span: Span,
+ location: Location,
+ ) -> Option<(Span, Span)> {
+ use rustc::hir::ExprClosure;
+ use rustc::mir::AggregateKind;
+
+ let local = if let StatementKind::Assign(Place::Local(local), _) =
+ self.mir[location.block].statements[location.statement_index].kind
+ {
+ local
+ } else {
+ return None;
+ };
+
+ for stmt in &self.mir[location.block].statements[location.statement_index + 1..] {
+ if maybe_closure_span != stmt.source_info.span {
+ break;
+ }
+
+ if let StatementKind::Assign(_, Rvalue::Aggregate(ref kind, ref places)) = stmt.kind {
+ if let AggregateKind::Closure(def_id, _) = **kind {
+ debug!("find_closure_span: found closure {:?}", places);
+
+ return if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
+ let args_span = if let ExprClosure(_, _, _, span, _) =
+ self.tcx.hir.expect_expr(node_id).node
+ {
+ span
+ } else {
+ return None;
+ };
+
+ self.tcx
+ .with_freevars(node_id, |freevars| {
+ for (v, place) in freevars.iter().zip(places) {
+ match *place {
+ Operand::Copy(Place::Local(l)) |
+ Operand::Move(Place::Local(l)) if local == l =>
+ {
+ debug!(
+ "find_closure_span: found captured local {:?}",
+ l
+ );
+ return Some(v.span);
+ }
+ _ => {}
+ }
+ }
+ None
+ })
+ .map(|var_span| (args_span, var_span))
+ } else {
+ None
+ };
+ }
+ }
+ }
+
+ None
+ }
+
+ fn report_conflicting_borrow(
+ &mut self,
+ context: Context,
+ common_prefix: &Place<'tcx>,
+ (place, span): (&Place<'tcx>, Span),
+ gen_borrow_kind: BorrowKind,
+ issued_borrow: &BorrowData,
+ end_issued_loan_span: Option<Span>,
+ ) {
+ use self::prefixes::IsPrefixOf;
+
+ assert!(common_prefix.is_prefix_of(place));
+ assert!(common_prefix.is_prefix_of(&issued_borrow.place));
+
+ let issued_span = self.retrieve_borrow_span(issued_borrow);
+
+ let new_closure_span = self.find_closure_span(span, context.loc);
+ let span = new_closure_span.map(|(args, _)| args).unwrap_or(span);
+ let old_closure_span = self.find_closure_span(issued_span, issued_borrow.location);
+ let issued_span = old_closure_span
+ .map(|(args, _)| args)
+ .unwrap_or(issued_span);
+
+ let desc_place = self.describe_place(place).unwrap_or("_".to_owned());
+
+ // FIXME: supply non-"" `opt_via` when appropriate
+ let mut err = match (
+ gen_borrow_kind,
+ "immutable",
+ "mutable",
+ issued_borrow.kind,
+ "immutable",
+ "mutable",
+ ) {
+ (BorrowKind::Shared, lft, _, BorrowKind::Mut, _, rgt) |
+ (BorrowKind::Mut, _, lft, BorrowKind::Shared, rgt, _) => self.tcx
+ .cannot_reborrow_already_borrowed(
+ span,
+ &desc_place,
+ "",
+ lft,
+ issued_span,
+ "it",
+ rgt,
+ "",
+ end_issued_loan_span,
+ Origin::Mir,
+ ),
+
+ (BorrowKind::Mut, _, _, BorrowKind::Mut, _, _) => self.tcx
+ .cannot_mutably_borrow_multiply(
+ span,
+ &desc_place,
+ "",
+ issued_span,
+ "",
+ end_issued_loan_span,
+ Origin::Mir,
+ ),
+
+ (BorrowKind::Unique, _, _, BorrowKind::Unique, _, _) => self.tcx
+ .cannot_uniquely_borrow_by_two_closures(
+ span,
+ &desc_place,
+ issued_span,
+ end_issued_loan_span,
+ Origin::Mir,
+ ),
+
+ (BorrowKind::Unique, _, _, _, _, _) => self.tcx.cannot_uniquely_borrow_by_one_closure(
+ span,
+ &desc_place,
+ "",
+ issued_span,
+ "it",
+ "",
+ end_issued_loan_span,
+ Origin::Mir,
+ ),
+
+ (_, _, _, BorrowKind::Unique, _, _) => self.tcx
+ .cannot_reborrow_already_uniquely_borrowed(
+ span,
+ &desc_place,
+ "it",
+ "",
+ issued_span,
+ "",
+ end_issued_loan_span,
+ Origin::Mir,
+ ),
+
+ (BorrowKind::Shared, _, _, BorrowKind::Shared, _, _) => unreachable!(),
+ };
+
+ if let Some((_, var_span)) = old_closure_span {
+ err.span_label(
+ var_span,
+ format!(
+ "previous borrow occurs due to use of `{}` in closure",
+ desc_place
+ ),
+ );
+ }
+
+ if let Some((_, var_span)) = new_closure_span {
+ err.span_label(
+ var_span,
+ format!("borrow occurs due to use of `{}` in closure", desc_place),
+ );
+ }
+
+ err.emit();
+ }
+
+ fn report_borrowed_value_does_not_live_long_enough(
+ &mut self,
+ _: Context,
+ (place, span): (&Place<'tcx>, Span),
+ end_span: Option<Span>,
+ ) {
+ let root_place = self.prefixes(place, PrefixSet::All).last().unwrap();
+ let proper_span = match *root_place {
+ Place::Local(local) => self.mir.local_decls[local].source_info.span,
+ _ => span,
+ };
+ let mut err = self.tcx
+ .path_does_not_live_long_enough(span, "borrowed value", Origin::Mir);
+ err.span_label(proper_span, "temporary value created here");
+ err.span_label(span, "temporary value dropped here while still borrowed");
+ err.note("consider using a `let` binding to increase its lifetime");
+
+ if let Some(end) = end_span {
+ err.span_label(end, "temporary value needs to live until here");
+ }
+
+ err.emit();
+ }
+
+ fn report_illegal_mutation_of_borrowed(
+ &mut self,
+ _: Context,
+ (place, span): (&Place<'tcx>, Span),
+ loan: &BorrowData,
+ ) {
+ let mut err = self.tcx.cannot_assign_to_borrowed(
+ span,
+ self.retrieve_borrow_span(loan),
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ );
+
+ err.emit();
+ }
+
+ fn report_illegal_reassignment(
+ &mut self,
+ _context: Context,
+ (place, span): (&Place<'tcx>, Span),
+ assigned_span: Span,
+ ) {
+ let mut err = self.tcx.cannot_reassign_immutable(
+ span,
+ &self.describe_place(place).unwrap_or("_".to_owned()),
+ Origin::Mir,
+ );
+ err.span_label(span, "cannot assign twice to immutable variable");
+ if span != assigned_span {
+ let value_msg = match self.describe_place(place) {
+ Some(name) => format!("`{}`", name),
+ None => "value".to_owned(),
+ };
+ err.span_label(assigned_span, format!("first assignment to {}", value_msg));
+ }
+ err.emit();
+ }
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ // End-user visible description of `place` if one can be found. If the
+ // place is a temporary for instance, None will be returned.
+ fn describe_place(&self, place: &Place<'tcx>) -> Option<String> {
+ let mut buf = String::new();
+ match self.append_place_to_string(place, &mut buf, false) {
+ Ok(()) => Some(buf),
+ Err(()) => None,
+ }
+ }
+
+ /// If this is a field projection, and the field is being projected from a closure type,
+ /// then returns the index of the field being projected. Note that this closure will always
+ /// be `self` in the current MIR, because that is the only time we directly access the fields
+ /// of a closure type.
+ fn is_upvar_field_projection(&self, place: &Place<'tcx>) -> Option<Field> {
+ match *place {
+ Place::Projection(ref proj) => match proj.elem {
+ ProjectionElem::Field(field, _ty) => {
+ let is_projection_from_ty_closure = proj.base
+ .ty(self.mir, self.tcx)
+ .to_ty(self.tcx)
+ .is_closure();
+
+ if is_projection_from_ty_closure {
+ Some(field)
+ } else {
+ None
+ }
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+
+ // Appends end-user visible description of `place` to `buf`.
+ fn append_place_to_string(
+ &self,
+ place: &Place<'tcx>,
+ buf: &mut String,
+ mut autoderef: bool,
+ ) -> Result<(), ()> {
+ match *place {
+ Place::Local(local) => {
+ self.append_local_to_string(local, buf)?;
+ }
+ Place::Static(ref static_) => {
+ buf.push_str(&format!("{}", &self.tcx.item_name(static_.def_id)));
+ }
+ Place::Projection(ref proj) => {
+ match proj.elem {
+ ProjectionElem::Deref => {
+ if let Some(field) = self.is_upvar_field_projection(&proj.base) {
+ let var_index = field.index();
+ let name = self.mir.upvar_decls[var_index].debug_name.to_string();
+ if self.mir.upvar_decls[var_index].by_ref {
+ buf.push_str(&name);
+ } else {
+ buf.push_str(&format!("*{}", &name));
+ }
+ } else {
+ if autoderef {
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ } else {
+ buf.push_str(&"*");
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ }
+ }
+ }
+ ProjectionElem::Downcast(..) => {
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ }
+ ProjectionElem::Field(field, _ty) => {
+ autoderef = true;
+
+ if let Some(field) = self.is_upvar_field_projection(place) {
+ let var_index = field.index();
+ let name = self.mir.upvar_decls[var_index].debug_name.to_string();
+ buf.push_str(&name);
+ } else {
+ let field_name = self.describe_field(&proj.base, field);
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ buf.push_str(&format!(".{}", field_name));
+ }
+ }
+ ProjectionElem::Index(index) => {
+ autoderef = true;
+
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ buf.push_str("[");
+ if let Err(_) = self.append_local_to_string(index, buf) {
+ buf.push_str("..");
+ }
+ buf.push_str("]");
+ }
+ ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
+ autoderef = true;
+ // Since it isn't possible to borrow an element on a particular index and
+ // then use another while the borrow is held, don't output indices details
+ // to avoid confusing the end-user
+ self.append_place_to_string(&proj.base, buf, autoderef)?;
+ buf.push_str(&"[..]");
+ }
+ };
+ }
+ }
+
+ Ok(())
+ }
+
+ // Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have
+ // a name, then `Err` is returned
+ fn append_local_to_string(&self, local_index: Local, buf: &mut String) -> Result<(), ()> {
+ let local = &self.mir.local_decls[local_index];
+ match local.name {
+ Some(name) => {
+ buf.push_str(&format!("{}", name));
+ Ok(())
+ }
+ None => Err(()),
+ }
+ }
+
+ // End-user visible description of the `field`nth field of `base`
+ fn describe_field(&self, base: &Place, field: Field) -> String {
+ match *base {
+ Place::Local(local) => {
+ let local = &self.mir.local_decls[local];
+ self.describe_field_from_ty(&local.ty, field)
+ }
+ Place::Static(ref static_) => self.describe_field_from_ty(&static_.ty, field),
+ Place::Projection(ref proj) => match proj.elem {
+ ProjectionElem::Deref => self.describe_field(&proj.base, field),
+ ProjectionElem::Downcast(def, variant_index) => {
+ format!("{}", def.variants[variant_index].fields[field.index()].name)
+ }
+ ProjectionElem::Field(_, field_type) => {
+ self.describe_field_from_ty(&field_type, field)
+ }
+ ProjectionElem::Index(..) |
+ ProjectionElem::ConstantIndex { .. } |
+ ProjectionElem::Subslice { .. } => {
+ format!("{}", self.describe_field(&proj.base, field))
+ }
+ },
+ }
+ }
+
+ // End-user visible description of the `field_index`nth field of `ty`
+ fn describe_field_from_ty(&self, ty: &ty::Ty, field: Field) -> String {
+ if ty.is_box() {
+ // If the type is a box, the field is described from the boxed type
+ self.describe_field_from_ty(&ty.boxed_ty(), field)
+ } else {
+ match ty.sty {
+ ty::TyAdt(def, _) => if def.is_enum() {
+ format!("{}", field.index())
+ } else {
+ format!("{}", def.struct_variant().fields[field.index()].name)
+ },
+ ty::TyTuple(_, _) => format!("{}", field.index()),
+ ty::TyRef(_, tnm) | ty::TyRawPtr(tnm) => {
+ self.describe_field_from_ty(&tnm.ty, field)
+ }
+ ty::TyArray(ty, _) | ty::TySlice(ty) => self.describe_field_from_ty(&ty, field),
+ ty::TyClosure(closure_def_id, _) => {
+ // Convert the def-id into a node-id. node-ids are only valid for
+ // the local code in the current crate, so this returns an `Option` in case
+ // the closure comes from another crate. But in that case we wouldn't
+ // be borrowck'ing it, so we can just unwrap:
+ let node_id = self.tcx.hir.as_local_node_id(closure_def_id).unwrap();
+ let freevar = self.tcx.with_freevars(node_id, |fv| fv[field.index()]);
+
+ self.tcx.hir.name(freevar.var_id()).to_string()
+ }
+ _ => {
+ // Might need a revision when the fields in trait RFC is implemented
+ // (https://github.com/rust-lang/rfcs/pull/1546)
+ bug!(
+ "End-user description not implemented for field access on `{:?}`",
+ ty.sty
+ );
+ }
+ }
+ }
+ }
+
+ // Retrieve span of given borrow from the current MIR representation
+ fn retrieve_borrow_span(&self, borrow: &BorrowData) -> Span {
+ self.mir.source_info(borrow.location).span
+ }
+}
+
+impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
+ // FIXME (#16118): function intended to allow the borrow checker
+ // to be less precise in its handling of Box while still allowing
+ // moves out of a Box. They should be removed when/if we stop
+ // treating Box specially (e.g. when/if DerefMove is added...)
+
+ fn base_path<'d>(&self, place: &'d Place<'tcx>) -> &'d Place<'tcx> {
+ //! Returns the base of the leftmost (deepest) dereference of an
+ //! Box in `place`. If there is no dereference of an Box
+ //! in `place`, then it just returns `place` itself.
+
+ let mut cursor = place;
+ let mut deepest = place;
+ loop {
+ let proj = match *cursor {
+ Place::Local(..) | Place::Static(..) => return deepest,
+ Place::Projection(ref proj) => proj,
+ };
+ if proj.elem == ProjectionElem::Deref
+ && place.ty(self.mir, self.tcx).to_ty(self.tcx).is_box()
+ {
+ deepest = &proj.base;
+ }
+ cursor = &proj.base;
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+struct Context {
+ kind: ContextKind,
+ loc: Location,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum ContextKind {
+ AssignLhs,
+ AssignRhs,
+ SetDiscrim,
+ InlineAsm,
+ SwitchInt,
+ Drop,
+ DropAndReplace,
+ CallOperator,
+ CallOperand,
+ CallDest,
+ Assert,
+ Yield,
+ StorageDead,
+}
+
+impl ContextKind {
+ fn new(self, loc: Location) -> Context {
+ Context {
+ kind: self,
+ loc: loc,
+ }
+ }
+}
+
+impl<'b, 'gcx, 'tcx> InProgress<'b, 'gcx, 'tcx> {
+ fn new(
+ borrows: FlowInProgress<Borrows<'b, 'gcx, 'tcx>>,
+ inits: FlowInProgress<MaybeInitializedLvals<'b, 'gcx, 'tcx>>,
+ uninits: FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>,
+ move_outs: FlowInProgress<MovingOutStatements<'b, 'gcx, 'tcx>>,
+ ever_inits: FlowInProgress<EverInitializedLvals<'b, 'gcx, 'tcx>>,
+ ) -> Self {
+ InProgress {
+ borrows,
+ inits,
+ uninits,
+ move_outs,
+ ever_inits,
+ }
+ }
+
+ fn each_flow<XB, XI, XU, XM, XE>(
+ &mut self,
+ mut xform_borrows: XB,
+ mut xform_inits: XI,
+ mut xform_uninits: XU,
+ mut xform_move_outs: XM,
+ mut xform_ever_inits: XE,
+ ) where
+ XB: FnMut(&mut FlowInProgress<Borrows<'b, 'gcx, 'tcx>>),
+ XI: FnMut(&mut FlowInProgress<MaybeInitializedLvals<'b, 'gcx, 'tcx>>),
+ XU: FnMut(&mut FlowInProgress<MaybeUninitializedLvals<'b, 'gcx, 'tcx>>),
+ XM: FnMut(&mut FlowInProgress<MovingOutStatements<'b, 'gcx, 'tcx>>),
+ XE: FnMut(&mut FlowInProgress<EverInitializedLvals<'b, 'gcx, 'tcx>>),
+ {
+ xform_borrows(&mut self.borrows);
+ xform_inits(&mut self.inits);
+ xform_uninits(&mut self.uninits);
+ xform_move_outs(&mut self.move_outs);
+ xform_ever_inits(&mut self.ever_inits);
+ }
+
+ fn summary(&self) -> String {
+ let mut s = String::new();
+
+ s.push_str("borrows in effect: [");
+ let mut saw_one = false;
+ self.borrows.each_state_bit(|borrow| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let borrow_data = &self.borrows.base_results.operator().borrows()[borrow];
+ s.push_str(&format!("{}", borrow_data));
+ });
+ s.push_str("] ");
+
+ s.push_str("borrows generated: [");
+ let mut saw_one = false;
+ self.borrows.each_gen_bit(|borrow| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let borrow_data = &self.borrows.base_results.operator().borrows()[borrow];
+ s.push_str(&format!("{}", borrow_data));
+ });
+ s.push_str("] ");
+
+ s.push_str("inits: [");
+ let mut saw_one = false;
+ self.inits.each_state_bit(|mpi_init| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let move_path = &self.inits.base_results.operator().move_data().move_paths[mpi_init];
+ s.push_str(&format!("{}", move_path));
+ });
+ s.push_str("] ");
+
+ s.push_str("uninits: [");
+ let mut saw_one = false;
+ self.uninits.each_state_bit(|mpi_uninit| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let move_path =
+ &self.uninits.base_results.operator().move_data().move_paths[mpi_uninit];
+ s.push_str(&format!("{}", move_path));
+ });
+ s.push_str("] ");
+
+ s.push_str("move_out: [");
+ let mut saw_one = false;
+ self.move_outs.each_state_bit(|mpi_move_out| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let move_out = &self.move_outs.base_results.operator().move_data().moves[mpi_move_out];
+ s.push_str(&format!("{:?}", move_out));
+ });
+ s.push_str("] ");
+
+ s.push_str("ever_init: [");
+ let mut saw_one = false;
+ self.ever_inits.each_state_bit(|mpi_ever_init| {
+ if saw_one {
+ s.push_str(", ");
+ };
+ saw_one = true;
+ let ever_init =
+ &self.ever_inits.base_results.operator().move_data().inits[mpi_ever_init];
+ s.push_str(&format!("{:?}", ever_init));
+ });
+ s.push_str("]");
+
+ return s;
+ }
+}
+
+impl<'tcx, T> FlowInProgress<T>
+where
+ T: HasMoveData<'tcx> + BitDenotation<Idx = MovePathIndex>,
+{
+ fn has_any_child_of(&self, mpi: T::Idx) -> Option<T::Idx> {
+ let move_data = self.base_results.operator().move_data();
+
+ let mut todo = vec![mpi];
+ let mut push_siblings = false; // don't look at siblings of original `mpi`.
+ while let Some(mpi) = todo.pop() {
+ if self.curr_state.contains(&mpi) {
+ return Some(mpi);
+ }
+ let move_path = &move_data.move_paths[mpi];
+ if let Some(child) = move_path.first_child {
+ todo.push(child);
+ }
+ if push_siblings {
+ if let Some(sibling) = move_path.next_sibling {
+ todo.push(sibling);
+ }
+ } else {
+ // after we've processed the original `mpi`, we should
+ // always traverse the siblings of any of its
+ // children.
+ push_siblings = true;
+ }
+ }
+ return None;
+ }
+}
+
+impl<BD> FlowInProgress<BD>
+where
+ BD: BitDenotation,
+{
+ fn each_state_bit<F>(&self, f: F)
+ where
+ F: FnMut(BD::Idx),
+ {
+ self.curr_state
+ .each_bit(self.base_results.operator().bits_per_block(), f)
+ }
+
+ fn each_gen_bit<F>(&self, f: F)
+ where
+ F: FnMut(BD::Idx),
+ {
+ self.stmt_gen
+ .each_bit(self.base_results.operator().bits_per_block(), f)
+ }
+
+ fn new(results: DataflowResults<BD>) -> Self {
+ let bits_per_block = results.sets().bits_per_block();
+ let curr_state = IdxSetBuf::new_empty(bits_per_block);
+ let stmt_gen = IdxSetBuf::new_empty(bits_per_block);
+ let stmt_kill = IdxSetBuf::new_empty(bits_per_block);
+ FlowInProgress {
+ base_results: results,
+ curr_state: curr_state,
+ stmt_gen: stmt_gen,
+ stmt_kill: stmt_kill,
+ }
+ }
+
+ fn reset_to_entry_of(&mut self, bb: BasicBlock) {
+ (*self.curr_state).clone_from(self.base_results.sets().on_entry_set_for(bb.index()));
+ }
+
+ fn reconstruct_statement_effect(&mut self, loc: Location) {
+ self.stmt_gen.reset_to_empty();
+ self.stmt_kill.reset_to_empty();
+ let mut ignored = IdxSetBuf::new_empty(0);
+ let mut sets = BlockSets {
+ on_entry: &mut ignored,
+ gen_set: &mut self.stmt_gen,
+ kill_set: &mut self.stmt_kill,
+ };
+ self.base_results
+ .operator()
+ .statement_effect(&mut sets, loc);
+ }
+
+ fn reconstruct_terminator_effect(&mut self, loc: Location) {
+ self.stmt_gen.reset_to_empty();
+ self.stmt_kill.reset_to_empty();
+ let mut ignored = IdxSetBuf::new_empty(0);
+ let mut sets = BlockSets {
+ on_entry: &mut ignored,
+ gen_set: &mut self.stmt_gen,
+ kill_set: &mut self.stmt_kill,
+ };
+ self.base_results
+ .operator()
+ .terminator_effect(&mut sets, loc);
+ }
+
+ fn apply_local_effect(&mut self) {
+ self.curr_state.union(&self.stmt_gen);
+ self.curr_state.subtract(&self.stmt_kill);
+ }
+
+ fn elems_incoming(&self) -> indexed_set::Elems<BD::Idx> {
+ let univ = self.base_results.sets().bits_per_block();
+ self.curr_state.elems(univ)
+ }
+
+ fn with_elems_outgoing<F>(&self, f: F)
+ where
+ F: FnOnce(indexed_set::Elems<BD::Idx>),
+ {
+ let mut curr_state = self.curr_state.clone();
+ curr_state.union(&self.stmt_gen);
+ curr_state.subtract(&self.stmt_kill);
+ let univ = self.base_results.sets().bits_per_block();
+ f(curr_state.elems(univ));
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use rustc::hir;
+use rustc::mir::{Local, Location, Place, Mir, Rvalue};
+use rustc::mir::visit::Visitor;
+use rustc::mir::Place::Projection;
+use rustc::mir::{PlaceProjection, ProjectionElem};
+use rustc::infer::InferCtxt;
+use rustc::traits::{self, ObligationCause};
+use rustc::ty::{self, Ty};
+use rustc::ty::fold::TypeFoldable;
+use rustc::util::common::ErrorReported;
+use rustc_data_structures::fx::FxHashSet;
+use syntax::codemap::DUMMY_SP;
+use borrow_check::FlowInProgress;
+use dataflow::MaybeInitializedLvals;
+use dataflow::move_paths::{MoveData, HasMoveData};
+
+use super::LivenessResults;
+use super::ToRegionVid;
+use super::region_infer::RegionInferenceContext;
+
+pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
+ regioncx: &mut RegionInferenceContext<'tcx>,
+ mir: &Mir<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ liveness: &LivenessResults,
+ flow_inits: &mut FlowInProgress<MaybeInitializedLvals<'cx, 'gcx, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+) {
+ ConstraintGeneration {
+ infcx,
+ regioncx,
+ mir,
+ liveness,
+ param_env,
+ flow_inits,
+ move_data,
+ }.add_constraints();
+}
+
+/// 'cg = the duration of the constraint generation process itself.
+struct ConstraintGeneration<'cg, 'cx: 'cg, 'gcx: 'tcx, 'tcx: 'cx> {
+ infcx: &'cg InferCtxt<'cx, 'gcx, 'tcx>,
+ regioncx: &'cg mut RegionInferenceContext<'tcx>,
+ mir: &'cg Mir<'tcx>,
+ liveness: &'cg LivenessResults,
+ param_env: ty::ParamEnv<'tcx>,
+ flow_inits: &'cg mut FlowInProgress<MaybeInitializedLvals<'cx, 'gcx, 'tcx>>,
+ move_data: &'cg MoveData<'tcx>,
+}
+
+impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
+ fn add_constraints(&mut self) {
+ self.add_liveness_constraints();
+ self.add_borrow_constraints();
+ }
+
+ /// Liveness constraints:
+ ///
+ /// > If a variable V is live at point P, then all regions R in the type of V
+ /// > must include the point P.
+ fn add_liveness_constraints(&mut self) {
+ debug!("add_liveness_constraints()");
+ for bb in self.mir.basic_blocks().indices() {
+ debug!("add_liveness_constraints: bb={:?}", bb);
+
+ self.liveness
+ .regular
+ .simulate_block(self.mir, bb, |location, live_locals| {
+ for live_local in live_locals.iter() {
+ let live_local_ty = self.mir.local_decls[live_local].ty;
+ self.add_regular_live_constraint(live_local_ty, location);
+ }
+ });
+
+ let mut all_live_locals: Vec<(Location, Vec<Local>)> = vec![];
+ self.liveness.drop.simulate_block(self.mir, bb, |location, live_locals| {
+ all_live_locals.push((location, live_locals.iter().collect()));
+ });
+ debug!("add_liveness_constraints: all_live_locals={:#?}", all_live_locals);
+
+ let terminator_index = self.mir.basic_blocks()[bb].statements.len();
+ self.flow_inits.reset_to_entry_of(bb);
+ while let Some((location, live_locals)) = all_live_locals.pop() {
+ for live_local in live_locals {
+ debug!("add_liveness_constraints: location={:?} live_local={:?}", location,
+ live_local);
+
+ self.flow_inits.each_state_bit(|mpi_init| {
+ debug!("add_liveness_constraints: location={:?} initialized={:?}",
+ location,
+ &self.flow_inits
+ .base_results
+ .operator()
+ .move_data()
+ .move_paths[mpi_init]);
+ });
+
+ let mpi = self.move_data.rev_lookup.find_local(live_local);
+ if let Some(initialized_child) = self.flow_inits.has_any_child_of(mpi) {
+ debug!("add_liveness_constraints: mpi={:?} has initialized child {:?}",
+ self.move_data.move_paths[mpi],
+ self.move_data.move_paths[initialized_child]);
+
+ let live_local_ty = self.mir.local_decls[live_local].ty;
+ self.add_drop_live_constraint(live_local_ty, location);
+ }
+ }
+
+ if location.statement_index == terminator_index {
+ debug!("add_liveness_constraints: reconstruct_terminator_effect from {:#?}",
+ location);
+ self.flow_inits.reconstruct_terminator_effect(location);
+ } else {
+ debug!("add_liveness_constraints: reconstruct_statement_effect from {:#?}",
+ location);
+ self.flow_inits.reconstruct_statement_effect(location);
+ }
+ self.flow_inits.apply_local_effect();
+ }
+ }
+ }
+
+ /// Some variable with type `live_ty` is "regular live" at
+ /// `location` -- i.e., it may be used later. This means that all
+ /// regions appearing in the type `live_ty` must be live at
+ /// `location`.
+ fn add_regular_live_constraint<T>(&mut self, live_ty: T, location: Location)
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ debug!(
+ "add_regular_live_constraint(live_ty={:?}, location={:?})",
+ live_ty,
+ location
+ );
+
+ self.infcx
+ .tcx
+ .for_each_free_region(&live_ty, |live_region| {
+ let vid = live_region.to_region_vid();
+ self.regioncx.add_live_point(vid, location);
+ });
+ }
+
+ /// Some variable with type `live_ty` is "drop live" at `location`
+ /// -- i.e., it may be dropped later. This means that *some* of
+ /// the regions in its type must be live at `location`. The
+ /// precise set will depend on the dropck constraints, and in
+ /// particular this takes `#[may_dangle]` into account.
+ fn add_drop_live_constraint(&mut self, dropped_ty: Ty<'tcx>, location: Location) {
+ debug!(
+ "add_drop_live_constraint(dropped_ty={:?}, location={:?})",
+ dropped_ty,
+ location
+ );
+
+ let tcx = self.infcx.tcx;
+ let mut types = vec![(dropped_ty, 0)];
+ let mut known = FxHashSet();
+ while let Some((ty, depth)) = types.pop() {
+ let span = DUMMY_SP; // FIXME
+ let result = match tcx.dtorck_constraint_for_ty(span, dropped_ty, depth, ty) {
+ Ok(result) => result,
+ Err(ErrorReported) => {
+ continue;
+ }
+ };
+
+ let ty::DtorckConstraint {
+ outlives,
+ dtorck_types,
+ } = result;
+
+ // All things in the `outlives` array may be touched by
+ // the destructor and must be live at this point.
+ for outlive in outlives {
+ if let Some(ty) = outlive.as_type() {
+ self.add_regular_live_constraint(ty, location);
+ } else if let Some(r) = outlive.as_region() {
+ self.add_regular_live_constraint(r, location);
+ } else {
+ bug!()
+ }
+ }
+
+ // However, there may also be some types that
+ // `dtorck_constraint_for_ty` could not resolve (e.g.,
+ // associated types and parameters). We need to normalize
+ // associated types here and possibly recursively process.
+ for ty in dtorck_types {
+ let cause = ObligationCause::dummy();
+ // We know that our original `dropped_ty` is well-formed,
+ // so region obligations resulting from this normalization
+ // should always hold.
+ //
+ // Therefore we ignore them instead of trying to match
+ // them up with a location.
+ let fulfillcx = traits::FulfillmentContext::new_ignoring_regions();
+ match traits::fully_normalize_with_fulfillcx(
+ self.infcx, fulfillcx, cause, self.param_env, &ty
+ ) {
+ Ok(ty) => match ty.sty {
+ ty::TyParam(..) | ty::TyProjection(..) | ty::TyAnon(..) => {
+ self.add_regular_live_constraint(ty, location);
+ }
+
+ _ => if known.insert(ty) {
+ types.push((ty, depth + 1));
+ },
+ },
+
+ Err(errors) => {
+ self.infcx.report_fulfillment_errors(&errors, None);
+ }
+ }
+ }
+ }
+ }
+
+ fn add_borrow_constraints(&mut self) {
+ self.visit_mir(self.mir);
+ }
+
+ 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;
+
+ 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;
+
+ if let ty::TyRef(base_region, ty::TypeAndMut{ ty: _, mutbl }) = *base_sty {
+ match mutbl {
+ hir::Mutability::MutImmutable => { },
+
+ hir::Mutability::MutMutable => {
+ self.add_reborrow_constraint(location, borrow_region, base);
+ },
+ }
+
+ 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());
+ }
+ }
+ }
+ }
+}
+
+impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx, 'tcx> {
+ fn visit_rvalue(&mut self,
+ rvalue: &Rvalue<'tcx>,
+ location: Location) {
+ debug!("visit_rvalue(rvalue={:?}, location={:?})", rvalue, location);
+
+ // Look for an rvalue like:
+ //
+ // & L
+ //
+ // where L is the path that is borrowed. In that case, we have
+ // to add the reborrow constraints (which don't fall out
+ // naturally from the type-checker).
+ if let Rvalue::Ref(region, _bk, ref borrowed_place) = *rvalue {
+ self.add_reborrow_constraint(location, region, borrowed_place);
+ }
+
+ self.super_rvalue(rvalue, location);
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Code to extract the free regions declared on a function and the
+//! relationships between them. For example:
+//!
+//! ```
+//! fn foo<'a, 'b, 'c: 'b>() { }
+//! ```
+//!
+//! here we would be returning a map assigning each of `{'a, 'b, 'c}`
+//! to an index, as well as the `FreeRegionMap` which can compute
+//! relationships between them.
+//!
+//! The code in this file doesn't *do anything* with those results; it
+//! just returns them for other code to use.
+
+use rustc::hir::def_id::DefId;
+use rustc::infer::InferCtxt;
+use rustc::middle::free_region::FreeRegionMap;
+use rustc::ty::{self, RegionVid};
+use rustc::ty::subst::Substs;
+use rustc::util::nodemap::FxHashMap;
+use rustc_data_structures::indexed_vec::Idx;
+
+#[derive(Debug)]
+pub struct FreeRegions<'tcx> {
+ /// Given a free region defined on this function (either early- or
+ /// late-bound), this maps it to its internal region index. When
+ /// the region context is created, the first N variables will be
+ /// created based on these indices.
+ pub indices: FxHashMap<ty::Region<'tcx>, RegionVid>,
+
+ /// The map from the typeck tables telling us how to relate free regions.
+ pub free_region_map: &'tcx FreeRegionMap<'tcx>,
+}
+
+pub fn free_regions<'a, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'a, 'gcx, 'tcx>,
+ item_def_id: DefId,
+) -> FreeRegions<'tcx> {
+ debug!("free_regions(item_def_id={:?})", item_def_id);
+
+ let mut indices = FxHashMap();
+
+ // `'static` is always free.
+ insert_free_region(&mut indices, infcx.tcx.types.re_static);
+
+ // Extract the early regions.
+ let item_substs = Substs::identity_for_item(infcx.tcx, item_def_id);
+ for item_subst in item_substs {
+ if let Some(region) = item_subst.as_region() {
+ insert_free_region(&mut indices, region);
+ }
+ }
+
+ // Extract the late-bound regions. Use the liberated fn sigs,
+ // where the late-bound regions will have been converted into free
+ // regions, and add them to the map.
+ let item_id = infcx.tcx.hir.as_local_node_id(item_def_id).unwrap();
+ let fn_hir_id = infcx.tcx.hir.node_to_hir_id(item_id);
+ let tables = infcx.tcx.typeck_tables_of(item_def_id);
+ let fn_sig = tables.liberated_fn_sigs()[fn_hir_id].clone();
+ infcx
+ .tcx
+ .for_each_free_region(&fn_sig.inputs_and_output, |region| {
+ if let ty::ReFree(_) = *region {
+ insert_free_region(&mut indices, region);
+ }
+ });
+
+ debug!("free_regions: indices={:#?}", indices);
+
+ FreeRegions { indices, free_region_map: &tables.free_region_map }
+}
+
+fn insert_free_region<'tcx>(
+ free_regions: &mut FxHashMap<ty::Region<'tcx>, RegionVid>,
+ region: ty::Region<'tcx>,
+) {
+ let next = RegionVid::new(free_regions.len());
+ free_regions.entry(region).or_insert(next);
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use rustc::hir::def_id::DefId;
+use rustc::mir::Mir;
+use rustc::infer::InferCtxt;
+use rustc::ty::{self, RegionKind, RegionVid};
+use rustc::util::nodemap::FxHashMap;
+use std::collections::BTreeSet;
+use transform::MirSource;
+use transform::type_check;
+use util::liveness::{self, LivenessMode, LivenessResult, LocalSet};
+use borrow_check::FlowInProgress;
+use dataflow::MaybeInitializedLvals;
+use dataflow::move_paths::MoveData;
+
+use util as mir_util;
+use self::mir_util::PassWhere;
+
+mod constraint_generation;
+mod subtype_constraint_generation;
+mod free_regions;
+use self::free_regions::FreeRegions;
+
+pub(crate) mod region_infer;
+use self::region_infer::RegionInferenceContext;
+
+mod renumber;
+
+/// Rewrites the regions in the MIR to use NLL variables, also
+/// scraping out the set of free regions (e.g., region parameters)
+/// declared on the function. That set will need to be given to
+/// `compute_regions`.
+pub(in borrow_check) fn replace_regions_in_mir<'cx, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
+ def_id: DefId,
+ mir: &mut Mir<'tcx>,
+) -> FreeRegions<'tcx> {
+ // Compute named region information.
+ let free_regions = free_regions::free_regions(infcx, def_id);
+
+ // Replace all regions with fresh inference variables.
+ renumber::renumber_mir(infcx, &free_regions, mir);
+
+ free_regions
+}
+
+/// Computes the (non-lexical) regions from the input MIR.
+///
+/// This may result in errors being reported.
+pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
+ def_id: DefId,
+ free_regions: FreeRegions<'tcx>,
+ mir: &Mir<'tcx>,
+ param_env: ty::ParamEnv<'gcx>,
+ flow_inits: &mut FlowInProgress<MaybeInitializedLvals<'cx, 'gcx, 'tcx>>,
+ move_data: &MoveData<'tcx>,
+) -> RegionInferenceContext<'tcx> {
+ // Run the MIR type-checker.
+ let mir_node_id = infcx.tcx.hir.as_local_node_id(def_id).unwrap();
+ let constraint_sets = &type_check::type_check(infcx, mir_node_id, param_env, mir);
+
+ // Create the region inference context, taking ownership of the region inference
+ // data that was contained in `infcx`.
+ let var_origins = infcx.take_region_var_origins();
+ let mut regioncx = RegionInferenceContext::new(var_origins, &free_regions, mir);
+ subtype_constraint_generation::generate(&mut regioncx, &free_regions, mir, constraint_sets);
+
+ // Compute what is live where.
+ let liveness = &LivenessResults {
+ regular: liveness::liveness_of_locals(
+ &mir,
+ LivenessMode {
+ include_regular_use: true,
+ include_drops: false,
+ },
+ ),
+
+ drop: liveness::liveness_of_locals(
+ &mir,
+ LivenessMode {
+ include_regular_use: false,
+ include_drops: true,
+ },
+ ),
+ };
+
+ // Generate non-subtyping constraints.
+ constraint_generation::generate_constraints(
+ infcx,
+ &mut regioncx,
+ &mir,
+ param_env,
+ liveness,
+ flow_inits,
+ move_data,
+ );
+
+ // Solve the region constraints.
+ regioncx.solve(infcx, &mir);
+
+ // Dump MIR results into a file, if that is enabled. This let us
+ // write unit-tests.
+ dump_mir_results(infcx, liveness, MirSource::item(def_id), &mir, ®ioncx);
+
+ regioncx
+}
+
+struct LivenessResults {
+ regular: LivenessResult,
+ drop: LivenessResult,
+}
+
+fn dump_mir_results<'a, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'a, 'gcx, 'tcx>,
+ liveness: &LivenessResults,
+ source: MirSource,
+ mir: &Mir<'tcx>,
+ regioncx: &RegionInferenceContext,
+) {
+ if !mir_util::dump_enabled(infcx.tcx, "nll", source) {
+ return;
+ }
+
+ let regular_liveness_per_location: FxHashMap<_, _> = mir.basic_blocks()
+ .indices()
+ .flat_map(|bb| {
+ let mut results = vec![];
+ liveness
+ .regular
+ .simulate_block(&mir, bb, |location, local_set| {
+ results.push((location, local_set.clone()));
+ });
+ results
+ })
+ .collect();
+
+ let drop_liveness_per_location: FxHashMap<_, _> = mir.basic_blocks()
+ .indices()
+ .flat_map(|bb| {
+ let mut results = vec![];
+ liveness
+ .drop
+ .simulate_block(&mir, bb, |location, local_set| {
+ results.push((location, local_set.clone()));
+ });
+ results
+ })
+ .collect();
+
+ mir_util::dump_mir(infcx.tcx, None, "nll", &0, source, mir, |pass_where, out| {
+ match pass_where {
+ // Before the CFG, dump out the values for each region variable.
+ PassWhere::BeforeCFG => for region in regioncx.regions() {
+ writeln!(out, "| {:?}: {:?}", region, regioncx.region_value(region))?;
+ },
+
+ // Before each basic block, dump out the values
+ // that are live on entry to the basic block.
+ PassWhere::BeforeBlock(bb) => {
+ let s = live_variable_set(&liveness.regular.ins[bb], &liveness.drop.ins[bb]);
+ writeln!(out, " | Live variables on entry to {:?}: {}", bb, s)?;
+ }
+
+ PassWhere::InCFG(location) => {
+ let s = live_variable_set(
+ ®ular_liveness_per_location[&location],
+ &drop_liveness_per_location[&location],
+ );
+ writeln!(out, " | Live variables at {:?}: {}", location, s)?;
+ }
+
+ PassWhere::AfterCFG => {}
+ }
+ Ok(())
+ });
+}
+
+/// Right now, we piggy back on the `ReVar` to store our NLL inference
+/// regions. These are indexed with `RegionVid`. This method will
+/// assert that the region is a `ReVar` and extract its interal index.
+/// This is reasonable because in our MIR we replace all free regions
+/// with inference variables.
+pub trait ToRegionVid {
+ fn to_region_vid(&self) -> RegionVid;
+}
+
+impl ToRegionVid for RegionKind {
+ fn to_region_vid(&self) -> RegionVid {
+ if let &ty::ReVar(vid) = self {
+ vid
+ } else {
+ bug!("region is not an ReVar: {:?}", self)
+ }
+ }
+}
+
+fn live_variable_set(regular: &LocalSet, drops: &LocalSet) -> String {
+ // sort and deduplicate:
+ let all_locals: BTreeSet<_> = regular.iter().chain(drops.iter()).collect();
+
+ // construct a string with each local, including `(drop)` if it is
+ // only dropped, versus a regular use.
+ let mut string = String::new();
+ for local in all_locals {
+ string.push_str(&format!("{:?}", local));
+
+ if !regular.contains(&local) {
+ assert!(drops.contains(&local));
+ string.push_str(" (drop)");
+ }
+
+ string.push_str(", ");
+ }
+
+ let len = if string.is_empty() {
+ 0
+ } else {
+ string.len() - 2
+ };
+
+ format!("[{}]", &string[..len])
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::free_regions::FreeRegions;
+use rustc::infer::InferCtxt;
+use rustc::infer::RegionVariableOrigin;
+use rustc::infer::NLLRegionVariableOrigin;
+use rustc::infer::region_constraints::VarOrigins;
+use rustc::middle::free_region::FreeRegionMap;
+use rustc::mir::{Location, Mir};
+use rustc::ty::{self, RegionVid};
+use rustc_data_structures::indexed_vec::IndexVec;
+use rustc_data_structures::fx::FxHashSet;
+use std::collections::BTreeSet;
+use std::fmt;
+use syntax_pos::Span;
+
+pub struct RegionInferenceContext<'tcx> {
+ /// Contains the definition for every region variable. Region
+ /// variables are identified by their index (`RegionVid`). The
+ /// definition contains information about where the region came
+ /// from as well as its final inferred value.
+ definitions: IndexVec<RegionVid, RegionDefinition<'tcx>>,
+
+ /// The liveness constraints added to each region. For most
+ /// regions, these start out empty and steadily grow, though for
+ /// each free region R they start out containing the entire CFG
+ /// and `end(R)`.
+ liveness_constraints: IndexVec<RegionVid, Region>,
+
+ /// The final inferred values of the inference variables; `None`
+ /// until `solve` is invoked.
+ inferred_values: Option<IndexVec<RegionVid, Region>>,
+
+ /// The constraints we have accumulated and used during solving.
+ constraints: Vec<Constraint>,
+
+ free_region_map: &'tcx FreeRegionMap<'tcx>,
+}
+
+struct RegionDefinition<'tcx> {
+ /// Why we created this variable. Mostly these will be
+ /// `RegionVariableOrigin::NLL`, but some variables get created
+ /// elsewhere in the code with other causes (e.g., instantiation
+ /// late-bound-regions).
+ origin: RegionVariableOrigin,
+
+ /// If this is a free-region, then this is `Some(X)` where `X` is
+ /// the name of the region.
+ name: Option<ty::Region<'tcx>>,
+}
+
+/// The value of an individual region variable. Region variables
+/// consist of a set of points in the CFG as well as a set of "free
+/// regions", which are sometimes written as `end(R)`. These
+/// correspond to the named lifetimes and refer to portions of the
+/// caller's control-flow graph -- specifically some portion that can
+/// be reached after we return.
+#[derive(Clone, Default, PartialEq, Eq)]
+struct Region {
+ points: BTreeSet<Location>,
+ free_regions: BTreeSet<RegionVid>,
+}
+
+impl fmt::Debug for Region {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ formatter
+ .debug_set()
+ .entries(&self.points)
+ .entries(&self.free_regions)
+ .finish()
+ }
+}
+
+impl Region {
+ fn add_point(&mut self, point: Location) -> bool {
+ self.points.insert(point)
+ }
+
+ fn add_free_region(&mut self, region: RegionVid) -> bool {
+ self.free_regions.insert(region)
+ }
+
+ fn contains_point(&self, point: Location) -> bool {
+ self.points.contains(&point)
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Constraint {
+ // NB. The ordering here is not significant for correctness, but
+ // it is for convenience. Before we dump the constraints in the
+ // debugging logs, we sort them, and we'd like the "super region"
+ // to be first, etc. (In particular, span should remain last.)
+ /// The region SUP must outlive SUB...
+ sup: RegionVid,
+
+ /// Region that must be outlived.
+ sub: RegionVid,
+
+ /// At this location.
+ point: Location,
+
+ /// Where did this constraint arise?
+ span: Span,
+}
+
+impl<'tcx> RegionInferenceContext<'tcx> {
+ /// Creates a new region inference context with a total of
+ /// `num_region_variables` valid inference variables; the first N
+ /// of those will be constant regions representing the free
+ /// regions defined in `free_regions`.
+ pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) -> Self {
+ let num_region_variables = var_origins.len();
+
+ // Create a RegionDefinition for each inference variable.
+ let definitions = var_origins
+ .into_iter()
+ .map(|origin| RegionDefinition::new(origin))
+ .collect();
+
+ let mut result = Self {
+ definitions: definitions,
+ liveness_constraints: IndexVec::from_elem_n(Region::default(), num_region_variables),
+ inferred_values: None,
+ constraints: Vec::new(),
+ free_region_map: free_regions.free_region_map,
+ };
+
+ result.init_free_regions(free_regions, mir);
+
+ result
+ }
+
+ /// Initializes the region variables for each free region
+ /// (lifetime parameter). The first N variables always correspond
+ /// to the free regions appearing in the function signature (both
+ /// named and anonymous) and where clauses. This function iterates
+ /// over those regions and initializes them with minimum values.
+ ///
+ /// For example:
+ ///
+ /// fn foo<'a, 'b>(..) where 'a: 'b
+ ///
+ /// would initialize two variables like so:
+ ///
+ /// R0 = { CFG, R0 } // 'a
+ /// R1 = { CFG, R0, R1 } // 'b
+ ///
+ /// Here, R0 represents `'a`, and it contains (a) the entire CFG
+ /// and (b) any free regions that it outlives, which in this case
+ /// is just itself. R1 (`'b`) in contrast also outlives `'a` and
+ /// hence contains R0 and R1.
+ fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) {
+ let FreeRegions {
+ indices,
+ free_region_map: _,
+ } = free_regions;
+
+ // For each free region X:
+ for (free_region, &variable) in indices {
+ // These should be free-region variables.
+ assert!(match self.definitions[variable].origin {
+ RegionVariableOrigin::NLL(NLLRegionVariableOrigin::FreeRegion) => true,
+ _ => false,
+ });
+
+ // Initialize the name and a few other details.
+ self.definitions[variable].name = Some(free_region);
+
+ // Add all nodes in the CFG to `definition.value`.
+ for (block, block_data) in mir.basic_blocks().iter_enumerated() {
+ let liveness_constraint = &mut self.liveness_constraints[variable];
+ for statement_index in 0..block_data.statements.len() + 1 {
+ let location = Location {
+ block,
+ statement_index,
+ };
+ liveness_constraint.add_point(location);
+ }
+ }
+
+ // Add `end(X)` into the set for X.
+ self.liveness_constraints[variable].add_free_region(variable);
+
+ debug!(
+ "init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`",
+ free_region,
+ variable,
+ self.liveness_constraints[variable],
+ );
+ }
+ }
+
+ /// Returns an iterator over all the region indices.
+ pub fn regions(&self) -> impl Iterator<Item = RegionVid> {
+ self.definitions.indices()
+ }
+
+ /// Returns true if the region `r` contains the point `p`.
+ ///
+ /// Until `solve()` executes, this value is not particularly meaningful.
+ pub fn region_contains_point(&self, r: RegionVid, p: Location) -> bool {
+ let inferred_values = self.inferred_values
+ .as_ref()
+ .expect("region values not yet inferred");
+ inferred_values[r].contains_point(p)
+ }
+
+ /// Returns access to the value of `r` for debugging purposes.
+ pub(super) fn region_value(&self, r: RegionVid) -> &fmt::Debug {
+ let inferred_values = self.inferred_values
+ .as_ref()
+ .expect("region values not yet inferred");
+ &inferred_values[r]
+ }
+
+ /// Indicates that the region variable `v` is live at the point `point`.
+ pub(super) fn add_live_point(&mut self, v: RegionVid, point: Location) {
+ debug!("add_live_point({:?}, {:?})", v, point);
+ assert!(self.inferred_values.is_none(), "values already inferred");
+ self.liveness_constraints[v].add_point(point);
+ }
+
+ /// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`.
+ pub(super) fn add_outlives(
+ &mut self,
+ span: Span,
+ sup: RegionVid,
+ sub: RegionVid,
+ point: Location,
+ ) {
+ debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point);
+ assert!(self.inferred_values.is_none(), "values already inferred");
+ self.constraints.push(Constraint {
+ span,
+ sup,
+ sub,
+ point,
+ });
+ }
+
+ /// Perform region inference.
+ pub(super) fn solve(&mut self, infcx: &InferCtxt<'_, '_, 'tcx>, mir: &Mir<'tcx>) {
+ assert!(self.inferred_values.is_none(), "values already inferred");
+
+ // Find the minimal regions that can solve the constraints. This is infallible.
+ self.propagate_constraints(mir);
+
+ // Now, see whether any of the constraints were too strong. In particular,
+ // we want to check for a case where a free region exceeded its bounds.
+ // Consider:
+ //
+ // fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
+ //
+ // In this case, returning `x` requires `&'a u32 <: &'b u32`
+ // and hence we establish (transitively) a constraint that
+ // `'a: 'b`. The `propagate_constraints` code above will
+ // therefore add `end('a)` into the region for `'b` -- but we
+ // have no evidence that `'b` outlives `'a`, so we want to report
+ // an error.
+
+ // The free regions are always found in a prefix of the full list.
+ let free_region_definitions = self.definitions
+ .iter_enumerated()
+ .take_while(|(_, fr_definition)| fr_definition.name.is_some());
+
+ for (fr, fr_definition) in free_region_definitions {
+ self.check_free_region(infcx, fr, fr_definition);
+ }
+ }
+
+ fn check_free_region(
+ &self,
+ infcx: &InferCtxt<'_, '_, 'tcx>,
+ fr: RegionVid,
+ fr_definition: &RegionDefinition<'tcx>,
+ ) {
+ let inferred_values = self.inferred_values.as_ref().unwrap();
+ let fr_name = fr_definition.name.unwrap();
+ let fr_value = &inferred_values[fr];
+
+ // Find every region `o` such that `fr: o`
+ // (because `fr` includes `end(o)`).
+ for &outlived_fr in &fr_value.free_regions {
+ // `fr` includes `end(fr)`, that's not especially
+ // interesting.
+ if fr == outlived_fr {
+ continue;
+ }
+
+ let outlived_fr_definition = &self.definitions[outlived_fr];
+ let outlived_fr_name = outlived_fr_definition.name.unwrap();
+
+ // Check that `o <= fr`. If not, report an error.
+ if !self.free_region_map
+ .sub_free_regions(outlived_fr_name, fr_name)
+ {
+ // worst error msg ever
+ let blame_span = self.blame_span(fr, outlived_fr);
+ infcx.tcx.sess.span_err(
+ blame_span,
+ &format!(
+ "free region `{}` does not outlive `{}`",
+ fr_name,
+ outlived_fr_name
+ ),
+ );
+ }
+ }
+ }
+
+ /// Propagate the region constraints: this will grow the values
+ /// for each region variable until all the constraints are
+ /// satisfied. Note that some values may grow **too** large to be
+ /// feasible, but we check this later.
+ fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
+ let mut changed = true;
+ let mut dfs = Dfs::new(mir);
+
+ debug!("propagate_constraints()");
+ debug!("propagate_constraints: constraints={:#?}", {
+ let mut constraints: Vec<_> = self.constraints.iter().collect();
+ constraints.sort();
+ constraints
+ });
+
+ // The initial values for each region are derived from the liveness
+ // constraints we have accumulated.
+ let mut inferred_values = self.liveness_constraints.clone();
+
+ while changed {
+ changed = false;
+ debug!("propagate_constraints: --------------------");
+ for constraint in &self.constraints {
+ debug!("propagate_constraints: constraint={:?}", constraint);
+
+ let sub = &inferred_values[constraint.sub].clone();
+ let sup_value = &mut inferred_values[constraint.sup];
+
+ // Grow the value as needed to accommodate the
+ // outlives constraint.
+
+ if dfs.copy(sub, sup_value, constraint.point) {
+ debug!("propagate_constraints: sub={:?}", sub);
+ debug!("propagate_constraints: sup={:?}", sup_value);
+ changed = true;
+ }
+ }
+ debug!("\n");
+ }
+
+ self.inferred_values = Some(inferred_values);
+ }
+
+ /// Tries to finds a good span to blame for the fact that `fr1`
+ /// contains `fr2`.
+ fn blame_span(&self, fr1: RegionVid, fr2: RegionVid) -> Span {
+ // Find everything that influenced final value of `fr`.
+ let influenced_fr1 = self.dependencies(fr1);
+
+ // Try to find some outlives constraint `'X: fr2` where `'X`
+ // influenced `fr1`. Blame that.
+ //
+ // NB, this is a pretty bad choice most of the time. In
+ // particular, the connection between `'X` and `fr1` may not
+ // be obvious to the user -- not to mention the naive notion
+ // of dependencies, which doesn't account for the locations of
+ // contraints at all. But it will do for now.
+ for constraint in &self.constraints {
+ if constraint.sub == fr2 && influenced_fr1[constraint.sup] {
+ return constraint.span;
+ }
+ }
+
+ bug!(
+ "could not find any constraint to blame for {:?}: {:?}",
+ fr1,
+ fr2
+ );
+ }
+
+ /// Finds all regions whose values `'a` may depend on in some way.
+ /// Basically if there exists a constraint `'a: 'b @ P`, then `'b`
+ /// and `dependencies('b)` will be in the final set.
+ ///
+ /// Used during error reporting, extremely naive and inefficient.
+ fn dependencies(&self, r0: RegionVid) -> IndexVec<RegionVid, bool> {
+ let mut result_set = IndexVec::from_elem(false, &self.definitions);
+ let mut changed = true;
+ result_set[r0] = true;
+
+ while changed {
+ changed = false;
+ for constraint in &self.constraints {
+ if result_set[constraint.sup] {
+ if !result_set[constraint.sub] {
+ result_set[constraint.sub] = true;
+ changed = true;
+ }
+ }
+ }
+ }
+
+ result_set
+ }
+}
+
+struct Dfs<'a, 'tcx: 'a> {
+ mir: &'a Mir<'tcx>,
+}
+
+impl<'a, 'tcx> Dfs<'a, 'tcx> {
+ fn new(mir: &'a Mir<'tcx>) -> Self {
+ Self { mir }
+ }
+
+ fn copy(
+ &mut self,
+ from_region: &Region,
+ to_region: &mut Region,
+ start_point: Location,
+ ) -> bool {
+ let mut changed = false;
+
+ let mut stack = vec![];
+ let mut visited = FxHashSet();
+
+ stack.push(start_point);
+ while let Some(p) = stack.pop() {
+ debug!(" dfs: p={:?}", p);
+
+ if !from_region.contains_point(p) {
+ debug!(" not in from-region");
+ continue;
+ }
+
+ if !visited.insert(p) {
+ debug!(" already visited");
+ continue;
+ }
+
+ changed |= to_region.add_point(p);
+
+ let block_data = &self.mir[p.block];
+ let successor_points = if p.statement_index < block_data.statements.len() {
+ vec![
+ Location {
+ statement_index: p.statement_index + 1,
+ ..p
+ },
+ ]
+ } else {
+ block_data
+ .terminator()
+ .successors()
+ .iter()
+ .map(|&basic_block| {
+ Location {
+ statement_index: 0,
+ block: basic_block,
+ }
+ })
+ .collect::<Vec<_>>()
+ };
+
+ if successor_points.is_empty() {
+ // If we reach the END point in the graph, then copy
+ // over any skolemized end points in the `from_region`
+ // and make sure they are included in the `to_region`.
+
+ debug!(" dfs: free_regions={:?}", from_region.free_regions);
+ for &fr in &from_region.free_regions {
+ changed |= to_region.free_regions.insert(fr);
+ }
+ } else {
+ stack.extend(successor_points);
+ }
+ }
+
+ changed
+ }
+}
+
+impl<'tcx> RegionDefinition<'tcx> {
+ fn new(origin: RegionVariableOrigin) -> Self {
+ // Create a new region definition. Note that, for free
+ // regions, these fields get updated later in
+ // `init_free_regions`.
+ Self { origin, name: None }
+ }
+}
+
+impl fmt::Debug for Constraint {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(
+ formatter,
+ "({:?}: {:?} @ {:?}) due to {:?}",
+ self.sup,
+ self.sub,
+ self.point,
+ self.span
+ )
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use rustc_data_structures::indexed_vec::{Idx, IndexVec};
+use rustc::ty::subst::Substs;
+use rustc::ty::{self, ClosureSubsts, RegionVid, Ty, TypeFoldable};
+use rustc::mir::{BasicBlock, Local, Location, Mir, Statement, StatementKind};
+use rustc::mir::visit::{MutVisitor, TyContext};
+use rustc::infer::{InferCtxt, NLLRegionVariableOrigin};
+
+use super::ToRegionVid;
+use super::free_regions::FreeRegions;
+
+/// Replaces all free regions appearing in the MIR with fresh
+/// inference variables, returning the number of variables created.
+pub fn renumber_mir<'a, 'gcx, 'tcx>(
+ infcx: &InferCtxt<'a, 'gcx, 'tcx>,
+ free_regions: &FreeRegions<'tcx>,
+ mir: &mut Mir<'tcx>,
+) {
+ // Create inference variables for each of the free regions
+ // declared on the function signature.
+ let free_region_inference_vars = (0..free_regions.indices.len())
+ .map(RegionVid::new)
+ .map(|vid_expected| {
+ let r = infcx.next_nll_region_var(NLLRegionVariableOrigin::FreeRegion);
+ assert_eq!(vid_expected, r.to_region_vid());
+ r
+ })
+ .collect();
+
+ debug!("renumber_mir()");
+ debug!("renumber_mir: free_regions={:#?}", free_regions);
+ debug!("renumber_mir: mir.arg_count={:?}", mir.arg_count);
+
+ let mut visitor = NLLVisitor {
+ infcx,
+ free_regions,
+ free_region_inference_vars,
+ arg_count: mir.arg_count,
+ };
+ visitor.visit_mir(mir);
+}
+
+struct NLLVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
+ infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
+ free_regions: &'a FreeRegions<'tcx>,
+ free_region_inference_vars: IndexVec<RegionVid, ty::Region<'tcx>>,
+ arg_count: usize,
+}
+
+impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> {
+ /// Replaces all regions appearing in `value` with fresh inference
+ /// variables. This is what we do for almost the entire MIR, with
+ /// the exception of the declared types of our arguments.
+ fn renumber_regions<T>(&mut self, ty_context: TyContext, value: &T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ debug!("renumber_regions(value={:?})", value);
+
+ self.infcx
+ .tcx
+ .fold_regions(value, &mut false, |_region, _depth| {
+ let origin = NLLRegionVariableOrigin::Inferred(ty_context);
+ self.infcx.next_nll_region_var(origin)
+ })
+ }
+
+ /// Renumbers the regions appearing in `value`, but those regions
+ /// are expected to be free regions from the function signature.
+ fn renumber_free_regions<T>(&mut self, value: &T) -> T
+ where
+ T: TypeFoldable<'tcx>,
+ {
+ debug!("renumber_free_regions(value={:?})", value);
+
+ self.infcx
+ .tcx
+ .fold_regions(value, &mut false, |region, _depth| {
+ let index = self.free_regions.indices[®ion];
+ self.free_region_inference_vars[index]
+ })
+ }
+
+ fn is_argument_or_return_slot(&self, local: Local) -> bool {
+ // The first argument is return slot, next N are arguments.
+ local.index() <= self.arg_count
+ }
+}
+
+impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> {
+ fn visit_ty(&mut self, ty: &mut Ty<'tcx>, ty_context: TyContext) {
+ let is_arg = match ty_context {
+ TyContext::LocalDecl { local, .. } => self.is_argument_or_return_slot(local),
+ TyContext::ReturnTy(..) => true,
+ TyContext::Location(..) => false,
+ };
+ debug!(
+ "visit_ty(ty={:?}, is_arg={:?}, ty_context={:?})",
+ ty,
+ is_arg,
+ ty_context
+ );
+
+ let old_ty = *ty;
+ *ty = if is_arg {
+ self.renumber_free_regions(&old_ty)
+ } else {
+ self.renumber_regions(ty_context, &old_ty)
+ };
+ debug!("visit_ty: ty={:?}", ty);
+ }
+
+ fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, location: Location) {
+ debug!("visit_substs(substs={:?}, location={:?})", substs, location);
+
+ let ty_context = TyContext::Location(location);
+ *substs = self.renumber_regions(ty_context, &{ *substs });
+
+ debug!("visit_substs: substs={:?}", substs);
+ }
+
+ fn visit_region(&mut self, region: &mut ty::Region<'tcx>, location: Location) {
+ debug!("visit_region(region={:?}, location={:?})", region, location);
+
+ let old_region = *region;
+ let ty_context = TyContext::Location(location);
+ *region = self.renumber_regions(ty_context, &old_region);
+
+ debug!("visit_region: region={:?}", region);
+ }
+
+ fn visit_closure_substs(&mut self, substs: &mut ClosureSubsts<'tcx>, location: Location) {
+ debug!(
+ "visit_closure_substs(substs={:?}, location={:?})",
+ substs,
+ location
+ );
+
+ let ty_context = TyContext::Location(location);
+ *substs = self.renumber_regions(ty_context, substs);
+
+ debug!("visit_closure_substs: substs={:?}", substs);
+ }
+
+ fn visit_statement(
+ &mut self,
+ block: BasicBlock,
+ statement: &mut Statement<'tcx>,
+ location: Location,
+ ) {
+ if let StatementKind::EndRegion(_) = statement.kind {
+ statement.kind = StatementKind::Nop;
+ }
+ self.super_statement(block, statement, location);
+ }
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use rustc::mir::Mir;
+use rustc::infer::region_constraints::Constraint;
+use rustc::infer::region_constraints::RegionConstraintData;
+use rustc::ty;
+use transform::type_check::MirTypeckRegionConstraints;
+use transform::type_check::OutlivesSet;
+
+use super::free_regions::FreeRegions;
+use super::region_infer::RegionInferenceContext;
+
+/// When the MIR type-checker executes, it validates all the types in
+/// the MIR, and in the process generates a set of constraints that
+/// must hold regarding the regions in the MIR, along with locations
+/// *where* they must hold. This code takes those constriants and adds
+/// them into the NLL `RegionInferenceContext`.
+pub(super) fn generate<'tcx>(
+ regioncx: &mut RegionInferenceContext<'tcx>,
+ free_regions: &FreeRegions<'tcx>,
+ mir: &Mir<'tcx>,
+ constraints: &MirTypeckRegionConstraints<'tcx>,
+) {
+ SubtypeConstraintGenerator {
+ regioncx,
+ free_regions,
+ mir,
+ }.generate(constraints);
+}
+
+struct SubtypeConstraintGenerator<'cx, 'tcx: 'cx> {
+ regioncx: &'cx mut RegionInferenceContext<'tcx>,
+ free_regions: &'cx FreeRegions<'tcx>,
+ mir: &'cx Mir<'tcx>,
+}
+
+impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
+ fn generate(&mut self, constraints: &MirTypeckRegionConstraints<'tcx>) {
+ let MirTypeckRegionConstraints {
+ liveness_set,
+ outlives_sets,
+ } = constraints;
+
+ debug!(
+ "generate(liveness_set={} items, outlives_sets={} items)",
+ liveness_set.len(),
+ outlives_sets.len()
+ );
+
+ for (region, location) in liveness_set {
+ debug!("generate: {:#?} is live at {:#?}", region, location);
+ let region_vid = self.to_region_vid(region);
+ self.regioncx.add_live_point(region_vid, *location);
+ }
+
+ for OutlivesSet { locations, data } in outlives_sets {
+ debug!("generate: constraints at: {:#?}", locations);
+ let RegionConstraintData {
+ constraints,
+ verifys,
+ givens,
+ } = data;
+
+ for constraint in constraints.keys() {
+ debug!("generate: constraint: {:?}", constraint);
+ let (a_vid, b_vid) = match constraint {
+ Constraint::VarSubVar(a_vid, b_vid) => (*a_vid, *b_vid),
+ Constraint::RegSubVar(a_r, b_vid) => (self.to_region_vid(a_r), *b_vid),
+ Constraint::VarSubReg(a_vid, b_r) => (*a_vid, self.to_region_vid(b_r)),
+ Constraint::RegSubReg(a_r, b_r) => {
+ (self.to_region_vid(a_r), self.to_region_vid(b_r))
+ }
+ };
+
+ // We have the constraint that `a_vid <= b_vid`. Add
+ // `b_vid: a_vid` to our region checker. Note that we
+ // reverse direction, because `regioncx` talks about
+ // "outlives" (`>=`) whereas the region constraints
+ // talk about `<=`.
+ let span = self.mir.source_info(locations.from_location).span;
+ self.regioncx
+ .add_outlives(span, b_vid, a_vid, locations.at_location);
+ }
+
+ assert!(verifys.is_empty(), "verifys not yet implemented");
+ assert!(
+ givens.is_empty(),
+ "MIR type-checker does not use givens (thank goodness)"
+ );
+ }
+ }
+
+ fn to_region_vid(&self, r: ty::Region<'tcx>) -> ty::RegionVid {
+ // Every region that we see in the constraints came from the
+ // MIR or from the parameter environment. If the former, it
+ // will be a region variable. If the latter, it will be in
+ // the set of free regions *somewhere*.
+ if let ty::ReVar(vid) = r {
+ *vid
+ } else {
+ self.free_regions.indices[&r]
+ }
+ }
+}
use dataflow::{BitDenotation, BlockSets, DataflowOperator};
pub use dataflow::indexes::BorrowIndex;
-use transform::nll::region_infer::RegionInferenceContext;
-use transform::nll::ToRegionVid;
+use borrow_check::nll::region_infer::RegionInferenceContext;
+use borrow_check::nll::ToRegionVid;
use syntax_pos::Span;
location_map: FxHashMap<Location, BorrowIndex>,
region_map: FxHashMap<Region<'tcx>, FxHashSet<BorrowIndex>>,
region_span_map: FxHashMap<RegionKind, Span>,
- nonlexical_regioncx: Option<&'a RegionInferenceContext<'tcx>>,
+ nonlexical_regioncx: Option<RegionInferenceContext<'tcx>>,
}
// temporarily allow some dead fields: `kind` and `region` will be
impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>,
mir: &'a Mir<'tcx>,
- nonlexical_regioncx: Option<&'a RegionInferenceContext<'tcx>>)
+ nonlexical_regioncx: Option<RegionInferenceContext<'tcx>>)
-> Self {
let mut visitor = GatherBorrows {
tcx,
fn kill_loans_out_of_scope_at_location(&self,
sets: &mut BlockSets<BorrowIndex>,
location: Location) {
- if let Some(regioncx) = self.nonlexical_regioncx {
+ if let Some(ref regioncx) = self.nonlexical_regioncx {
for (borrow_index, borrow_data) in self.borrows.iter_enumerated() {
let borrow_region = borrow_data.region.to_region_vid();
if !regioncx.region_contains_point(borrow_region, location) {
}
}
}
+
+ pub fn find_local(&self, local: Local) -> MovePathIndex {
+ self.locals[local]
+ }
}
#[derive(Debug)]
pub mod copy_prop;
pub mod generator;
pub mod inline;
-pub mod nll;
pub mod lower_128bit;
pub(crate) fn provide(providers: &mut Providers) {
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use rustc::hir;
-use rustc::mir::{Location, Place, Mir, Rvalue};
-use rustc::mir::visit::Visitor;
-use rustc::mir::Place::Projection;
-use rustc::mir::{PlaceProjection, ProjectionElem};
-use rustc::infer::InferCtxt;
-use rustc::traits::{self, ObligationCause};
-use rustc::ty::{self, Ty};
-use rustc::ty::fold::TypeFoldable;
-use rustc::util::common::ErrorReported;
-use rustc_data_structures::fx::FxHashSet;
-use syntax::codemap::DUMMY_SP;
-
-use super::LivenessResults;
-use super::ToRegionVid;
-use super::region_infer::RegionInferenceContext;
-
-pub(super) fn generate_constraints<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- regioncx: &mut RegionInferenceContext<'tcx>,
- mir: &Mir<'tcx>,
- param_env: ty::ParamEnv<'tcx>,
- liveness: &LivenessResults,
-) {
- ConstraintGeneration {
- infcx,
- regioncx,
- mir,
- liveness,
- param_env,
- }.add_constraints();
-}
-
-struct ConstraintGeneration<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
- infcx: &'cx InferCtxt<'cx, 'gcx, 'tcx>,
- regioncx: &'cx mut RegionInferenceContext<'tcx>,
- mir: &'cx Mir<'tcx>,
- liveness: &'cx LivenessResults,
- param_env: ty::ParamEnv<'tcx>,
-}
-
-impl<'cx, 'gcx, 'tcx> ConstraintGeneration<'cx, 'gcx, 'tcx> {
- fn add_constraints(&mut self) {
- self.add_liveness_constraints();
- self.add_borrow_constraints();
- }
-
- /// Liveness constraints:
- ///
- /// > If a variable V is live at point P, then all regions R in the type of V
- /// > must include the point P.
- fn add_liveness_constraints(&mut self) {
- debug!("add_liveness_constraints()");
- for bb in self.mir.basic_blocks().indices() {
- debug!("add_liveness_constraints: bb={:?}", bb);
-
- self.liveness
- .regular
- .simulate_block(self.mir, bb, |location, live_locals| {
- for live_local in live_locals.iter() {
- let live_local_ty = self.mir.local_decls[live_local].ty;
- self.add_regular_live_constraint(live_local_ty, location);
- }
- });
-
- self.liveness
- .drop
- .simulate_block(self.mir, bb, |location, live_locals| {
- for live_local in live_locals.iter() {
- let live_local_ty = self.mir.local_decls[live_local].ty;
- self.add_drop_live_constraint(live_local_ty, location);
- }
- });
- }
- }
-
- /// Some variable with type `live_ty` is "regular live" at
- /// `location` -- i.e., it may be used later. This means that all
- /// regions appearing in the type `live_ty` must be live at
- /// `location`.
- fn add_regular_live_constraint<T>(&mut self, live_ty: T, location: Location)
- where
- T: TypeFoldable<'tcx>,
- {
- debug!(
- "add_regular_live_constraint(live_ty={:?}, location={:?})",
- live_ty,
- location
- );
-
- self.infcx
- .tcx
- .for_each_free_region(&live_ty, |live_region| {
- let vid = live_region.to_region_vid();
- self.regioncx.add_live_point(vid, location);
- });
- }
-
- /// Some variable with type `live_ty` is "drop live" at `location`
- /// -- i.e., it may be dropped later. This means that *some* of
- /// the regions in its type must be live at `location`. The
- /// precise set will depend on the dropck constraints, and in
- /// particular this takes `#[may_dangle]` into account.
- fn add_drop_live_constraint(&mut self, dropped_ty: Ty<'tcx>, location: Location) {
- debug!(
- "add_drop_live_constraint(dropped_ty={:?}, location={:?})",
- dropped_ty,
- location
- );
-
- let tcx = self.infcx.tcx;
- let mut types = vec![(dropped_ty, 0)];
- let mut known = FxHashSet();
- while let Some((ty, depth)) = types.pop() {
- let span = DUMMY_SP; // FIXME
- let result = match tcx.dtorck_constraint_for_ty(span, dropped_ty, depth, ty) {
- Ok(result) => result,
- Err(ErrorReported) => {
- continue;
- }
- };
-
- let ty::DtorckConstraint {
- outlives,
- dtorck_types,
- } = result;
-
- // All things in the `outlives` array may be touched by
- // the destructor and must be live at this point.
- for outlive in outlives {
- if let Some(ty) = outlive.as_type() {
- self.add_regular_live_constraint(ty, location);
- } else if let Some(r) = outlive.as_region() {
- self.add_regular_live_constraint(r, location);
- } else {
- bug!()
- }
- }
-
- // However, there may also be some types that
- // `dtorck_constraint_for_ty` could not resolve (e.g.,
- // associated types and parameters). We need to normalize
- // associated types here and possibly recursively process.
- for ty in dtorck_types {
- let cause = ObligationCause::dummy();
- // We know that our original `dropped_ty` is well-formed,
- // so region obligations resulting from this normalization
- // should always hold.
- //
- // Therefore we ignore them instead of trying to match
- // them up with a location.
- let fulfillcx = traits::FulfillmentContext::new_ignoring_regions();
- match traits::fully_normalize_with_fulfillcx(
- self.infcx, fulfillcx, cause, self.param_env, &ty
- ) {
- Ok(ty) => match ty.sty {
- ty::TyParam(..) | ty::TyProjection(..) | ty::TyAnon(..) => {
- self.add_regular_live_constraint(ty, location);
- }
-
- _ => if known.insert(ty) {
- types.push((ty, depth + 1));
- },
- },
-
- Err(errors) => {
- self.infcx.report_fulfillment_errors(&errors, None);
- }
- }
- }
- }
- }
-
- fn add_borrow_constraints(&mut self) {
- self.visit_mir(self.mir);
- }
-
- 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;
-
- 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;
-
- if let ty::TyRef(base_region, ty::TypeAndMut{ ty: _, mutbl }) = *base_sty {
- match mutbl {
- hir::Mutability::MutImmutable => { },
-
- hir::Mutability::MutMutable => {
- self.add_reborrow_constraint(location, borrow_region, base);
- },
- }
-
- 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());
- }
- }
- }
- }
-}
-
-impl<'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cx, 'gcx, 'tcx> {
- fn visit_rvalue(&mut self,
- rvalue: &Rvalue<'tcx>,
- location: Location) {
- debug!("visit_rvalue(rvalue={:?}, location={:?})", rvalue, location);
-
- // Look for an rvalue like:
- //
- // & L
- //
- // where L is the path that is borrowed. In that case, we have
- // to add the reborrow constraints (which don't fall out
- // naturally from the type-checker).
- if let Rvalue::Ref(region, _bk, ref borrowed_place) = *rvalue {
- self.add_reborrow_constraint(location, region, borrowed_place);
- }
-
- self.super_rvalue(rvalue, location);
- }
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-//! Code to extract the free regions declared on a function and the
-//! relationships between them. For example:
-//!
-//! ```
-//! fn foo<'a, 'b, 'c: 'b>() { }
-//! ```
-//!
-//! here we would be returning a map assigning each of `{'a, 'b, 'c}`
-//! to an index, as well as the `FreeRegionMap` which can compute
-//! relationships between them.
-//!
-//! The code in this file doesn't *do anything* with those results; it
-//! just returns them for other code to use.
-
-use rustc::hir::def_id::DefId;
-use rustc::infer::InferCtxt;
-use rustc::middle::free_region::FreeRegionMap;
-use rustc::ty::{self, RegionVid};
-use rustc::ty::subst::Substs;
-use rustc::util::nodemap::FxHashMap;
-use rustc_data_structures::indexed_vec::Idx;
-
-#[derive(Debug)]
-pub struct FreeRegions<'tcx> {
- /// Given a free region defined on this function (either early- or
- /// late-bound), this maps it to its internal region index. When
- /// the region context is created, the first N variables will be
- /// created based on these indices.
- pub indices: FxHashMap<ty::Region<'tcx>, RegionVid>,
-
- /// The map from the typeck tables telling us how to relate free regions.
- pub free_region_map: &'tcx FreeRegionMap<'tcx>,
-}
-
-pub fn free_regions<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- item_def_id: DefId,
-) -> FreeRegions<'tcx> {
- debug!("free_regions(item_def_id={:?})", item_def_id);
-
- let mut indices = FxHashMap();
-
- // `'static` is always free.
- insert_free_region(&mut indices, infcx.tcx.types.re_static);
-
- // Extract the early regions.
- let item_substs = Substs::identity_for_item(infcx.tcx, item_def_id);
- for item_subst in item_substs {
- if let Some(region) = item_subst.as_region() {
- insert_free_region(&mut indices, region);
- }
- }
-
- // Extract the late-bound regions. Use the liberated fn sigs,
- // where the late-bound regions will have been converted into free
- // regions, and add them to the map.
- let item_id = infcx.tcx.hir.as_local_node_id(item_def_id).unwrap();
- let fn_hir_id = infcx.tcx.hir.node_to_hir_id(item_id);
- let tables = infcx.tcx.typeck_tables_of(item_def_id);
- let fn_sig = tables.liberated_fn_sigs()[fn_hir_id].clone();
- infcx
- .tcx
- .for_each_free_region(&fn_sig.inputs_and_output, |region| {
- if let ty::ReFree(_) = *region {
- insert_free_region(&mut indices, region);
- }
- });
-
- debug!("free_regions: indices={:#?}", indices);
-
- FreeRegions { indices, free_region_map: &tables.free_region_map }
-}
-
-fn insert_free_region<'tcx>(
- free_regions: &mut FxHashMap<ty::Region<'tcx>, RegionVid>,
- region: ty::Region<'tcx>,
-) {
- let next = RegionVid::new(free_regions.len());
- free_regions.entry(region).or_insert(next);
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use rustc::hir::def_id::DefId;
-use rustc::mir::Mir;
-use rustc::infer::InferCtxt;
-use rustc::ty::{self, RegionKind, RegionVid};
-use rustc::util::nodemap::FxHashMap;
-use std::collections::BTreeSet;
-use transform::MirSource;
-use transform::type_check;
-use util::liveness::{self, LivenessMode, LivenessResult, LocalSet};
-
-use util as mir_util;
-use self::mir_util::PassWhere;
-
-mod constraint_generation;
-mod subtype_constraint_generation;
-mod free_regions;
-
-pub(crate) mod region_infer;
-use self::region_infer::RegionInferenceContext;
-
-mod renumber;
-
-/// Computes the (non-lexical) regions from the input MIR.
-///
-/// This may result in errors being reported.
-pub fn compute_regions<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- def_id: DefId,
- param_env: ty::ParamEnv<'gcx>,
- mir: &mut Mir<'tcx>,
-) -> RegionInferenceContext<'tcx> {
- // Compute named region information.
- let free_regions = &free_regions::free_regions(infcx, def_id);
-
- // Replace all regions with fresh inference variables.
- renumber::renumber_mir(infcx, free_regions, mir);
-
- // Run the MIR type-checker.
- let mir_node_id = infcx.tcx.hir.as_local_node_id(def_id).unwrap();
- let constraint_sets = &type_check::type_check(infcx, mir_node_id, param_env, mir);
-
- // Create the region inference context, taking ownership of the region inference
- // data that was contained in `infcx`.
- let var_origins = infcx.take_region_var_origins();
- let mut regioncx = RegionInferenceContext::new(var_origins, free_regions, mir);
- subtype_constraint_generation::generate(&mut regioncx, free_regions, mir, constraint_sets);
-
- // Compute what is live where.
- let liveness = &LivenessResults {
- regular: liveness::liveness_of_locals(
- &mir,
- LivenessMode {
- include_regular_use: true,
- include_drops: false,
- },
- ),
-
- drop: liveness::liveness_of_locals(
- &mir,
- LivenessMode {
- include_regular_use: false,
- include_drops: true,
- },
- ),
- };
-
- // Generate non-subtyping constraints.
- constraint_generation::generate_constraints(infcx, &mut regioncx, &mir, param_env, liveness);
-
- // Solve the region constraints.
- regioncx.solve(infcx, &mir);
-
- // Dump MIR results into a file, if that is enabled. This let us
- // write unit-tests.
- dump_mir_results(infcx, liveness, MirSource::item(def_id), &mir, ®ioncx);
-
- regioncx
-}
-
-struct LivenessResults {
- regular: LivenessResult,
- drop: LivenessResult,
-}
-
-fn dump_mir_results<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- liveness: &LivenessResults,
- source: MirSource,
- mir: &Mir<'tcx>,
- regioncx: &RegionInferenceContext,
-) {
- if !mir_util::dump_enabled(infcx.tcx, "nll", source) {
- return;
- }
-
- let regular_liveness_per_location: FxHashMap<_, _> = mir.basic_blocks()
- .indices()
- .flat_map(|bb| {
- let mut results = vec![];
- liveness
- .regular
- .simulate_block(&mir, bb, |location, local_set| {
- results.push((location, local_set.clone()));
- });
- results
- })
- .collect();
-
- let drop_liveness_per_location: FxHashMap<_, _> = mir.basic_blocks()
- .indices()
- .flat_map(|bb| {
- let mut results = vec![];
- liveness
- .drop
- .simulate_block(&mir, bb, |location, local_set| {
- results.push((location, local_set.clone()));
- });
- results
- })
- .collect();
-
- mir_util::dump_mir(infcx.tcx, None, "nll", &0, source, mir, |pass_where, out| {
- match pass_where {
- // Before the CFG, dump out the values for each region variable.
- PassWhere::BeforeCFG => for region in regioncx.regions() {
- writeln!(out, "| {:?}: {:?}", region, regioncx.region_value(region))?;
- },
-
- // Before each basic block, dump out the values
- // that are live on entry to the basic block.
- PassWhere::BeforeBlock(bb) => {
- let s = live_variable_set(&liveness.regular.ins[bb], &liveness.drop.ins[bb]);
- writeln!(out, " | Live variables on entry to {:?}: {}", bb, s)?;
- }
-
- PassWhere::InCFG(location) => {
- let s = live_variable_set(
- ®ular_liveness_per_location[&location],
- &drop_liveness_per_location[&location],
- );
- writeln!(out, " | Live variables at {:?}: {}", location, s)?;
- }
-
- PassWhere::AfterCFG => {}
- }
- Ok(())
- });
-}
-
-/// Right now, we piggy back on the `ReVar` to store our NLL inference
-/// regions. These are indexed with `RegionVid`. This method will
-/// assert that the region is a `ReVar` and extract its interal index.
-/// This is reasonable because in our MIR we replace all free regions
-/// with inference variables.
-pub trait ToRegionVid {
- fn to_region_vid(&self) -> RegionVid;
-}
-
-impl ToRegionVid for RegionKind {
- fn to_region_vid(&self) -> RegionVid {
- if let &ty::ReVar(vid) = self {
- vid
- } else {
- bug!("region is not an ReVar: {:?}", self)
- }
- }
-}
-
-fn live_variable_set(regular: &LocalSet, drops: &LocalSet) -> String {
- // sort and deduplicate:
- let all_locals: BTreeSet<_> = regular.iter().chain(drops.iter()).collect();
-
- // construct a string with each local, including `(drop)` if it is
- // only dropped, versus a regular use.
- let mut string = String::new();
- for local in all_locals {
- string.push_str(&format!("{:?}", local));
-
- if !regular.contains(&local) {
- assert!(drops.contains(&local));
- string.push_str(" (drop)");
- }
-
- string.push_str(", ");
- }
-
- let len = if string.is_empty() {
- 0
- } else {
- string.len() - 2
- };
-
- format!("[{}]", &string[..len])
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use super::free_regions::FreeRegions;
-use rustc::infer::InferCtxt;
-use rustc::infer::RegionVariableOrigin;
-use rustc::infer::NLLRegionVariableOrigin;
-use rustc::infer::region_constraints::VarOrigins;
-use rustc::middle::free_region::FreeRegionMap;
-use rustc::mir::{Location, Mir};
-use rustc::ty::{self, RegionVid};
-use rustc_data_structures::indexed_vec::IndexVec;
-use rustc_data_structures::fx::FxHashSet;
-use std::collections::BTreeSet;
-use std::fmt;
-use syntax_pos::Span;
-
-pub struct RegionInferenceContext<'tcx> {
- /// Contains the definition for every region variable. Region
- /// variables are identified by their index (`RegionVid`). The
- /// definition contains information about where the region came
- /// from as well as its final inferred value.
- definitions: IndexVec<RegionVid, RegionDefinition<'tcx>>,
-
- /// The liveness constraints added to each region. For most
- /// regions, these start out empty and steadily grow, though for
- /// each free region R they start out containing the entire CFG
- /// and `end(R)`.
- liveness_constraints: IndexVec<RegionVid, Region>,
-
- /// The final inferred values of the inference variables; `None`
- /// until `solve` is invoked.
- inferred_values: Option<IndexVec<RegionVid, Region>>,
-
- /// The constraints we have accumulated and used during solving.
- constraints: Vec<Constraint>,
-
- free_region_map: &'tcx FreeRegionMap<'tcx>,
-}
-
-struct RegionDefinition<'tcx> {
- /// Why we created this variable. Mostly these will be
- /// `RegionVariableOrigin::NLL`, but some variables get created
- /// elsewhere in the code with other causes (e.g., instantiation
- /// late-bound-regions).
- origin: RegionVariableOrigin,
-
- /// If this is a free-region, then this is `Some(X)` where `X` is
- /// the name of the region.
- name: Option<ty::Region<'tcx>>,
-}
-
-/// The value of an individual region variable. Region variables
-/// consist of a set of points in the CFG as well as a set of "free
-/// regions", which are sometimes written as `end(R)`. These
-/// correspond to the named lifetimes and refer to portions of the
-/// caller's control-flow graph -- specifically some portion that can
-/// be reached after we return.
-#[derive(Clone, Default, PartialEq, Eq)]
-struct Region {
- points: BTreeSet<Location>,
- free_regions: BTreeSet<RegionVid>,
-}
-
-impl fmt::Debug for Region {
- fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- formatter
- .debug_set()
- .entries(&self.points)
- .entries(&self.free_regions)
- .finish()
- }
-}
-
-impl Region {
- fn add_point(&mut self, point: Location) -> bool {
- self.points.insert(point)
- }
-
- fn add_free_region(&mut self, region: RegionVid) -> bool {
- self.free_regions.insert(region)
- }
-
- fn contains_point(&self, point: Location) -> bool {
- self.points.contains(&point)
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Constraint {
- // NB. The ordering here is not significant for correctness, but
- // it is for convenience. Before we dump the constraints in the
- // debugging logs, we sort them, and we'd like the "super region"
- // to be first, etc. (In particular, span should remain last.)
- /// The region SUP must outlive SUB...
- sup: RegionVid,
-
- /// Region that must be outlived.
- sub: RegionVid,
-
- /// At this location.
- point: Location,
-
- /// Where did this constraint arise?
- span: Span,
-}
-
-impl<'tcx> RegionInferenceContext<'tcx> {
- /// Creates a new region inference context with a total of
- /// `num_region_variables` valid inference variables; the first N
- /// of those will be constant regions representing the free
- /// regions defined in `free_regions`.
- pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) -> Self {
- let num_region_variables = var_origins.len();
-
- // Create a RegionDefinition for each inference variable.
- let definitions = var_origins
- .into_iter()
- .map(|origin| RegionDefinition::new(origin))
- .collect();
-
- let mut result = Self {
- definitions: definitions,
- liveness_constraints: IndexVec::from_elem_n(Region::default(), num_region_variables),
- inferred_values: None,
- constraints: Vec::new(),
- free_region_map: free_regions.free_region_map,
- };
-
- result.init_free_regions(free_regions, mir);
-
- result
- }
-
- /// Initializes the region variables for each free region
- /// (lifetime parameter). The first N variables always correspond
- /// to the free regions appearing in the function signature (both
- /// named and anonymous) and where clauses. This function iterates
- /// over those regions and initializes them with minimum values.
- ///
- /// For example:
- ///
- /// fn foo<'a, 'b>(..) where 'a: 'b
- ///
- /// would initialize two variables like so:
- ///
- /// R0 = { CFG, R0 } // 'a
- /// R1 = { CFG, R0, R1 } // 'b
- ///
- /// Here, R0 represents `'a`, and it contains (a) the entire CFG
- /// and (b) any free regions that it outlives, which in this case
- /// is just itself. R1 (`'b`) in contrast also outlives `'a` and
- /// hence contains R0 and R1.
- fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) {
- let FreeRegions {
- indices,
- free_region_map: _,
- } = free_regions;
-
- // For each free region X:
- for (free_region, &variable) in indices {
- // These should be free-region variables.
- assert!(match self.definitions[variable].origin {
- RegionVariableOrigin::NLL(NLLRegionVariableOrigin::FreeRegion) => true,
- _ => false,
- });
-
- // Initialize the name and a few other details.
- self.definitions[variable].name = Some(free_region);
-
- // Add all nodes in the CFG to `definition.value`.
- for (block, block_data) in mir.basic_blocks().iter_enumerated() {
- let liveness_constraint = &mut self.liveness_constraints[variable];
- for statement_index in 0..block_data.statements.len() + 1 {
- let location = Location {
- block,
- statement_index,
- };
- liveness_constraint.add_point(location);
- }
- }
-
- // Add `end(X)` into the set for X.
- self.liveness_constraints[variable].add_free_region(variable);
-
- debug!(
- "init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`",
- free_region,
- variable,
- self.liveness_constraints[variable],
- );
- }
- }
-
- /// Returns an iterator over all the region indices.
- pub fn regions(&self) -> impl Iterator<Item = RegionVid> {
- self.definitions.indices()
- }
-
- /// Returns true if the region `r` contains the point `p`.
- ///
- /// Until `solve()` executes, this value is not particularly meaningful.
- pub fn region_contains_point(&self, r: RegionVid, p: Location) -> bool {
- let inferred_values = self.inferred_values
- .as_ref()
- .expect("region values not yet inferred");
- inferred_values[r].contains_point(p)
- }
-
- /// Returns access to the value of `r` for debugging purposes.
- pub(super) fn region_value(&self, r: RegionVid) -> &fmt::Debug {
- let inferred_values = self.inferred_values
- .as_ref()
- .expect("region values not yet inferred");
- &inferred_values[r]
- }
-
- /// Indicates that the region variable `v` is live at the point `point`.
- pub(super) fn add_live_point(&mut self, v: RegionVid, point: Location) {
- debug!("add_live_point({:?}, {:?})", v, point);
- assert!(self.inferred_values.is_none(), "values already inferred");
- self.liveness_constraints[v].add_point(point);
- }
-
- /// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`.
- pub(super) fn add_outlives(
- &mut self,
- span: Span,
- sup: RegionVid,
- sub: RegionVid,
- point: Location,
- ) {
- debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point);
- assert!(self.inferred_values.is_none(), "values already inferred");
- self.constraints.push(Constraint {
- span,
- sup,
- sub,
- point,
- });
- }
-
- /// Perform region inference.
- pub(super) fn solve(&mut self, infcx: &InferCtxt<'_, '_, 'tcx>, mir: &Mir<'tcx>) {
- assert!(self.inferred_values.is_none(), "values already inferred");
-
- // Find the minimal regions that can solve the constraints. This is infallible.
- self.propagate_constraints(mir);
-
- // Now, see whether any of the constraints were too strong. In particular,
- // we want to check for a case where a free region exceeded its bounds.
- // Consider:
- //
- // fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
- //
- // In this case, returning `x` requires `&'a u32 <: &'b u32`
- // and hence we establish (transitively) a constraint that
- // `'a: 'b`. The `propagate_constraints` code above will
- // therefore add `end('a)` into the region for `'b` -- but we
- // have no evidence that `'b` outlives `'a`, so we want to report
- // an error.
-
- // The free regions are always found in a prefix of the full list.
- let free_region_definitions = self.definitions
- .iter_enumerated()
- .take_while(|(_, fr_definition)| fr_definition.name.is_some());
-
- for (fr, fr_definition) in free_region_definitions {
- self.check_free_region(infcx, fr, fr_definition);
- }
- }
-
- fn check_free_region(
- &self,
- infcx: &InferCtxt<'_, '_, 'tcx>,
- fr: RegionVid,
- fr_definition: &RegionDefinition<'tcx>,
- ) {
- let inferred_values = self.inferred_values.as_ref().unwrap();
- let fr_name = fr_definition.name.unwrap();
- let fr_value = &inferred_values[fr];
-
- // Find every region `o` such that `fr: o`
- // (because `fr` includes `end(o)`).
- for &outlived_fr in &fr_value.free_regions {
- // `fr` includes `end(fr)`, that's not especially
- // interesting.
- if fr == outlived_fr {
- continue;
- }
-
- let outlived_fr_definition = &self.definitions[outlived_fr];
- let outlived_fr_name = outlived_fr_definition.name.unwrap();
-
- // Check that `o <= fr`. If not, report an error.
- if !self.free_region_map
- .sub_free_regions(outlived_fr_name, fr_name)
- {
- // worst error msg ever
- let blame_span = self.blame_span(fr, outlived_fr);
- infcx.tcx.sess.span_err(
- blame_span,
- &format!(
- "free region `{}` does not outlive `{}`",
- fr_name,
- outlived_fr_name
- ),
- );
- }
- }
- }
-
- /// Propagate the region constraints: this will grow the values
- /// for each region variable until all the constraints are
- /// satisfied. Note that some values may grow **too** large to be
- /// feasible, but we check this later.
- fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
- let mut changed = true;
- let mut dfs = Dfs::new(mir);
-
- debug!("propagate_constraints()");
- debug!("propagate_constraints: constraints={:#?}", {
- let mut constraints: Vec<_> = self.constraints.iter().collect();
- constraints.sort();
- constraints
- });
-
- // The initial values for each region are derived from the liveness
- // constraints we have accumulated.
- let mut inferred_values = self.liveness_constraints.clone();
-
- while changed {
- changed = false;
- debug!("propagate_constraints: --------------------");
- for constraint in &self.constraints {
- debug!("propagate_constraints: constraint={:?}", constraint);
-
- let sub = &inferred_values[constraint.sub].clone();
- let sup_value = &mut inferred_values[constraint.sup];
-
- // Grow the value as needed to accommodate the
- // outlives constraint.
-
- if dfs.copy(sub, sup_value, constraint.point) {
- debug!("propagate_constraints: sub={:?}", sub);
- debug!("propagate_constraints: sup={:?}", sup_value);
- changed = true;
- }
- }
- debug!("\n");
- }
-
- self.inferred_values = Some(inferred_values);
- }
-
- /// Tries to finds a good span to blame for the fact that `fr1`
- /// contains `fr2`.
- fn blame_span(&self, fr1: RegionVid, fr2: RegionVid) -> Span {
- // Find everything that influenced final value of `fr`.
- let influenced_fr1 = self.dependencies(fr1);
-
- // Try to find some outlives constraint `'X: fr2` where `'X`
- // influenced `fr1`. Blame that.
- //
- // NB, this is a pretty bad choice most of the time. In
- // particular, the connection between `'X` and `fr1` may not
- // be obvious to the user -- not to mention the naive notion
- // of dependencies, which doesn't account for the locations of
- // contraints at all. But it will do for now.
- for constraint in &self.constraints {
- if constraint.sub == fr2 && influenced_fr1[constraint.sup] {
- return constraint.span;
- }
- }
-
- bug!(
- "could not find any constraint to blame for {:?}: {:?}",
- fr1,
- fr2
- );
- }
-
- /// Finds all regions whose values `'a` may depend on in some way.
- /// Basically if there exists a constraint `'a: 'b @ P`, then `'b`
- /// and `dependencies('b)` will be in the final set.
- ///
- /// Used during error reporting, extremely naive and inefficient.
- fn dependencies(&self, r0: RegionVid) -> IndexVec<RegionVid, bool> {
- let mut result_set = IndexVec::from_elem(false, &self.definitions);
- let mut changed = true;
- result_set[r0] = true;
-
- while changed {
- changed = false;
- for constraint in &self.constraints {
- if result_set[constraint.sup] {
- if !result_set[constraint.sub] {
- result_set[constraint.sub] = true;
- changed = true;
- }
- }
- }
- }
-
- result_set
- }
-}
-
-struct Dfs<'a, 'tcx: 'a> {
- mir: &'a Mir<'tcx>,
-}
-
-impl<'a, 'tcx> Dfs<'a, 'tcx> {
- fn new(mir: &'a Mir<'tcx>) -> Self {
- Self { mir }
- }
-
- fn copy(
- &mut self,
- from_region: &Region,
- to_region: &mut Region,
- start_point: Location,
- ) -> bool {
- let mut changed = false;
-
- let mut stack = vec![];
- let mut visited = FxHashSet();
-
- stack.push(start_point);
- while let Some(p) = stack.pop() {
- debug!(" dfs: p={:?}", p);
-
- if !from_region.contains_point(p) {
- debug!(" not in from-region");
- continue;
- }
-
- if !visited.insert(p) {
- debug!(" already visited");
- continue;
- }
-
- changed |= to_region.add_point(p);
-
- let block_data = &self.mir[p.block];
- let successor_points = if p.statement_index < block_data.statements.len() {
- vec![
- Location {
- statement_index: p.statement_index + 1,
- ..p
- },
- ]
- } else {
- block_data
- .terminator()
- .successors()
- .iter()
- .map(|&basic_block| {
- Location {
- statement_index: 0,
- block: basic_block,
- }
- })
- .collect::<Vec<_>>()
- };
-
- if successor_points.is_empty() {
- // If we reach the END point in the graph, then copy
- // over any skolemized end points in the `from_region`
- // and make sure they are included in the `to_region`.
-
- debug!(" dfs: free_regions={:?}", from_region.free_regions);
- for &fr in &from_region.free_regions {
- changed |= to_region.free_regions.insert(fr);
- }
- } else {
- stack.extend(successor_points);
- }
- }
-
- changed
- }
-}
-
-impl<'tcx> RegionDefinition<'tcx> {
- fn new(origin: RegionVariableOrigin) -> Self {
- // Create a new region definition. Note that, for free
- // regions, these fields get updated later in
- // `init_free_regions`.
- Self { origin, name: None }
- }
-}
-
-impl fmt::Debug for Constraint {
- fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- write!(
- formatter,
- "({:?}: {:?} @ {:?}) due to {:?}",
- self.sup,
- self.sub,
- self.point,
- self.span
- )
- }
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use rustc_data_structures::indexed_vec::{Idx, IndexVec};
-use rustc::ty::subst::Substs;
-use rustc::ty::{self, ClosureSubsts, RegionVid, Ty, TypeFoldable};
-use rustc::mir::{BasicBlock, Local, Location, Mir, Statement, StatementKind};
-use rustc::mir::visit::{MutVisitor, TyContext};
-use rustc::infer::{InferCtxt, NLLRegionVariableOrigin};
-
-use super::ToRegionVid;
-use super::free_regions::FreeRegions;
-
-/// Replaces all free regions appearing in the MIR with fresh
-/// inference variables, returning the number of variables created.
-pub fn renumber_mir<'a, 'gcx, 'tcx>(
- infcx: &InferCtxt<'a, 'gcx, 'tcx>,
- free_regions: &FreeRegions<'tcx>,
- mir: &mut Mir<'tcx>,
-) {
- // Create inference variables for each of the free regions
- // declared on the function signature.
- let free_region_inference_vars = (0..free_regions.indices.len())
- .map(RegionVid::new)
- .map(|vid_expected| {
- let r = infcx.next_nll_region_var(NLLRegionVariableOrigin::FreeRegion);
- assert_eq!(vid_expected, r.to_region_vid());
- r
- })
- .collect();
-
- debug!("renumber_mir()");
- debug!("renumber_mir: free_regions={:#?}", free_regions);
- debug!("renumber_mir: mir.arg_count={:?}", mir.arg_count);
-
- let mut visitor = NLLVisitor {
- infcx,
- free_regions,
- free_region_inference_vars,
- arg_count: mir.arg_count,
- };
- visitor.visit_mir(mir);
-}
-
-struct NLLVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
- infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
- free_regions: &'a FreeRegions<'tcx>,
- free_region_inference_vars: IndexVec<RegionVid, ty::Region<'tcx>>,
- arg_count: usize,
-}
-
-impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> {
- /// Replaces all regions appearing in `value` with fresh inference
- /// variables. This is what we do for almost the entire MIR, with
- /// the exception of the declared types of our arguments.
- fn renumber_regions<T>(&mut self, ty_context: TyContext, value: &T) -> T
- where
- T: TypeFoldable<'tcx>,
- {
- debug!("renumber_regions(value={:?})", value);
-
- self.infcx
- .tcx
- .fold_regions(value, &mut false, |_region, _depth| {
- let origin = NLLRegionVariableOrigin::Inferred(ty_context);
- self.infcx.next_nll_region_var(origin)
- })
- }
-
- /// Renumbers the regions appearing in `value`, but those regions
- /// are expected to be free regions from the function signature.
- fn renumber_free_regions<T>(&mut self, value: &T) -> T
- where
- T: TypeFoldable<'tcx>,
- {
- debug!("renumber_free_regions(value={:?})", value);
-
- self.infcx
- .tcx
- .fold_regions(value, &mut false, |region, _depth| {
- let index = self.free_regions.indices[®ion];
- self.free_region_inference_vars[index]
- })
- }
-
- fn is_argument_or_return_slot(&self, local: Local) -> bool {
- // The first argument is return slot, next N are arguments.
- local.index() <= self.arg_count
- }
-}
-
-impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> {
- fn visit_ty(&mut self, ty: &mut Ty<'tcx>, ty_context: TyContext) {
- let is_arg = match ty_context {
- TyContext::LocalDecl { local, .. } => self.is_argument_or_return_slot(local),
- TyContext::ReturnTy(..) => true,
- TyContext::Location(..) => false,
- };
- debug!(
- "visit_ty(ty={:?}, is_arg={:?}, ty_context={:?})",
- ty,
- is_arg,
- ty_context
- );
-
- let old_ty = *ty;
- *ty = if is_arg {
- self.renumber_free_regions(&old_ty)
- } else {
- self.renumber_regions(ty_context, &old_ty)
- };
- debug!("visit_ty: ty={:?}", ty);
- }
-
- fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, location: Location) {
- debug!("visit_substs(substs={:?}, location={:?})", substs, location);
-
- let ty_context = TyContext::Location(location);
- *substs = self.renumber_regions(ty_context, &{ *substs });
-
- debug!("visit_substs: substs={:?}", substs);
- }
-
- fn visit_region(&mut self, region: &mut ty::Region<'tcx>, location: Location) {
- debug!("visit_region(region={:?}, location={:?})", region, location);
-
- let old_region = *region;
- let ty_context = TyContext::Location(location);
- *region = self.renumber_regions(ty_context, &old_region);
-
- debug!("visit_region: region={:?}", region);
- }
-
- fn visit_closure_substs(&mut self, substs: &mut ClosureSubsts<'tcx>, location: Location) {
- debug!(
- "visit_closure_substs(substs={:?}, location={:?})",
- substs,
- location
- );
-
- let ty_context = TyContext::Location(location);
- *substs = self.renumber_regions(ty_context, substs);
-
- debug!("visit_closure_substs: substs={:?}", substs);
- }
-
- fn visit_statement(
- &mut self,
- block: BasicBlock,
- statement: &mut Statement<'tcx>,
- location: Location,
- ) {
- if let StatementKind::EndRegion(_) = statement.kind {
- statement.kind = StatementKind::Nop;
- }
- self.super_statement(block, statement, location);
- }
-}
+++ /dev/null
-// 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 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use rustc::mir::Mir;
-use rustc::infer::region_constraints::Constraint;
-use rustc::infer::region_constraints::RegionConstraintData;
-use rustc::ty;
-use transform::type_check::MirTypeckRegionConstraints;
-use transform::type_check::OutlivesSet;
-
-use super::free_regions::FreeRegions;
-use super::region_infer::RegionInferenceContext;
-
-/// When the MIR type-checker executes, it validates all the types in
-/// the MIR, and in the process generates a set of constraints that
-/// must hold regarding the regions in the MIR, along with locations
-/// *where* they must hold. This code takes those constriants and adds
-/// them into the NLL `RegionInferenceContext`.
-pub(super) fn generate<'tcx>(
- regioncx: &mut RegionInferenceContext<'tcx>,
- free_regions: &FreeRegions<'tcx>,
- mir: &Mir<'tcx>,
- constraints: &MirTypeckRegionConstraints<'tcx>,
-) {
- SubtypeConstraintGenerator {
- regioncx,
- free_regions,
- mir,
- }.generate(constraints);
-}
-
-struct SubtypeConstraintGenerator<'cx, 'tcx: 'cx> {
- regioncx: &'cx mut RegionInferenceContext<'tcx>,
- free_regions: &'cx FreeRegions<'tcx>,
- mir: &'cx Mir<'tcx>,
-}
-
-impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
- fn generate(&mut self, constraints: &MirTypeckRegionConstraints<'tcx>) {
- let MirTypeckRegionConstraints {
- liveness_set,
- outlives_sets,
- } = constraints;
-
- debug!(
- "generate(liveness_set={} items, outlives_sets={} items)",
- liveness_set.len(),
- outlives_sets.len()
- );
-
- for (region, location) in liveness_set {
- debug!("generate: {:#?} is live at {:#?}", region, location);
- let region_vid = self.to_region_vid(region);
- self.regioncx.add_live_point(region_vid, *location);
- }
-
- for OutlivesSet { locations, data } in outlives_sets {
- debug!("generate: constraints at: {:#?}", locations);
- let RegionConstraintData {
- constraints,
- verifys,
- givens,
- } = data;
-
- for constraint in constraints.keys() {
- debug!("generate: constraint: {:?}", constraint);
- let (a_vid, b_vid) = match constraint {
- Constraint::VarSubVar(a_vid, b_vid) => (*a_vid, *b_vid),
- Constraint::RegSubVar(a_r, b_vid) => (self.to_region_vid(a_r), *b_vid),
- Constraint::VarSubReg(a_vid, b_r) => (*a_vid, self.to_region_vid(b_r)),
- Constraint::RegSubReg(a_r, b_r) => {
- (self.to_region_vid(a_r), self.to_region_vid(b_r))
- }
- };
-
- // We have the constraint that `a_vid <= b_vid`. Add
- // `b_vid: a_vid` to our region checker. Note that we
- // reverse direction, because `regioncx` talks about
- // "outlives" (`>=`) whereas the region constraints
- // talk about `<=`.
- let span = self.mir.source_info(locations.from_location).span;
- self.regioncx
- .add_outlives(span, b_vid, a_vid, locations.at_location);
- }
-
- assert!(verifys.is_empty(), "verifys not yet implemented");
- assert!(
- givens.is_empty(),
- "MIR type-checker does not use givens (thank goodness)"
- );
- }
- }
-
- fn to_region_vid(&self, r: ty::Region<'tcx>) -> ty::RegionVid {
- // Every region that we see in the constraints came from the
- // MIR or from the parameter environment. If the former, it
- // will be a region variable. If the latter, it will be in
- // the set of free regions *somewhere*.
- if let ty::ReVar(vid) = r {
- *vid
- } else {
- self.free_regions.indices[&r]
- }
- }
-}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//compile-flags: -Z emit-end-regions -Zborrowck=mir -Z nll
+
+#![allow(warnings)]
+
+struct Wrap<'p> { p: &'p mut i32 }
+
+impl<'p> Drop for Wrap<'p> {
+ fn drop(&mut self) {
+ *self.p += 1;
+ }
+}
+
+struct Foo<'p> { a: String, b: Wrap<'p> }
+
+fn main() {
+ let mut x = 0;
+ let wrap = Wrap { p: &mut x };
+ let s = String::from("str");
+ let foo = Foo { a: s, b: wrap };
+ std::mem::drop(foo.b);
+ x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ // FIXME ^ Should not error in the future with implicit dtors, only manually implemented ones
+}
--- /dev/null
+error[E0506]: cannot assign to `x` because it is borrowed
+ --> $DIR/maybe-initialized-drop-implicit-fragment-drop.rs:31:5
+ |
+27 | let wrap = Wrap { p: &mut x };
+ | ------ borrow of `x` occurs here
+...
+31 | x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ | ^^^^^ assignment to borrowed `x` occurs here
+
+error: aborting due to previous error
+
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//compile-flags: -Z emit-end-regions -Zborrowck=mir -Z nll
+
+#![allow(warnings)]
+
+struct Wrap<'p> { p: &'p mut i32 }
+
+impl<'p> Drop for Wrap<'p> {
+ fn drop(&mut self) {
+ *self.p += 1;
+ }
+}
+
+fn main() {
+ let mut x = 0;
+ let wrap = Wrap { p: &mut x };
+ std::mem::drop(wrap);
+ x = 1; // OK, drop is inert
+}
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//compile-flags: -Z emit-end-regions -Zborrowck=mir -Z nll
+
+#![allow(warnings)]
+
+struct Wrap<'p> { p: &'p mut i32 }
+
+impl<'p> Drop for Wrap<'p> {
+ fn drop(&mut self) {
+ *self.p += 1;
+ }
+}
+
+struct Foo<'p> { a: String, b: Wrap<'p> }
+
+fn main() {
+ let mut x = 0;
+ let wrap = Wrap { p: &mut x };
+ let s = String::from("str");
+ let foo = Foo { a: s, b: wrap };
+ std::mem::drop(foo.a);
+ x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+}
--- /dev/null
+error[E0506]: cannot assign to `x` because it is borrowed
+ --> $DIR/maybe-initialized-drop-with-fragment.rs:31:5
+ |
+27 | let wrap = Wrap { p: &mut x };
+ | ------ borrow of `x` occurs here
+...
+31 | x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ | ^^^^^ assignment to borrowed `x` occurs here
+
+error: aborting due to previous error
+
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//compile-flags: -Z emit-end-regions -Zborrowck=mir -Z nll
+
+#![allow(warnings)]
+
+struct Wrap<'p> { p: &'p mut i32 }
+
+impl<'p> Drop for Wrap<'p> {
+ fn drop(&mut self) {
+ *self.p += 1;
+ }
+}
+
+struct Foo<'p> { a: String, b: Wrap<'p> }
+
+fn main() {
+ let mut x = 0;
+ let wrap = Wrap { p: &mut x };
+ let s = String::from("str");
+ let foo = Foo { a: s, b: wrap };
+ std::mem::drop(foo.a);
+ std::mem::drop(foo.b);
+ x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ // FIXME ^ This currently errors and it should not.
+}
--- /dev/null
+error[E0506]: cannot assign to `x` because it is borrowed
+ --> $DIR/maybe-initialized-drop-with-uninitialized-fragments.rs:32:5
+ |
+27 | let wrap = Wrap { p: &mut x };
+ | ------ borrow of `x` occurs here
+...
+32 | x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ | ^^^^^ assignment to borrowed `x` occurs here
+
+error: aborting due to previous error
+
--- /dev/null
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//compile-flags: -Z emit-end-regions -Zborrowck=mir -Z nll
+
+#![allow(warnings)]
+
+struct Wrap<'p> { p: &'p mut i32 }
+
+impl<'p> Drop for Wrap<'p> {
+ fn drop(&mut self) {
+ *self.p += 1;
+ }
+}
+
+fn main() {
+ let mut x = 0;
+ let wrap = Wrap { p: &mut x };
+ x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+}
--- /dev/null
+error[E0506]: cannot assign to `x` because it is borrowed
+ --> $DIR/maybe-initialized-drop.rs:26:5
+ |
+25 | let wrap = Wrap { p: &mut x };
+ | ------ borrow of `x` occurs here
+26 | x = 1; //~ ERROR cannot assign to `x` because it is borrowed [E0506]
+ | ^^^^^ assignment to borrowed `x` occurs here
+
+error: aborting due to previous error
+