// Copyright 2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! 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, TyCtxt, ParamEnv}; use rustc::ty::maps::Providers; use rustc::mir::{AssertMessage, BasicBlock, BorrowKind, Location, Lvalue, Local}; 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::{self}; use syntax_pos::Span; use dataflow::{do_dataflow}; use dataflow::{MoveDataParamEnv}; use dataflow::{BitDenotation, BlockSets, DataflowResults, DataflowResultsConsumer}; use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals}; use dataflow::{MovingOutStatements, EverInitializedLvals}; use dataflow::{Borrows, BorrowData, BorrowIndex}; use dataflow::move_paths::{MoveError, IllegalMoveOriginKind}; use dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex, LookupResult, MoveOutIndex}; 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, } // (forced to be `pub` due to its use as an associated type below.) pub struct InProgress<'b, 'gcx: 'tcx, 'tcx: 'b> { borrows: FlowInProgress>, inits: FlowInProgress>, uninits: FlowInProgress>, move_outs: FlowInProgress>, ever_inits: FlowInProgress>, } struct FlowInProgress where BD: BitDenotation { base_results: DataflowResults, curr_state: IdxSetBuf, stmt_gen: IdxSetBuf, stmt_kill: IdxSetBuf, } // 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 `lv = `, then any loan for some // path P of which `lv` is a prefix is killed." self.mutate_lvalue(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 lvalue, variant_index: _ } => { self.mutate_lvalue(ContextKind::SetDiscrim.new(location), (lvalue, 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 lvalue derefs instead. self.access_lvalue(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_lvalue(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_lvalue(ContextKind::StorageDead.new(location), (&Lvalue::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_lvalue, target: _, unwind: _ } => { self.access_lvalue(ContextKind::Drop.new(loc), (drop_lvalue, span), (Deep, Write(WriteKind::StorageDeadOrDrop)), LocalMutationIsAllowed::Yes, flow_state); } TerminatorKind::DropAndReplace { location: ref drop_lvalue, value: ref new_value, target: _, unwind: _ } => { self.mutate_lvalue(ContextKind::DropAndReplace.new(loc), (drop_lvalue, 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_lvalue(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(); for borrow in domain.borrows() { let root_lvalue = self.prefixes( &borrow.lvalue, PrefixSet::All ).last().unwrap(); match root_lvalue { Lvalue::Static(_) => { self.access_lvalue( ContextKind::StorageDead.new(loc), (&root_lvalue, self.mir.source_info(borrow.location).span), (Deep, Write(WriteKind::StorageDeadOrDrop)), LocalMutationIsAllowed::Yes, flow_state ); } Lvalue::Local(_) => { self.access_lvalue( ContextKind::StorageDead.new(loc), (&root_lvalue, self.mir.source_info(borrow.location).span), (Shallow(None), Write(WriteKind::StorageDeadOrDrop)), LocalMutationIsAllowed::Yes, flow_state ); } Lvalue::Projection(_) => () } } } 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::{Shallow, Deep}; 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), /// From the RFC: "A *deep* access means that all data reachable /// through the given lvalue 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 an lvalue access, this flag is used to indicate that an immutable /// local lvalue 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 lvalue 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 /// lvalue 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_lvalue(&mut self, context: Context, lvalue_span: (&Lvalue<'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 (lvalue_span.0, rw) { (&Lvalue::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(lvalue_span, rw, is_local_mutation_allowed); self.each_borrow_involving_path( context, (sd, lvalue_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, lvalue_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, lvalue_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, lvalue_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, lvalue_span, end_span) }, WriteKind::Mutate => { error_reported = true; this.report_illegal_mutation_of_borrowed( context, lvalue_span, borrow) }, WriteKind::Move => { error_reported = true; this.report_move_out_while_borrowed( context, lvalue_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_lvalue(&mut self, context: Context, lvalue_span: (&Lvalue<'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, lvalue_span, flow_state); } MutateMode::JustWrite => { self.check_if_assigned_path_is_moved(context, lvalue_span, flow_state); } } self.access_lvalue(context, lvalue_span, (kind, Write(WriteKind::Mutate)), LocalMutationIsAllowed::Yes, flow_state); // check for reassignments to immutable local variables self.check_if_reassignment_to_immutable_state(context, lvalue_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 lvalue) => { let access_kind = match bk { BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))), BorrowKind::Unique | BorrowKind::Mut => (Deep, Write(WriteKind::MutableBorrow(bk))), }; self.access_lvalue(context, (lvalue, span), access_kind, LocalMutationIsAllowed::No, flow_state); self.check_if_path_is_moved(context, InitializationRequiringAction::Borrow, (lvalue, 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 lvalue) | Rvalue::Discriminant(ref lvalue) => { let af = match *rvalue { Rvalue::Len(..) => ArtificialField::ArrayLength, Rvalue::Discriminant(..) => ArtificialField::Discriminant, _ => unreachable!(), }; self.access_lvalue(context, (lvalue, span), (Shallow(Some(af)), Read(ReadKind::Copy)), LocalMutationIsAllowed::No, flow_state); self.check_if_path_is_moved(context, InitializationRequiringAction::Use, (lvalue, 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 lvalue) => { // copy of lvalue: check if this is "copy of frozen path" // (FIXME: see check_loans.rs) self.access_lvalue(context, (lvalue, 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, (lvalue, span), flow_state); } Operand::Move(ref lvalue) => { // move of lvalue: check if this is move of already borrowed path self.access_lvalue(context, (lvalue, 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, (lvalue, span), flow_state); } Operand::Constant(_) => {} } } } impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { fn check_if_reassignment_to_immutable_state(&mut self, context: Context, (lvalue, span): (&Lvalue<'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(lvalue, LocalMutationIsAllowed::No) { return; } if let Err(_) = self.is_mutable(lvalue, LocalMutationIsAllowed::Yes) { return; } match self.move_path_closest_to(lvalue) { 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, (lvalue, span), first_assign_span); break; } } }, Err(NoMovePathFound::ReachedStatic) => { let item_msg = match self.describe_lvalue(lvalue) { 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, lvalue_span: (&Lvalue<'tcx>, Span), flow_state: &InProgress<'cx, 'gcx, 'tcx>) { // FIXME: analogous code in check_loans first maps `lvalue` to // its base_path ... but is that what we want here? let lvalue = self.base_path(lvalue_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 lvalue scenario. // // This code covers scenarios 1, 2, and 4. debug!("check_if_path_is_moved part1 lvalue: {:?}", lvalue); match self.move_path_closest_to(lvalue) { Ok(mpi) => { if maybe_uninits.curr_state.contains(&mpi) { self.report_use_of_moved_or_uninitialized(context, desired_action, lvalue_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 `lvalue` also interferes // with an attempt to use `lvalue`. This is scenario 3 above. // // (Distinct from handling of scenarios 1+2+4 above because // `lvalue` 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 lvalue: {:?}", lvalue); if let Some(mpi) = self.move_path_for_lvalue(lvalue) { if let Some(child_mpi) = maybe_uninits.has_any_child_of(mpi) { self.report_use_of_moved_or_uninitialized(context, desired_action, lvalue_span, child_mpi, curr_move_outs); return; // don't bother finding other problems. } } } /// Currently MoveData does not store entries for all lvalues in /// the input MIR. For example it will currently filter out /// lvalues that are Copy; thus we do not track lvalues of shared /// reference type. This routine will walk up an lvalue along its /// prefixes, searching for a foundational lvalue that *is* /// tracked in the MoveData. /// /// An Err result includes a tag indicated why the search failed. /// Currenly this can only occur if the lvalue is built off of a /// static variable, as we do not track those in the MoveData. fn move_path_closest_to(&mut self, lvalue: &Lvalue<'tcx>) -> Result { let mut last_prefix = lvalue; for prefix in self.prefixes(lvalue, PrefixSet::All) { if let Some(mpi) = self.move_path_for_lvalue(prefix) { return Ok(mpi); } last_prefix = prefix; } match *last_prefix { Lvalue::Local(_) => panic!("should have move path for every Local"), Lvalue::Projection(_) => panic!("PrefixSet::All meant dont stop for Projection"), Lvalue::Static(_) => return Err(NoMovePathFound::ReachedStatic), } } fn move_path_for_lvalue(&mut self, lvalue: &Lvalue<'tcx>) -> Option { // If returns None, then there is no move path corresponding // to a direct owner of `lvalue` (which means there is nothing // that borrowck tracks for its analysis). match self.move_data.rev_lookup.find(lvalue) { LookupResult::Parent(_) => None, LookupResult::Exact(mpi) => Some(mpi), } } fn check_if_assigned_path_is_moved(&mut self, context: Context, (lvalue, span): (&Lvalue<'tcx>, Span), flow_state: &InProgress<'cx, 'gcx, 'tcx>) { // recur down lvalue; dispatch to check_if_path_is_moved when necessary let mut lvalue = lvalue; loop { match *lvalue { Lvalue::Local(_) | Lvalue::Static(_) => { // assigning to `x` does not require `x` be initialized. break; } Lvalue::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; } _ => {} } } } lvalue = base; continue; } } } } /// Check the permissions for the given lvalue and read or write kind /// /// Returns true if an error is reported, false otherwise. fn check_access_permissions(&self, (lvalue, span): (&Lvalue<'tcx>, Span), kind: ReadOrWrite, is_local_mutation_allowed: LocalMutationIsAllowed) -> bool { debug!("check_access_permissions({:?}, {:?}, {:?})", lvalue, kind, is_local_mutation_allowed); let mut error_reported = false; match kind { Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => { if let Err(_lvalue_err) = self.is_unique(lvalue) { span_bug!(span, "&unique borrow for {:?} should not fail", lvalue); } }, Write(WriteKind::MutableBorrow(BorrowKind::Mut)) => { if let Err(lvalue_err) = self.is_mutable(lvalue, is_local_mutation_allowed) { error_reported = true; let item_msg = match self.describe_lvalue(lvalue) { 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 lvalue != lvalue_err { if let Some(name) = self.describe_lvalue(lvalue_err) { err.note(&format!("Value not mutable causing this error: `{}`", name)); } } err.emit(); } }, Write(WriteKind::Mutate) => { if let Err(lvalue_err) = self.is_mutable(lvalue, is_local_mutation_allowed) { error_reported = true; let item_msg = match self.describe_lvalue(lvalue) { 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 lvalue != lvalue_err { if let Some(name) = self.describe_lvalue(lvalue_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(_lvalue_err) = self.is_mutable(lvalue, is_local_mutation_allowed) { self.tcx.sess.delay_span_bug(span, &format!("Accessing `{:?}` with the kind `{:?}` shouldn't be possible", lvalue, 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, lvalue: &'d Lvalue<'tcx>, is_local_mutation_allowed: LocalMutationIsAllowed) -> Result<(), &'d Lvalue<'tcx>> { match *lvalue { Lvalue::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(lvalue), }, Mutability::Mut => Ok(()) } }, Lvalue::Static(ref static_) => { if !self.tcx.is_static_mut(static_.def_id) { Err(lvalue) } else { Ok(()) } }, Lvalue::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(lvalue), // 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(lvalue), // `*mut` raw pointers are always mutable, regardless of context // The users have to check by themselve. hir::MutMutable => Ok(()), } }, // `Box` 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(lvalue); 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(lvalue), }; } self.is_mutable(&proj.base, LocalMutationIsAllowed::No) } } } } } /// Does this lvalue have a unique path fn is_unique<'d>(&self, lvalue: &'d Lvalue<'tcx>) -> Result<(), &'d Lvalue<'tcx>> { match *lvalue { Lvalue::Local(..) => { // Local variables are unique Ok(()) }, Lvalue::Static(..) => { // Static variables are not Err(lvalue) }, Lvalue::Projection(ref proj) => { match proj.elem { ProjectionElem::Deref => { let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); // `Box` 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 { // lvalue represent an aliased location hir::MutImmutable => Err(lvalue), // `&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(&mut self, _context: Context, access_lvalue: (ShallowOrDeep, &Lvalue<'tcx>), flow_state: &InProgress<'cx, 'gcx, 'tcx>, mut op: F) where F: FnMut(&mut Self, BorrowIndex, &BorrowData<'tcx>, &Lvalue<'tcx>) -> Control { let (access, lvalue) = access_lvalue; // FIXME: analogous code in check_loans first maps `lvalue` 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 `lvalue` (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(lvalue, PrefixSet::All) { if *accessed_prefix == borrowed.lvalue { // FIXME: pass in enum describing case we are in? let ctrl = op(self, i, borrowed, accessed_prefix); if ctrl == Control::Break { return; } } } // Is `lvalue` a prefix (modulo access type) of the // `borrowed.lvalue`? 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 lvalue (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.lvalue, prefix_kind) { if borrowed_prefix == lvalue { // 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 /// lvalue 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::{Lvalue, Mir, ProjectionElem}; pub trait IsPrefixOf<'tcx> { fn is_prefix_of(&self, other: &Lvalue<'tcx>) -> bool; } impl<'tcx> IsPrefixOf<'tcx> for Lvalue<'tcx> { fn is_prefix_of(&self, other: &Lvalue<'tcx>) -> bool { let mut cursor = other; loop { if self == cursor { return true; } match *cursor { Lvalue::Local(_) | Lvalue::Static(_) => return false, Lvalue::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 Lvalue<'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 `lvalue` /// (inclusive) from longest to smallest, potentially /// terminating the iteration early based on `kind`. pub(super) fn prefixes(&self, lvalue: &'cx Lvalue<'tcx>, kind: PrefixSet) -> Prefixes<'cx, 'gcx, 'tcx> { Prefixes { next: Some(lvalue), kind, mir: self.mir, tcx: self.tcx } } } impl<'cx, 'gcx, 'tcx> Iterator for Prefixes<'cx, 'gcx, 'tcx> { type Item = &'cx Lvalue<'tcx>; fn next(&mut self) -> Option { let mut cursor = match self.next { None => return None, Some(lvalue) => lvalue, }; // Post-processing `lvalue`: Enqueue any remaining // work. Also, `lvalue` 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 { Lvalue::Local(_) | // search yielded this leaf Lvalue::Static(_) => { self.next = None; return Some(cursor); } Lvalue::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, (lvalue, span): (&Lvalue<'tcx>, Span), mpi: MovePathIndex, curr_move_out: &IdxSetBuf) { let mois = self.move_data.path_map[mpi].iter().filter( |moi| curr_move_out.contains(moi)).collect::>(); if mois.is_empty() { let item_msg = match self.describe_lvalue(lvalue) { Some(name) => format!("`{}`", name), None => "value".to_owned() }; self.tcx.cannot_act_on_uninitialized_variable(span, desired_action.as_noun(), &self.describe_lvalue(lvalue) .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_lvalue(lvalue) .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, (lvalue, span): (&Lvalue<'tcx>, Span), borrow: &BorrowData<'tcx>) { let value_msg = match self.describe_lvalue(lvalue) { Some(name) => format!("`{}`", name), None => "value".to_owned() }; let borrow_msg = match self.describe_lvalue(&borrow.lvalue) { Some(name) => format!("`{}`", name), None => "value".to_owned() }; self.tcx.cannot_move_when_borrowed(span, &self.describe_lvalue(lvalue).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, (lvalue, span): (&Lvalue<'tcx>, Span), borrow : &BorrowData<'tcx>) { let mut err = self.tcx.cannot_use_when_mutably_borrowed( span, &self.describe_lvalue(lvalue).unwrap_or("_".to_owned()), self.retrieve_borrow_span(borrow), &self.describe_lvalue(&borrow.lvalue).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(Lvalue::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 lvs)) = stmt.kind { if let AggregateKind::Closure(def_id, _) = **kind { debug!("find_closure_span: found closure {:?}", lvs); 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, lv) in freevars.iter().zip(lvs) { match *lv { Operand::Copy(Lvalue::Local(l)) | Operand::Move(Lvalue::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: &Lvalue<'tcx>, (lvalue, span): (&Lvalue<'tcx>, Span), gen_borrow_kind: BorrowKind, issued_borrow: &BorrowData, end_issued_loan_span: Option) { use self::prefixes::IsPrefixOf; assert!(common_prefix.is_prefix_of(lvalue)); assert!(common_prefix.is_prefix_of(&issued_borrow.lvalue)); 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_lvalue = self.describe_lvalue(lvalue).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_lvalue, "", lft, issued_span, "it", rgt, "", end_issued_loan_span, Origin::Mir), (BorrowKind::Mut, _, _, BorrowKind::Mut, _, _) => self.tcx.cannot_mutably_borrow_multiply( span, &desc_lvalue, "", issued_span, "", end_issued_loan_span, Origin::Mir), (BorrowKind::Unique, _, _, BorrowKind::Unique, _, _) => self.tcx.cannot_uniquely_borrow_by_two_closures( span, &desc_lvalue, issued_span, end_issued_loan_span, Origin::Mir), (BorrowKind::Unique, _, _, _, _, _) => self.tcx.cannot_uniquely_borrow_by_one_closure( span, &desc_lvalue, "", issued_span, "it", "", end_issued_loan_span, Origin::Mir), (_, _, _, BorrowKind::Unique, _, _) => self.tcx.cannot_reborrow_already_uniquely_borrowed( span, &desc_lvalue, "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_lvalue), ); } if let Some((_, var_span)) = new_closure_span { err.span_label( var_span, format!("borrow occurs due to use of `{}` in closure", desc_lvalue), ); } err.emit(); } fn report_borrowed_value_does_not_live_long_enough(&mut self, _: Context, (lvalue, span): (&Lvalue, Span), end_span: Option) { let proper_span = match *lvalue { Lvalue::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, (lvalue, span): (&Lvalue<'tcx>, Span), loan: &BorrowData) { let mut err = self.tcx.cannot_assign_to_borrowed( span, self.retrieve_borrow_span(loan), &self.describe_lvalue(lvalue).unwrap_or("_".to_owned()), Origin::Mir); err.emit(); } fn report_illegal_reassignment(&mut self, _context: Context, (lvalue, span): (&Lvalue<'tcx>, Span), assigned_span: Span) { let mut err = self.tcx.cannot_reassign_immutable(span, &self.describe_lvalue(lvalue).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_lvalue(lvalue) { 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 `lvalue` if one can be found. If the // lvalue is a temporary for instance, None will be returned. fn describe_lvalue(&self, lvalue: &Lvalue<'tcx>) -> Option { let mut buf = String::new(); match self.append_lvalue_to_string(lvalue, &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, lvalue: &Lvalue<'tcx>) -> Option { match *lvalue { Lvalue::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 `lvalue` to `buf`. fn append_lvalue_to_string(&self, lvalue: &Lvalue<'tcx>, buf: &mut String, mut autoderef: bool) -> Result<(), ()> { match *lvalue { Lvalue::Local(local) => { self.append_local_to_string(local, buf,)?; } Lvalue::Static(ref static_) => { buf.push_str(&format!("{}", &self.tcx.item_name(static_.def_id))); } Lvalue::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_lvalue_to_string(&proj.base, buf, autoderef)?; } else { buf.push_str(&"*"); self.append_lvalue_to_string(&proj.base, buf, autoderef)?; } } }, ProjectionElem::Downcast(..) => { self.append_lvalue_to_string(&proj.base, buf, autoderef)?; }, ProjectionElem::Field(field, _ty) => { autoderef = true; if let Some(field) = self.is_upvar_field_projection(lvalue) { 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_lvalue_to_string(&proj.base, buf, autoderef)?; buf.push_str(&format!(".{}", field_name)); } }, ProjectionElem::Index(index) => { autoderef = true; self.append_lvalue_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_lvalue_to_string(&proj.base, buf, autoderef)?; buf.push_str(&"[..]"); }, }; } } Ok(()) } // Appends end-user visible description of the `local` lvalue 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: &Lvalue, field: Field) -> String { match *base { Lvalue::Local(local) => { let local = &self.mir.local_decls[local]; self.describe_field_from_ty(&local.ty, field) }, Lvalue::Static(ref static_) => { self.describe_field_from_ty(&static_.ty, field) }, Lvalue::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, lvalue: &'d Lvalue<'tcx>) -> &'d Lvalue<'tcx> { //! Returns the base of the leftmost (deepest) dereference of an //! Box in `lvalue`. If there is no dereference of an Box //! in `lvalue`, then it just returns `lvalue` itself. let mut cursor = lvalue; let mut deepest = lvalue; loop { let proj = match *cursor { Lvalue::Local(..) | Lvalue::Static(..) => return deepest, Lvalue::Projection(ref proj) => proj, }; if proj.elem == ProjectionElem::Deref && lvalue.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>, inits: DataflowResults>, uninits: DataflowResults>, move_out: DataflowResults>, ever_inits: DataflowResults>) -> 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(&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>), XI: FnMut(&mut FlowInProgress>), XU: FnMut(&mut FlowInProgress>), XM: FnMut(&mut FlowInProgress>), XE: FnMut(&mut FlowInProgress>), { 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> { fn has_any_child_of(&self, mpi: MovePathIndex) -> Option { 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 FlowInProgress where BD: BitDenotation { fn each_state_bit(&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(&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) -> 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 { let univ = self.base_results.sets().bits_per_block(); self.curr_state.elems(univ) } }