| use rustc::mir::tcx::RvalueInitializationState; |
| use rustc::mir::*; |
| use rustc::ty::{self, TyCtxt}; |
| use rustc_index::vec::IndexVec; |
| use smallvec::{smallvec, SmallVec}; |
| |
| use std::convert::TryInto; |
| use std::mem; |
| |
| use super::abs_domain::Lift; |
| use super::IllegalMoveOriginKind::*; |
| use super::{Init, InitIndex, InitKind, InitLocation, LookupResult, MoveError}; |
| use super::{ |
| LocationMap, MoveData, MoveOut, MoveOutIndex, MovePath, MovePathIndex, MovePathLookup, |
| }; |
| |
| struct MoveDataBuilder<'a, 'tcx> { |
| body: &'a Body<'tcx>, |
| tcx: TyCtxt<'tcx>, |
| param_env: ty::ParamEnv<'tcx>, |
| data: MoveData<'tcx>, |
| errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, |
| } |
| |
| impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { |
| fn new(body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { |
| let mut move_paths = IndexVec::new(); |
| let mut path_map = IndexVec::new(); |
| let mut init_path_map = IndexVec::new(); |
| |
| MoveDataBuilder { |
| body, |
| tcx, |
| param_env, |
| errors: Vec::new(), |
| data: MoveData { |
| moves: IndexVec::new(), |
| loc_map: LocationMap::new(body), |
| rev_lookup: MovePathLookup { |
| locals: body |
| .local_decls |
| .indices() |
| .map(|i| { |
| Self::new_move_path( |
| &mut move_paths, |
| &mut path_map, |
| &mut init_path_map, |
| None, |
| Place::from(i), |
| ) |
| }) |
| .collect(), |
| projections: Default::default(), |
| }, |
| move_paths, |
| path_map, |
| inits: IndexVec::new(), |
| init_loc_map: LocationMap::new(body), |
| init_path_map, |
| }, |
| } |
| } |
| |
| fn new_move_path( |
| move_paths: &mut IndexVec<MovePathIndex, MovePath<'tcx>>, |
| path_map: &mut IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>, |
| init_path_map: &mut IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>, |
| parent: Option<MovePathIndex>, |
| place: Place<'tcx>, |
| ) -> MovePathIndex { |
| let move_path = |
| move_paths.push(MovePath { next_sibling: None, first_child: None, parent, place }); |
| |
| if let Some(parent) = parent { |
| let next_sibling = mem::replace(&mut move_paths[parent].first_child, Some(move_path)); |
| move_paths[move_path].next_sibling = next_sibling; |
| } |
| |
| let path_map_ent = path_map.push(smallvec![]); |
| assert_eq!(path_map_ent, move_path); |
| |
| let init_path_map_ent = init_path_map.push(smallvec![]); |
| assert_eq!(init_path_map_ent, move_path); |
| |
| move_path |
| } |
| } |
| |
| impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { |
| /// This creates a MovePath for a given place, returning an `MovePathError` |
| /// if that place can't be moved from. |
| /// |
| /// NOTE: places behind references *do not* get a move path, which is |
| /// problematic for borrowck. |
| /// |
| /// Maybe we should have separate "borrowck" and "moveck" modes. |
| fn move_path_for(&mut self, place: &Place<'tcx>) -> Result<MovePathIndex, MoveError<'tcx>> { |
| debug!("lookup({:?})", place); |
| let mut base = self.builder.data.rev_lookup.locals[place.local]; |
| |
| // The move path index of the first union that we find. Once this is |
| // some we stop creating child move paths, since moves from unions |
| // move the whole thing. |
| // We continue looking for other move errors though so that moving |
| // from `*(u.f: &_)` isn't allowed. |
| let mut union_path = None; |
| |
| for (i, elem) in place.projection.iter().enumerate() { |
| let proj_base = &place.projection[..i]; |
| let body = self.builder.body; |
| let tcx = self.builder.tcx; |
| let place_ty = Place::ty_from(place.local, proj_base, body, tcx).ty; |
| match place_ty.kind { |
| ty::Ref(..) | ty::RawPtr(..) => { |
| let proj = &place.projection[..i + 1]; |
| return Err(MoveError::cannot_move_out_of( |
| self.loc, |
| BorrowedContent { |
| target_place: Place { |
| local: place.local, |
| projection: tcx.intern_place_elems(proj), |
| }, |
| }, |
| )); |
| } |
| ty::Adt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() => { |
| return Err(MoveError::cannot_move_out_of( |
| self.loc, |
| InteriorOfTypeWithDestructor { container_ty: place_ty }, |
| )); |
| } |
| ty::Adt(adt, _) if adt.is_union() => { |
| union_path.get_or_insert(base); |
| } |
| ty::Slice(_) => { |
| return Err(MoveError::cannot_move_out_of( |
| self.loc, |
| InteriorOfSliceOrArray { |
| ty: place_ty, |
| is_index: match elem { |
| ProjectionElem::Index(..) => true, |
| _ => false, |
| }, |
| }, |
| )); |
| } |
| ty::Array(..) => match elem { |
| ProjectionElem::Index(..) => { |
| return Err(MoveError::cannot_move_out_of( |
| self.loc, |
| InteriorOfSliceOrArray { ty: place_ty, is_index: true }, |
| )); |
| } |
| _ => {} |
| }, |
| _ => {} |
| }; |
| |
| if union_path.is_none() { |
| base = self.add_move_path(base, elem, |tcx| Place { |
| local: place.local, |
| projection: tcx.intern_place_elems(&place.projection[..i + 1]), |
| }); |
| } |
| } |
| |
| if let Some(base) = union_path { |
| // Move out of union - always move the entire union. |
| Err(MoveError::UnionMove { path: base }) |
| } else { |
| Ok(base) |
| } |
| } |
| |
| fn add_move_path( |
| &mut self, |
| base: MovePathIndex, |
| elem: &PlaceElem<'tcx>, |
| mk_place: impl FnOnce(TyCtxt<'tcx>) -> Place<'tcx>, |
| ) -> MovePathIndex { |
| let MoveDataBuilder { |
| data: MoveData { rev_lookup, move_paths, path_map, init_path_map, .. }, |
| tcx, |
| .. |
| } = self.builder; |
| *rev_lookup.projections.entry((base, elem.lift())).or_insert_with(move || { |
| let path = MoveDataBuilder::new_move_path( |
| move_paths, |
| path_map, |
| init_path_map, |
| Some(base), |
| mk_place(*tcx), |
| ); |
| path |
| }) |
| } |
| |
| fn create_move_path(&mut self, place: &Place<'tcx>) { |
| // This is an non-moving access (such as an overwrite or |
| // drop), so this not being a valid move path is OK. |
| let _ = self.move_path_for(place); |
| } |
| } |
| |
| impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { |
| fn finalize( |
| self, |
| ) -> Result<MoveData<'tcx>, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)> { |
| debug!("{}", { |
| debug!("moves for {:?}:", self.body.span); |
| for (j, mo) in self.data.moves.iter_enumerated() { |
| debug!(" {:?} = {:?}", j, mo); |
| } |
| debug!("move paths for {:?}:", self.body.span); |
| for (j, path) in self.data.move_paths.iter_enumerated() { |
| debug!(" {:?} = {:?}", j, path); |
| } |
| "done dumping moves" |
| }); |
| |
| if !self.errors.is_empty() { Err((self.data, self.errors)) } else { Ok(self.data) } |
| } |
| } |
| |
| pub(super) fn gather_moves<'tcx>( |
| body: &Body<'tcx>, |
| tcx: TyCtxt<'tcx>, |
| param_env: ty::ParamEnv<'tcx>, |
| ) -> Result<MoveData<'tcx>, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)> { |
| let mut builder = MoveDataBuilder::new(body, tcx, param_env); |
| |
| builder.gather_args(); |
| |
| for (bb, block) in body.basic_blocks().iter_enumerated() { |
| for (i, stmt) in block.statements.iter().enumerate() { |
| let source = Location { block: bb, statement_index: i }; |
| builder.gather_statement(source, stmt); |
| } |
| |
| let terminator_loc = Location { block: bb, statement_index: block.statements.len() }; |
| builder.gather_terminator(terminator_loc, block.terminator()); |
| } |
| |
| builder.finalize() |
| } |
| |
| impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { |
| fn gather_args(&mut self) { |
| for arg in self.body.args_iter() { |
| let path = self.data.rev_lookup.locals[arg]; |
| |
| let init = self.data.inits.push(Init { |
| path, |
| kind: InitKind::Deep, |
| location: InitLocation::Argument(arg), |
| }); |
| |
| debug!("gather_args: adding init {:?} of {:?} for argument {:?}", init, path, arg); |
| |
| self.data.init_path_map[path].push(init); |
| } |
| } |
| |
| fn gather_statement(&mut self, loc: Location, stmt: &Statement<'tcx>) { |
| debug!("gather_statement({:?}, {:?})", loc, stmt); |
| (Gatherer { builder: self, loc }).gather_statement(stmt); |
| } |
| |
| fn gather_terminator(&mut self, loc: Location, term: &Terminator<'tcx>) { |
| debug!("gather_terminator({:?}, {:?})", loc, term); |
| (Gatherer { builder: self, loc }).gather_terminator(term); |
| } |
| } |
| |
| struct Gatherer<'b, 'a, 'tcx> { |
| builder: &'b mut MoveDataBuilder<'a, 'tcx>, |
| loc: Location, |
| } |
| |
| impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { |
| fn gather_statement(&mut self, stmt: &Statement<'tcx>) { |
| match stmt.kind { |
| StatementKind::Assign(box (ref place, ref rval)) => { |
| self.create_move_path(place); |
| if let RvalueInitializationState::Shallow = rval.initialization_state() { |
| // Box starts out uninitialized - need to create a separate |
| // move-path for the interior so it will be separate from |
| // the exterior. |
| self.create_move_path(&self.builder.tcx.mk_place_deref(place.clone())); |
| self.gather_init(place.as_ref(), InitKind::Shallow); |
| } else { |
| self.gather_init(place.as_ref(), InitKind::Deep); |
| } |
| self.gather_rvalue(rval); |
| } |
| StatementKind::FakeRead(_, ref place) => { |
| self.create_move_path(place); |
| } |
| StatementKind::InlineAsm(ref asm) => { |
| for (output, kind) in asm.outputs.iter().zip(&asm.asm.outputs) { |
| if !kind.is_indirect { |
| self.gather_init(output.as_ref(), InitKind::Deep); |
| } |
| } |
| for (_, input) in asm.inputs.iter() { |
| self.gather_operand(input); |
| } |
| } |
| StatementKind::StorageLive(_) => {} |
| StatementKind::StorageDead(local) => { |
| self.gather_move(&Place::from(local)); |
| } |
| StatementKind::SetDiscriminant { .. } => { |
| span_bug!( |
| stmt.source_info.span, |
| "SetDiscriminant should not exist during borrowck" |
| ); |
| } |
| StatementKind::Retag { .. } |
| | StatementKind::AscribeUserType(..) |
| | StatementKind::Nop => {} |
| } |
| } |
| |
| fn gather_rvalue(&mut self, rvalue: &Rvalue<'tcx>) { |
| match *rvalue { |
| Rvalue::Use(ref operand) |
| | Rvalue::Repeat(ref operand, _) |
| | Rvalue::Cast(_, ref operand, _) |
| | Rvalue::UnaryOp(_, ref operand) => self.gather_operand(operand), |
| Rvalue::BinaryOp(ref _binop, ref lhs, ref rhs) |
| | Rvalue::CheckedBinaryOp(ref _binop, ref lhs, ref rhs) => { |
| self.gather_operand(lhs); |
| self.gather_operand(rhs); |
| } |
| Rvalue::Aggregate(ref _kind, ref operands) => { |
| for operand in operands { |
| self.gather_operand(operand); |
| } |
| } |
| Rvalue::Ref(..) |
| | Rvalue::AddressOf(..) |
| | Rvalue::Discriminant(..) |
| | Rvalue::Len(..) |
| | Rvalue::NullaryOp(NullOp::SizeOf, _) |
| | Rvalue::NullaryOp(NullOp::Box, _) => { |
| // This returns an rvalue with uninitialized contents. We can't |
| // move out of it here because it is an rvalue - assignments always |
| // completely initialize their place. |
| // |
| // However, this does not matter - MIR building is careful to |
| // only emit a shallow free for the partially-initialized |
| // temporary. |
| // |
| // In any case, if we want to fix this, we have to register a |
| // special move and change the `statement_effect` functions. |
| } |
| } |
| } |
| |
| fn gather_terminator(&mut self, term: &Terminator<'tcx>) { |
| match term.kind { |
| TerminatorKind::Goto { target: _ } |
| | TerminatorKind::Resume |
| | TerminatorKind::Abort |
| | TerminatorKind::GeneratorDrop |
| | TerminatorKind::FalseEdges { .. } |
| | TerminatorKind::FalseUnwind { .. } |
| | TerminatorKind::Unreachable => {} |
| |
| TerminatorKind::Return => { |
| self.gather_move(&Place::return_place()); |
| } |
| |
| TerminatorKind::Assert { ref cond, .. } => { |
| self.gather_operand(cond); |
| } |
| |
| TerminatorKind::SwitchInt { ref discr, .. } => { |
| self.gather_operand(discr); |
| } |
| |
| TerminatorKind::Yield { ref value, resume_arg: ref place, .. } => { |
| self.gather_operand(value); |
| self.create_move_path(place); |
| self.gather_init(place.as_ref(), InitKind::Deep); |
| } |
| |
| TerminatorKind::Drop { ref location, target: _, unwind: _ } => { |
| self.gather_move(location); |
| } |
| TerminatorKind::DropAndReplace { ref location, ref value, .. } => { |
| self.create_move_path(location); |
| self.gather_operand(value); |
| self.gather_init(location.as_ref(), InitKind::Deep); |
| } |
| TerminatorKind::Call { |
| ref func, |
| ref args, |
| ref destination, |
| cleanup: _, |
| from_hir_call: _, |
| } => { |
| self.gather_operand(func); |
| for arg in args { |
| self.gather_operand(arg); |
| } |
| if let Some((ref destination, _bb)) = *destination { |
| self.create_move_path(destination); |
| self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); |
| } |
| } |
| } |
| } |
| |
| fn gather_operand(&mut self, operand: &Operand<'tcx>) { |
| match *operand { |
| Operand::Constant(..) | Operand::Copy(..) => {} // not-a-move |
| Operand::Move(ref place) => { |
| // a move |
| self.gather_move(place); |
| } |
| } |
| } |
| |
| fn gather_move(&mut self, place: &Place<'tcx>) { |
| debug!("gather_move({:?}, {:?})", self.loc, place); |
| |
| if let [ref base @ .., ProjectionElem::Subslice { from, to, from_end: false }] = |
| **place.projection |
| { |
| // Split `Subslice` patterns into the corresponding list of |
| // `ConstIndex` patterns. This is done to ensure that all move paths |
| // are disjoint, which is expected by drop elaboration. |
| let base_place = |
| Place { local: place.local, projection: self.builder.tcx.intern_place_elems(base) }; |
| let base_path = match self.move_path_for(&base_place) { |
| Ok(path) => path, |
| Err(MoveError::UnionMove { path }) => { |
| self.record_move(place, path); |
| return; |
| } |
| Err(error @ MoveError::IllegalMove { .. }) => { |
| self.builder.errors.push((base_place, error)); |
| return; |
| } |
| }; |
| let base_ty = base_place.ty(self.builder.body, self.builder.tcx).ty; |
| let len: u32 = match base_ty.kind { |
| ty::Array(_, size) => { |
| let length = size.eval_usize(self.builder.tcx, self.builder.param_env); |
| length |
| .try_into() |
| .expect("slice pattern of array with more than u32::MAX elements") |
| } |
| _ => bug!("from_end: false slice pattern of non-array type"), |
| }; |
| for offset in from..to { |
| let elem = |
| ProjectionElem::ConstantIndex { offset, min_length: len, from_end: false }; |
| let path = self.add_move_path(base_path, &elem, |tcx| { |
| tcx.mk_place_elem(base_place.clone(), elem) |
| }); |
| self.record_move(place, path); |
| } |
| } else { |
| match self.move_path_for(place) { |
| Ok(path) | Err(MoveError::UnionMove { path }) => self.record_move(place, path), |
| Err(error @ MoveError::IllegalMove { .. }) => { |
| self.builder.errors.push((*place, error)); |
| } |
| }; |
| } |
| } |
| |
| fn record_move(&mut self, place: &Place<'tcx>, path: MovePathIndex) { |
| let move_out = self.builder.data.moves.push(MoveOut { path, source: self.loc }); |
| debug!( |
| "gather_move({:?}, {:?}): adding move {:?} of {:?}", |
| self.loc, place, move_out, path |
| ); |
| self.builder.data.path_map[path].push(move_out); |
| self.builder.data.loc_map[self.loc].push(move_out); |
| } |
| |
| fn gather_init(&mut self, place: PlaceRef<'tcx>, kind: InitKind) { |
| debug!("gather_init({:?}, {:?})", self.loc, place); |
| |
| let mut place = place; |
| |
| // Check if we are assigning into a field of a union, if so, lookup the place |
| // of the union so it is marked as initialized again. |
| if let [proj_base @ .., ProjectionElem::Field(_, _)] = place.projection { |
| if let ty::Adt(def, _) = |
| Place::ty_from(place.local, proj_base, self.builder.body, self.builder.tcx).ty.kind |
| { |
| if def.is_union() { |
| place = PlaceRef { local: place.local, projection: proj_base } |
| } |
| } |
| } |
| |
| if let LookupResult::Exact(path) = self.builder.data.rev_lookup.find(place) { |
| let init = self.builder.data.inits.push(Init { |
| location: InitLocation::Statement(self.loc), |
| path, |
| kind, |
| }); |
| |
| debug!( |
| "gather_init({:?}, {:?}): adding init {:?} of {:?}", |
| self.loc, place, init, path |
| ); |
| |
| self.builder.data.init_path_map[path].push(init); |
| self.builder.data.init_loc_map[self.loc].push(init); |
| } |
| } |
| } |