| // 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 borrow_check::nll::region_infer::RegionInferenceContext; |
| use rustc::hir; |
| use rustc::hir::def_id::DefId; |
| use rustc::hir::map::definitions::DefPathData; |
| 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::mir::ClosureRegionRequirements; |
| |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_data_structures::indexed_set::IdxSetBuf; |
| use rustc_data_structures::indexed_vec::Idx; |
| |
| use std::rc::Rc; |
| |
| use syntax::ast; |
| use syntax_pos::Span; |
| |
| use dataflow::{do_dataflow, DebugFormatted}; |
| use dataflow::FlowAtLocation; |
| use dataflow::MoveDataParamEnv; |
| use dataflow::{DataflowAnalysis, DataflowResultsConsumer}; |
| use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals}; |
| use dataflow::{EverInitializedLvals, MovingOutStatements}; |
| use dataflow::{BorrowData, Borrows, ReserveOrActivateIndex}; |
| use dataflow::{ActiveBorrows, Reservations}; |
| use dataflow::indexes::BorrowIndex; |
| use dataflow::move_paths::{IllegalMoveOriginKind, MoveError}; |
| use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex}; |
| use util::borrowck_errors::{BorrowckErrors, Origin}; |
| |
| use std::iter; |
| |
| use self::flows::Flows; |
| use self::prefixes::PrefixSet; |
| use self::MutateMode::{JustWrite, WriteAndRead}; |
| |
| mod error_reporting; |
| mod flows; |
| mod prefixes; |
| |
| use std::borrow::Cow; |
| |
| 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, |
| ) -> Option<ClosureRegionRequirements<'tcx>> { |
| 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.use_mir() { |
| return None; |
| } |
| |
| let opt_closure_req = tcx.infer_ctxt().enter(|infcx| { |
| let input_mir: &Mir = &input_mir.borrow(); |
| do_mir_borrowck(&infcx, input_mir, def_id) |
| }); |
| debug!("mir_borrowck done"); |
| |
| opt_closure_req |
| } |
| |
| fn do_mir_borrowck<'a, 'gcx, 'tcx>( |
| infcx: &InferCtxt<'a, 'gcx, 'tcx>, |
| input_mir: &Mir<'gcx>, |
| def_id: DefId, |
| ) -> Option<ClosureRegionRequirements<'gcx>> { |
| 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.nll() { |
| None |
| } else { |
| let mir = &mut mir; |
| |
| // Replace all regions with fresh inference variables. |
| Some(nll::replace_regions_in_mir(infcx, def_id, param_env, 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 body_id = match tcx.def_key(def_id).disambiguated_data.data { |
| DefPathData::StructCtor | DefPathData::EnumVariant(_) => None, |
| _ => Some(tcx.hir.body_owned_by(id)), |
| }; |
| |
| let dead_unwinds = IdxSetBuf::new_empty(mir.basic_blocks().len()); |
| let mut flow_inits = FlowAtLocation::new(do_dataflow( |
| tcx, |
| mir, |
| id, |
| &attributes, |
| &dead_unwinds, |
| MaybeInitializedLvals::new(tcx, mir, &mdpe), |
| |bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]), |
| )); |
| let flow_uninits = FlowAtLocation::new(do_dataflow( |
| tcx, |
| mir, |
| id, |
| &attributes, |
| &dead_unwinds, |
| MaybeUninitializedLvals::new(tcx, mir, &mdpe), |
| |bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]), |
| )); |
| let flow_move_outs = FlowAtLocation::new(do_dataflow( |
| tcx, |
| mir, |
| id, |
| &attributes, |
| &dead_unwinds, |
| MovingOutStatements::new(tcx, mir, &mdpe), |
| |bd, i| DebugFormatted::new(&bd.move_data().moves[i]), |
| )); |
| let flow_ever_inits = FlowAtLocation::new(do_dataflow( |
| tcx, |
| mir, |
| id, |
| &attributes, |
| &dead_unwinds, |
| EverInitializedLvals::new(tcx, mir, &mdpe), |
| |bd, i| DebugFormatted::new(&bd.move_data().inits[i]), |
| )); |
| |
| // If we are in non-lexical mode, compute the non-lexical lifetimes. |
| let (opt_regioncx, opt_closure_req) = if let Some(free_regions) = free_regions { |
| let (regioncx, opt_closure_req) = nll::compute_regions( |
| infcx, |
| def_id, |
| free_regions, |
| mir, |
| param_env, |
| &mut flow_inits, |
| &mdpe.move_data, |
| ); |
| (Some(Rc::new(regioncx)), opt_closure_req) |
| } else { |
| assert!(!tcx.sess.nll()); |
| (None, 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, |
| locals_are_invalidated_at_exit: match tcx.hir.body_owner_kind(id) { |
| hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => false, |
| hir::BodyOwnerKind::Fn => true, |
| }, |
| storage_dead_or_drop_error_reported_l: FxHashSet(), |
| storage_dead_or_drop_error_reported_s: FxHashSet(), |
| reservation_error_reported: FxHashSet(), |
| nonlexical_regioncx: opt_regioncx.clone(), |
| }; |
| |
| let borrows = Borrows::new(tcx, mir, opt_regioncx, def_id, body_id); |
| let flow_reservations = do_dataflow( |
| tcx, |
| mir, |
| id, |
| &attributes, |
| &dead_unwinds, |
| Reservations::new(borrows), |
| |rs, i| { |
| // In principle we could make the dataflow ensure that |
| // only reservation bits show up, and assert so here. |
| // |
| // In practice it is easier to be looser; in particular, |
| // it is okay for the kill-sets to hold activation bits. |
| DebugFormatted::new(&(i.kind(), rs.location(i))) |
| }, |
| ); |
| let flow_active_borrows = { |
| let reservations_on_entry = flow_reservations.0.sets.entry_set_state(); |
| let reservations = flow_reservations.0.operator; |
| let a = DataflowAnalysis::new_with_entry_sets( |
| mir, |
| &dead_unwinds, |
| Cow::Borrowed(reservations_on_entry), |
| ActiveBorrows::new(reservations), |
| ); |
| let results = a.run(tcx, id, &attributes, |ab, i| { |
| DebugFormatted::new(&(i.kind(), ab.location(i))) |
| }); |
| FlowAtLocation::new(results) |
| }; |
| |
| let mut state = Flows::new( |
| flow_active_borrows, |
| flow_inits, |
| flow_uninits, |
| flow_move_outs, |
| flow_ever_inits, |
| ); |
| |
| mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer |
| |
| opt_closure_req |
| } |
| |
| #[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 keeps track of whether local variables are free-ed when the function |
| /// exits even without a `StorageDead`, which appears to be the case for |
| /// constants. |
| /// |
| /// I'm not sure this is the right approach - @eddyb could you try and |
| /// figure this out? |
| locals_are_invalidated_at_exit: bool, |
| /// 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_l: FxHashSet<Local>, |
| /// Same as the above, but for statics (thread-locals) |
| storage_dead_or_drop_error_reported_s: FxHashSet<DefId>, |
| /// This field keeps track of when borrow conflict errors are reported |
| /// for reservations, so that we don't report seemingly duplicate |
| /// errors for corresponding activations |
| /// |
| /// FIXME: Ideally this would be a set of BorrowIndex, not Places, |
| /// but it is currently inconvenient to track down the BorrowIndex |
| /// at the time we detect and report a reservation error. |
| reservation_error_reported: FxHashSet<Place<'tcx>>, |
| /// Non-lexical region inference context, if NLL is enabled. This |
| /// contains the results from region inference and lets us e.g. |
| /// find out which CFG points are contained in each borrow region. |
| nonlexical_regioncx: Option<Rc<RegionInferenceContext<'tcx>>>, |
| } |
| |
| // 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 = Flows<'cx, 'gcx, 'tcx>; |
| |
| fn mir(&self) -> &'cx Mir<'tcx> { |
| self.mir |
| } |
| |
| fn visit_block_entry(&mut self, bb: BasicBlock, flow_state: &Self::FlowState) { |
| debug!("MirBorrowckCtxt::process_block({:?}): {}", bb, flow_state); |
| } |
| |
| fn visit_statement_entry( |
| &mut self, |
| location: Location, |
| stmt: &Statement<'tcx>, |
| flow_state: &Self::FlowState, |
| ) { |
| debug!( |
| "MirBorrowckCtxt::process_statement({:?}, {:?}): {}", |
| location, |
| stmt, |
| flow_state |
| ); |
| let span = stmt.source_info.span; |
| |
| self.check_activations(location, span, flow_state); |
| |
| match stmt.kind { |
| StatementKind::Assign(ref lhs, ref rhs) => { |
| self.mutate_place( |
| ContextKind::AssignLhs.new(location), |
| (lhs, span), |
| Shallow(None), |
| 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), |
| if o.is_rw { Deep } else { Shallow(None) }, |
| 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; |
| debug!( |
| "MirBorrowckCtxt::process_terminator({:?}, {:?}): {}", |
| location, |
| term, |
| flow_state |
| ); |
| let span = term.source_info.span; |
| |
| self.check_activations(location, span, flow_state); |
| |
| 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.operator(); |
| let data = domain.borrows(); |
| flow_state.borrows.with_elems_outgoing(|borrows| { |
| for i in borrows { |
| let borrow = &data[i.borrow_index()]; |
| let context = ContextKind::StorageDead.new(loc); |
| self.check_for_invalidation_at_exit(context, borrow, span, flow_state); |
| } |
| }); |
| } |
| TerminatorKind::Goto { target: _ } |
| | TerminatorKind::Abort |
| | 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::{Activation, Read, Reservation, 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), |
| |
| /// For two-phase borrows, we distinguish a reservation (which is treated |
| /// like a Read) from an activation (which is treated like a write), and |
| /// each of those is furthermore distinguished from Reads/Writes above. |
| Reservation(WriteKind), |
| Activation(WriteKind, BorrowIndex), |
| } |
| |
| /// 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, |
| /// We want use of immutable upvars to cause a "write to immutable upvar" |
| /// error, not an "reassignment" error. |
| ExceptUpvars, |
| No, |
| } |
| |
| struct AccessErrorsReported { |
| mutability_error: bool, |
| #[allow(dead_code)] conflict_error: bool, |
| } |
| |
| #[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: &Flows<'cx, 'gcx, 'tcx>, |
| ) -> AccessErrorsReported { |
| let (sd, rw) = kind; |
| |
| if let Activation(_, borrow_index) = rw { |
| if self.reservation_error_reported.contains(&place_span.0) { |
| debug!( |
| "skipping access_place for activation of invalid reservation \ |
| place: {:?} borrow_index: {:?}", |
| place_span.0, |
| borrow_index |
| ); |
| return AccessErrorsReported { |
| mutability_error: false, |
| conflict_error: true, |
| }; |
| } |
| } |
| |
| let mutability_error = |
| self.check_access_permissions(place_span, rw, is_local_mutation_allowed); |
| let conflict_error = |
| self.check_access_for_conflict(context, place_span, sd, rw, flow_state); |
| |
| AccessErrorsReported { |
| mutability_error, |
| conflict_error, |
| } |
| } |
| |
| fn check_access_for_conflict( |
| &mut self, |
| context: Context, |
| place_span: (&Place<'tcx>, Span), |
| sd: ShallowOrDeep, |
| rw: ReadOrWrite, |
| flow_state: &Flows<'cx, 'gcx, 'tcx>, |
| ) -> bool { |
| let mut error_reported = false; |
| self.each_borrow_involving_path( |
| context, |
| (sd, place_span.0), |
| flow_state, |
| |this, index, borrow| match (rw, borrow.kind) { |
| // Obviously an activation is compatible with its own |
| // reservation (or even prior activating uses of same |
| // borrow); so don't check if they interfere. |
| // |
| // NOTE: *reservations* do conflict with themselves; |
| // thus aren't injecting unsoundenss w/ this check.) |
| (Activation(_, activating), _) if activating == index.borrow_index() => { |
| debug!( |
| "check_access_for_conflict place_span: {:?} sd: {:?} rw: {:?} \ |
| skipping {:?} b/c activation of same borrow_index: {:?}", |
| place_span, |
| sd, |
| rw, |
| (index, borrow), |
| index.borrow_index() |
| ); |
| Control::Continue |
| } |
| |
| (Read(_), BorrowKind::Shared) | (Reservation(..), BorrowKind::Shared) => { |
| Control::Continue |
| } |
| |
| (Read(kind), BorrowKind::Unique) | (Read(kind), BorrowKind::Mut) => { |
| // Reading from mere reservations of mutable-borrows is OK. |
| if this.tcx.sess.two_phase_borrows() && index.is_reservation() |
| { |
| return Control::Continue; |
| } |
| |
| 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 |
| .operator() |
| .opt_region_end_span(&borrow.region); |
| error_reported = true; |
| this.report_conflicting_borrow( |
| context, |
| place_span, |
| bk, |
| &borrow, |
| end_issued_loan_span, |
| ) |
| } |
| } |
| Control::Break |
| } |
| |
| (Reservation(kind), BorrowKind::Unique) |
| | (Reservation(kind), BorrowKind::Mut) |
| | (Activation(kind, _), _) |
| | (Write(kind), _) => { |
| match rw { |
| Reservation(_) => { |
| debug!( |
| "recording invalid reservation of \ |
| place: {:?}", |
| place_span.0 |
| ); |
| this.reservation_error_reported.insert(place_span.0.clone()); |
| } |
| Activation(_, activating) => { |
| debug!( |
| "observing check_place for activation of \ |
| borrow_index: {:?}", |
| activating |
| ); |
| } |
| Read(..) | Write(..) => {} |
| } |
| |
| match kind { |
| WriteKind::MutableBorrow(bk) => { |
| let end_issued_loan_span = flow_state |
| .borrows |
| .operator() |
| .opt_region_end_span(&borrow.region); |
| |
| error_reported = true; |
| this.report_conflicting_borrow( |
| context, |
| place_span, |
| bk, |
| &borrow, |
| end_issued_loan_span, |
| ) |
| } |
| WriteKind::StorageDeadOrDrop => { |
| error_reported = true; |
| this.report_borrowed_value_does_not_live_long_enough( |
| context, |
| borrow, |
| place_span.1, |
| flow_state.borrows.operator(), |
| ); |
| } |
| 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 |
| } |
| }, |
| ); |
| |
| error_reported |
| } |
| |
| fn mutate_place( |
| &mut self, |
| context: Context, |
| place_span: (&Place<'tcx>, Span), |
| kind: ShallowOrDeep, |
| mode: MutateMode, |
| flow_state: &Flows<'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); |
| } |
| } |
| |
| let errors_reported = self.access_place( |
| context, |
| place_span, |
| (kind, Write(WriteKind::Mutate)), |
| // We want immutable upvars to cause an "assignment to immutable var" |
| // error, not an "reassignment of immutable var" error, because the |
| // latter can't find a good previous assignment span. |
| // |
| // There's probably a better way to do this. |
| LocalMutationIsAllowed::ExceptUpvars, |
| flow_state, |
| ); |
| |
| if !errors_reported.mutability_error { |
| // 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: &Flows<'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 => { |
| let wk = WriteKind::MutableBorrow(bk); |
| if self.tcx.sess.two_phase_borrows() { |
| (Deep, Reservation(wk)) |
| } else { |
| (Deep, Write(wk)) |
| } |
| } |
| }; |
| |
| 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: &Flows<'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 check_for_invalidation_at_exit( |
| &mut self, |
| context: Context, |
| borrow: &BorrowData<'tcx>, |
| span: Span, |
| flow_state: &Flows<'cx, 'gcx, 'tcx>, |
| ) { |
| debug!("check_for_invalidation_at_exit({:?})", borrow); |
| let place = &borrow.borrowed_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. |
| // |
| // FIXME: allow thread-locals to borrow other thread locals? |
| 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, self.locals_are_invalidated_at_exit) |
| } |
| 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; |
| } |
| |
| // FIXME: replace this with a proper borrow_conflicts_with_place when |
| // that is merged. |
| let sd = if might_be_alive { Deep } else { Shallow(None) }; |
| |
| if self.places_conflict(place, root_place, sd) { |
| debug!("check_for_invalidation_at_exit({:?}): INVALID", place); |
| // FIXME: should be talking about the region lifetime instead |
| // of just a span here. |
| self.report_borrowed_value_does_not_live_long_enough( |
| context, |
| borrow, |
| span.end_point(), |
| flow_state.borrows.operator(), |
| ) |
| } |
| } |
| |
| fn check_activations( |
| &mut self, |
| location: Location, |
| span: Span, |
| flow_state: &Flows<'cx, 'gcx, 'tcx>, |
| ) { |
| if !self.tcx.sess.two_phase_borrows() { |
| return; |
| } |
| |
| // Two-phase borrow support: For each activation that is newly |
| // generated at this statement, check if it interferes with |
| // another borrow. |
| let domain = flow_state.borrows.operator(); |
| let data = domain.borrows(); |
| flow_state.borrows.each_gen_bit(|gen| { |
| if gen.is_activation() { |
| let borrow_index = gen.borrow_index(); |
| let borrow = &data[borrow_index]; |
| // currently the flow analysis registers |
| // activations for both mutable and immutable |
| // borrows. So make sure we are talking about a |
| // mutable borrow before we check it. |
| match borrow.kind { |
| BorrowKind::Shared => return, |
| BorrowKind::Unique | BorrowKind::Mut => {} |
| } |
| |
| self.access_place( |
| ContextKind::Activation.new(location), |
| (&borrow.borrowed_place, span), |
| ( |
| Deep, |
| Activation(WriteKind::MutableBorrow(borrow.kind), borrow_index), |
| ), |
| LocalMutationIsAllowed::No, |
| flow_state, |
| ); |
| // We do not need to call `check_if_path_is_moved` |
| // again, as we already called it when we made the |
| // initial reservation. |
| } |
| }); |
| } |
| } |
| |
| 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: &Flows<'cx, 'gcx, 'tcx>, |
| ) { |
| debug!("check_if_reassignment_to_immutable_state({:?})", place); |
| // determine if this path has a non-mut owner (and thus needs checking). |
| if let Ok(()) = self.is_mutable(place, LocalMutationIsAllowed::No) { |
| return; |
| } |
| debug!( |
| "check_if_reassignment_to_immutable_state({:?}) - is an imm local", |
| place |
| ); |
| |
| for i in flow_state.ever_inits.elems_incoming() { |
| let init = self.move_data.inits[i]; |
| let init_place = &self.move_data.move_paths[init.path].place; |
| if self.places_conflict(&init_place, place, Deep) { |
| self.report_illegal_reassignment(context, (place, span), init.span); |
| break; |
| } |
| } |
| } |
| |
| fn check_if_path_is_moved( |
| &mut self, |
| context: Context, |
| desired_action: InitializationRequiringAction, |
| place_span: (&Place<'tcx>, Span), |
| flow_state: &Flows<'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; |
| |
| // 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.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: &Flows<'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 { |
| Reservation(WriteKind::MutableBorrow(BorrowKind::Unique)) |
| | Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => { |
| if let Err(_place_err) = self.is_mutable(place, LocalMutationIsAllowed::Yes) { |
| span_bug!(span, "&unique borrow for {:?} should not fail", place); |
| } |
| } |
| Reservation(WriteKind::MutableBorrow(BorrowKind::Mut)) |
| | 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(); |
| }, |
| Reservation(WriteKind::Mutate) | 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(); |
| } |
| } |
| Reservation(WriteKind::Move) |
| | Reservation(WriteKind::StorageDeadOrDrop) |
| | Reservation(WriteKind::MutableBorrow(BorrowKind::Shared)) |
| | 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 |
| ), |
| ); |
| } |
| } |
| |
| Activation(..) => {} // permission checks are done at Reservation point. |
| |
| 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 | LocalMutationIsAllowed::ExceptUpvars => { |
| 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 => { |
| let mode = match self.is_upvar_field_projection(&proj.base) |
| { |
| Some(field) |
| if { |
| self.mir.upvar_decls[field.index()].by_ref |
| } => |
| { |
| is_local_mutation_allowed |
| } |
| _ => LocalMutationIsAllowed::Yes, |
| }; |
| |
| self.is_mutable(&proj.base, mode) |
| } |
| } |
| } |
| ty::TyRawPtr(tnm) => { |
| match tnm.mutbl { |
| // `*const` raw pointers are not mutable |
| hir::MutImmutable => return Err(place), |
| // `*mut` raw pointers are always mutable, regardless of context |
| // The users have to check by themselve. |
| hir::MutMutable => return Ok(()), |
| } |
| } |
| // `Box<T>` owns its content, so mutable if its location is mutable |
| _ if base_ty.is_box() => { |
| self.is_mutable(&proj.base, is_local_mutation_allowed) |
| } |
| // 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(..) => { |
| if let Some(field) = self.is_upvar_field_projection(place) { |
| let decl = &self.mir.upvar_decls[field.index()]; |
| debug!( |
| "decl.mutability={:?} local_mutation_is_allowed={:?} place={:?}", |
| decl, |
| is_local_mutation_allowed, |
| place |
| ); |
| match (decl.mutability, is_local_mutation_allowed) { |
| (Mutability::Not, LocalMutationIsAllowed::No) |
| | (Mutability::Not, LocalMutationIsAllowed::ExceptUpvars) => { |
| Err(place) |
| } |
| (Mutability::Not, LocalMutationIsAllowed::Yes) |
| | (Mutability::Mut, _) => { |
| self.is_mutable(&proj.base, is_local_mutation_allowed) |
| } |
| } |
| } else { |
| self.is_mutable(&proj.base, is_local_mutation_allowed) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /// 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, |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
| enum NoMovePathFound { |
| ReachedStatic, |
| } |
| |
| /// The degree of overlap between 2 places for borrow-checking. |
| enum Overlap { |
| /// The places might partially overlap - in this case, we give |
| /// up and say that they might conflict. This occurs when |
| /// different fields of a union are borrowed. For example, |
| /// if `u` is a union, we have no way of telling how disjoint |
| /// `u.a.x` and `a.b.y` are. |
| Arbitrary, |
| /// The places have the same type, and are either completely disjoint |
| /// or equal - i.e. they can't "partially" overlap as can occur with |
| /// unions. This is the "base case" on which we recur for extensions |
| /// of the place. |
| EqualOrDisjoint, |
| /// The places are disjoint, so we know all extensions of them |
| /// will also be disjoint. |
| Disjoint, |
| } |
| |
| impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { |
| // Given that the bases of `elem1` and `elem2` are always either equal |
| // or disjoint (and have the same type!), return the overlap situation |
| // between `elem1` and `elem2`. |
| fn place_element_conflict(&self, elem1: &Place<'tcx>, elem2: &Place<'tcx>) -> Overlap { |
| match (elem1, elem2) { |
| (Place::Local(l1), Place::Local(l2)) => { |
| if l1 == l2 { |
| // the same local - base case, equal |
| debug!("place_element_conflict: DISJOINT-OR-EQ-LOCAL"); |
| Overlap::EqualOrDisjoint |
| } else { |
| // different locals - base case, disjoint |
| debug!("place_element_conflict: DISJOINT-LOCAL"); |
| Overlap::Disjoint |
| } |
| } |
| (Place::Static(static1), Place::Static(static2)) => { |
| if static1.def_id != static2.def_id { |
| debug!("place_element_conflict: DISJOINT-STATIC"); |
| Overlap::Disjoint |
| } else if self.tcx.is_static_mut(static1.def_id) { |
| // We ignore mutable statics - they can only be unsafe code. |
| debug!("place_element_conflict: IGNORE-STATIC-MUT"); |
| Overlap::Disjoint |
| } else { |
| debug!("place_element_conflict: DISJOINT-OR-EQ-STATIC"); |
| Overlap::EqualOrDisjoint |
| } |
| } |
| (Place::Local(_), Place::Static(_)) | (Place::Static(_), Place::Local(_)) => { |
| debug!("place_element_conflict: DISJOINT-STATIC-LOCAL"); |
| Overlap::Disjoint |
| } |
| (Place::Projection(pi1), Place::Projection(pi2)) => { |
| match (&pi1.elem, &pi2.elem) { |
| (ProjectionElem::Deref, ProjectionElem::Deref) => { |
| // derefs (e.g. `*x` vs. `*x`) - recur. |
| debug!("place_element_conflict: DISJOINT-OR-EQ-DEREF"); |
| Overlap::EqualOrDisjoint |
| } |
| (ProjectionElem::Field(f1, _), ProjectionElem::Field(f2, _)) => { |
| if f1 == f2 { |
| // same field (e.g. `a.y` vs. `a.y`) - recur. |
| debug!("place_element_conflict: DISJOINT-OR-EQ-FIELD"); |
| Overlap::EqualOrDisjoint |
| } else { |
| let ty = pi1.base.ty(self.mir, self.tcx).to_ty(self.tcx); |
| match ty.sty { |
| ty::TyAdt(def, _) if def.is_union() => { |
| // Different fields of a union, we are basically stuck. |
| debug!("place_element_conflict: STUCK-UNION"); |
| Overlap::Arbitrary |
| } |
| _ => { |
| // Different fields of a struct (`a.x` vs. `a.y`). Disjoint! |
| debug!("place_element_conflict: DISJOINT-FIELD"); |
| Overlap::Disjoint |
| } |
| } |
| } |
| } |
| (ProjectionElem::Downcast(_, v1), ProjectionElem::Downcast(_, v2)) => { |
| // different variants are treated as having disjoint fields, |
| // even if they occupy the same "space", because it's |
| // impossible for 2 variants of the same enum to exist |
| // (and therefore, to be borrowed) at the same time. |
| // |
| // Note that this is different from unions - we *do* allow |
| // this code to compile: |
| // |
| // ``` |
| // fn foo(x: &mut Result<i32, i32>) { |
| // let mut v = None; |
| // if let Ok(ref mut a) = *x { |
| // v = Some(a); |
| // } |
| // // here, you would *think* that the |
| // // *entirety* of `x` would be borrowed, |
| // // but in fact only the `Ok` variant is, |
| // // so the `Err` variant is *entirely free*: |
| // if let Err(ref mut a) = *x { |
| // v = Some(a); |
| // } |
| // drop(v); |
| // } |
| // ``` |
| if v1 == v2 { |
| debug!("place_element_conflict: DISJOINT-OR-EQ-FIELD"); |
| Overlap::EqualOrDisjoint |
| } else { |
| debug!("place_element_conflict: DISJOINT-FIELD"); |
| Overlap::Disjoint |
| } |
| } |
| (ProjectionElem::Index(..), ProjectionElem::Index(..)) |
| | (ProjectionElem::Index(..), ProjectionElem::ConstantIndex { .. }) |
| | (ProjectionElem::Index(..), ProjectionElem::Subslice { .. }) |
| | (ProjectionElem::ConstantIndex { .. }, ProjectionElem::Index(..)) |
| | ( |
| ProjectionElem::ConstantIndex { .. }, |
| ProjectionElem::ConstantIndex { .. }, |
| ) |
| | (ProjectionElem::ConstantIndex { .. }, ProjectionElem::Subslice { .. }) |
| | (ProjectionElem::Subslice { .. }, ProjectionElem::Index(..)) |
| | (ProjectionElem::Subslice { .. }, ProjectionElem::ConstantIndex { .. }) |
| | (ProjectionElem::Subslice { .. }, ProjectionElem::Subslice { .. }) => { |
| // Array indexes (`a[0]` vs. `a[i]`). These can either be disjoint |
| // (if the indexes differ) or equal (if they are the same), so this |
| // is the recursive case that gives "equal *or* disjoint" its meaning. |
| // |
| // Note that by construction, MIR at borrowck can't subdivide |
| // `Subslice` accesses (e.g. `a[2..3][i]` will never be present) - they |
| // are only present in slice patterns, and we "merge together" nested |
| // slice patterns. That means we don't have to think about these. It's |
| // probably a good idea to assert this somewhere, but I'm too lazy. |
| // |
| // FIXME(#8636) we might want to return Disjoint if |
| // both projections are constant and disjoint. |
| debug!("place_element_conflict: DISJOINT-OR-EQ-ARRAY"); |
| Overlap::EqualOrDisjoint |
| } |
| |
| (ProjectionElem::Deref, _) |
| | (ProjectionElem::Field(..), _) |
| | (ProjectionElem::Index(..), _) |
| | (ProjectionElem::ConstantIndex { .. }, _) |
| | (ProjectionElem::Subslice { .. }, _) |
| | (ProjectionElem::Downcast(..), _) => bug!( |
| "mismatched projections in place_element_conflict: {:?} and {:?}", |
| elem1, |
| elem2 |
| ), |
| } |
| } |
| (Place::Projection(_), _) | (_, Place::Projection(_)) => bug!( |
| "unexpected elements in place_element_conflict: {:?} and {:?}", |
| elem1, |
| elem2 |
| ), |
| } |
| } |
| |
| /// Returns whether an access of kind `access` to `access_place` conflicts with |
| /// a borrow/full access to `borrow_place` (for deep accesses to mutable |
| /// locations, this function is symmetric between `borrow_place` & `access_place`). |
| fn places_conflict( |
| &mut self, |
| borrow_place: &Place<'tcx>, |
| access_place: &Place<'tcx>, |
| access: ShallowOrDeep, |
| ) -> bool { |
| debug!( |
| "places_conflict({:?},{:?},{:?})", |
| borrow_place, |
| access_place, |
| access |
| ); |
| |
| // Return all the prefixes of `place` in reverse order, including |
| // downcasts. |
| fn place_elements<'a, 'tcx>(place: &'a Place<'tcx>) -> Vec<&'a Place<'tcx>> { |
| let mut result = vec![]; |
| let mut place = place; |
| loop { |
| result.push(place); |
| match place { |
| Place::Projection(interior) => { |
| place = &interior.base; |
| } |
| Place::Local(_) | Place::Static(_) => { |
| result.reverse(); |
| return result; |
| } |
| } |
| } |
| } |
| |
| let borrow_components = place_elements(borrow_place); |
| let access_components = place_elements(access_place); |
| debug!( |
| "places_conflict: components {:?} / {:?}", |
| borrow_components, |
| access_components |
| ); |
| |
| let borrow_components = borrow_components |
| .into_iter() |
| .map(Some) |
| .chain(iter::repeat(None)); |
| let access_components = access_components |
| .into_iter() |
| .map(Some) |
| .chain(iter::repeat(None)); |
| // The borrowck rules for proving disjointness are applied from the "root" of the |
| // borrow forwards, iterating over "similar" projections in lockstep until |
| // we can prove overlap one way or another. Essentially, we treat `Overlap` as |
| // a monoid and report a conflict if the product ends up not being `Disjoint`. |
| // |
| // At each step, if we didn't run out of borrow or place, we know that our elements |
| // have the same type, and that they only overlap if they are the identical. |
| // |
| // For example, if we are comparing these: |
| // BORROW: (*x1[2].y).z.a |
| // ACCESS: (*x1[i].y).w.b |
| // |
| // Then our steps are: |
| // x1 | x1 -- places are the same |
| // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) |
| // x1[2].y | x1[i].y -- equal or disjoint |
| // *x1[2].y | *x1[i].y -- equal or disjoint |
| // (*x1[2].y).z | (*x1[i].y).w -- we are disjoint and don't need to check more! |
| // |
| // Because `zip` does potentially bad things to the iterator inside, this loop |
| // also handles the case where the access might be a *prefix* of the borrow, e.g. |
| // |
| // BORROW: (*x1[2].y).z.a |
| // ACCESS: x1[i].y |
| // |
| // Then our steps are: |
| // x1 | x1 -- places are the same |
| // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) |
| // x1[2].y | x1[i].y -- equal or disjoint |
| // |
| // -- here we run out of access - the borrow can access a part of it. If this |
| // is a full deep access, then we *know* the borrow conflicts with it. However, |
| // if the access is shallow, then we can proceed: |
| // |
| // x1[2].y | (*x1[i].y) -- a deref! the access can't get past this, so we |
| // are disjoint |
| // |
| // Our invariant is, that at each step of the iteration: |
| // - If we didn't run out of access to match, our borrow and access are comparable |
| // and either equal or disjoint. |
| // - If we did run out of accesss, the borrow can access a part of it. |
| for (borrow_c, access_c) in borrow_components.zip(access_components) { |
| // loop invariant: borrow_c is always either equal to access_c or disjoint from it. |
| debug!("places_conflict: {:?} vs. {:?}", borrow_c, access_c); |
| match (borrow_c, access_c) { |
| (None, _) => { |
| // If we didn't run out of access, the borrow can access all of our |
| // place (e.g. a borrow of `a.b` with an access to `a.b.c`), |
| // so we have a conflict. |
| // |
| // If we did, then we still know that the borrow can access a *part* |
| // of our place that our access cares about (a borrow of `a.b.c` |
| // with an access to `a.b`), so we still have a conflict. |
| // |
| // FIXME: Differs from AST-borrowck; includes drive-by fix |
| // to #38899. Will probably need back-compat mode flag. |
| debug!("places_conflict: full borrow, CONFLICT"); |
| return true; |
| } |
| (Some(borrow_c), None) => { |
| // We know that the borrow can access a part of our place. This |
| // is a conflict if that is a part our access cares about. |
| |
| let (base, elem) = match borrow_c { |
| Place::Projection(box Projection { base, elem }) => (base, elem), |
| _ => bug!("place has no base?"), |
| }; |
| let base_ty = base.ty(self.mir, self.tcx).to_ty(self.tcx); |
| |
| match (elem, &base_ty.sty, 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.) |
| // |
| // e.g. a (mutable) borrow of `a[5]` while we read the |
| // array length of `a`. |
| debug!("places_conflict: implicit field"); |
| return false; |
| } |
| |
| (ProjectionElem::Deref, _, Shallow(None)) => { |
| // e.g. a borrow of `*x.y` while we shallowly access `x.y` or some |
| // prefix thereof - the shallow access can't touch anything behind |
| // the pointer. |
| debug!("places_conflict: shallow access behind ptr"); |
| return false; |
| } |
| ( |
| ProjectionElem::Deref, |
| ty::TyRef( |
| _, |
| ty::TypeAndMut { |
| ty: _, |
| mutbl: hir::MutImmutable, |
| }, |
| ), |
| _, |
| ) => { |
| // the borrow goes through a dereference of a shared reference. |
| // |
| // I'm not sure why we are tracking these borrows - shared |
| // references can *always* be aliased, which means the |
| // permission check already account for this borrow. |
| debug!("places_conflict: behind a shared ref"); |
| return false; |
| } |
| |
| (ProjectionElem::Deref, _, Deep) |
| | (ProjectionElem::Field { .. }, _, _) |
| | (ProjectionElem::Index { .. }, _, _) |
| | (ProjectionElem::ConstantIndex { .. }, _, _) |
| | (ProjectionElem::Subslice { .. }, _, _) |
| | (ProjectionElem::Downcast { .. }, _, _) => { |
| // Recursive case. This can still be disjoint on a |
| // further iteration if this a shallow access and |
| // there's a deref later on, e.g. a borrow |
| // of `*x.y` while accessing `x`. |
| } |
| } |
| } |
| (Some(borrow_c), Some(access_c)) => { |
| match self.place_element_conflict(&borrow_c, access_c) { |
| Overlap::Arbitrary => { |
| // We have encountered different fields of potentially |
| // the same union - the borrow now partially overlaps. |
| // |
| // There is no *easy* way of comparing the fields |
| // further on, because they might have different types |
| // (e.g. borrows of `u.a.0` and `u.b.y` where `.0` and |
| // `.y` come from different structs). |
| // |
| // We could try to do some things here - e.g. count |
| // dereferences - but that's probably not a good |
| // idea, at least for now, so just give up and |
| // report a conflict. This is unsafe code anyway so |
| // the user could always use raw pointers. |
| debug!("places_conflict: arbitrary -> conflict"); |
| return true; |
| } |
| Overlap::EqualOrDisjoint => { |
| // This is the recursive case - proceed to the next element. |
| } |
| Overlap::Disjoint => { |
| // We have proven the borrow disjoint - further |
| // projections will remain disjoint. |
| debug!("places_conflict: disjoint"); |
| return false; |
| } |
| } |
| } |
| } |
| } |
| unreachable!("iter::repeat returned None") |
| } |
| |
| /// This function iterates over all of the current borrows |
| /// (represented by 1-bits in `flow_state.borrows`) that conflict |
| /// with an access to a place, invoking the `op` callback for each |
| /// one. |
| /// |
| /// "Current borrow" here means a borrow that reaches the point in |
| /// the control-flow where the access occurs. |
| /// |
| /// The borrow's phase is represented by the ReserveOrActivateIndex |
| /// passed to the callback: one can call `is_reservation()` and |
| /// `is_activation()` to determine what phase the borrow is |
| /// currently in, when such distinction matters. |
| fn each_borrow_involving_path<F>( |
| &mut self, |
| _context: Context, |
| access_place: (ShallowOrDeep, &Place<'tcx>), |
| flow_state: &Flows<'cx, 'gcx, 'tcx>, |
| mut op: F, |
| ) where |
| F: FnMut(&mut Self, ReserveOrActivateIndex, &BorrowData<'tcx>) -> Control, |
| { |
| let (access, place) = access_place; |
| |
| // FIXME: analogous code in check_loans first maps `place` to |
| // its base_path. |
| |
| let data = flow_state.borrows.operator().borrows(); |
| |
| // check for loan restricting path P being used. Accounts for |
| // borrows of P, P.a.b, etc. |
| let mut elems_incoming = flow_state.borrows.elems_incoming(); |
| while let Some(i) = elems_incoming.next() { |
| let borrowed = &data[i.borrow_index()]; |
| |
| if self.places_conflict(&borrowed.borrowed_place, place, access) { |
| debug!("each_borrow_involving_path: {:?} @ {:?} vs. {:?}/{:?}", |
| i, borrowed, place, access); |
| let ctrl = op(self, i, borrowed); |
| if ctrl == Control::Break { |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| 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 { |
| Activation, |
| 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, |
| } |
| } |
| } |