1 use crate::move_paths::builder::MoveDat; 2 use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; 3 use rustc_index::{IndexSlice, IndexVec}; 4 use rustc_middle::mir::*; 5 use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; 6 use rustc_span::Span; 7 use smallvec::SmallVec; 8 9 use std::fmt; 10 use std::ops::{Index, IndexMut}; 11 12 use self::abs_domain::{AbstractElem, Lift}; 13 14 mod abs_domain; 15 16 rustc_index::newtype_index! { 17 #[debug_format = "mp{}"] 18 pub struct MovePathIndex {} 19 } 20 21 impl polonius_engine::Atom for MovePathIndex { index(self) -> usize22 fn index(self) -> usize { 23 rustc_index::Idx::index(self) 24 } 25 } 26 27 rustc_index::newtype_index! { 28 #[debug_format = "mo{}"] 29 pub struct MoveOutIndex {} 30 } 31 32 rustc_index::newtype_index! { 33 #[debug_format = "in{}"] 34 pub struct InitIndex {} 35 } 36 37 impl MoveOutIndex { move_path_index(self, move_data: &MoveData<'_>) -> MovePathIndex38 pub fn move_path_index(self, move_data: &MoveData<'_>) -> MovePathIndex { 39 move_data.moves[self].path 40 } 41 } 42 43 /// `MovePath` is a canonicalized representation of a path that is 44 /// moved or assigned to. 45 /// 46 /// It follows a tree structure. 47 /// 48 /// Given `struct X { m: M, n: N }` and `x: X`, moves like `drop x.m;` 49 /// move *out* of the place `x.m`. 50 /// 51 /// The MovePaths representing `x.m` and `x.n` are siblings (that is, 52 /// one of them will link to the other via the `next_sibling` field, 53 /// and the other will have no entry in its `next_sibling` field), and 54 /// they both have the MovePath representing `x` as their parent. 55 #[derive(Clone)] 56 pub struct MovePath<'tcx> { 57 pub next_sibling: Option<MovePathIndex>, 58 pub first_child: Option<MovePathIndex>, 59 pub parent: Option<MovePathIndex>, 60 pub place: Place<'tcx>, 61 } 62 63 impl<'tcx> MovePath<'tcx> { 64 /// Returns an iterator over the parents of `self`. parents<'a>( &self, move_paths: &'a IndexSlice<MovePathIndex, MovePath<'tcx>>, ) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)>65 pub fn parents<'a>( 66 &self, 67 move_paths: &'a IndexSlice<MovePathIndex, MovePath<'tcx>>, 68 ) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> { 69 let first = self.parent.map(|mpi| (mpi, &move_paths[mpi])); 70 MovePathLinearIter { 71 next: first, 72 fetch_next: move |_, parent: &MovePath<'_>| { 73 parent.parent.map(|mpi| (mpi, &move_paths[mpi])) 74 }, 75 } 76 } 77 78 /// Returns an iterator over the immediate children of `self`. children<'a>( &self, move_paths: &'a IndexSlice<MovePathIndex, MovePath<'tcx>>, ) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)>79 pub fn children<'a>( 80 &self, 81 move_paths: &'a IndexSlice<MovePathIndex, MovePath<'tcx>>, 82 ) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> { 83 let first = self.first_child.map(|mpi| (mpi, &move_paths[mpi])); 84 MovePathLinearIter { 85 next: first, 86 fetch_next: move |_, child: &MovePath<'_>| { 87 child.next_sibling.map(|mpi| (mpi, &move_paths[mpi])) 88 }, 89 } 90 } 91 92 /// Finds the closest descendant of `self` for which `f` returns `true` using a breadth-first 93 /// search. 94 /// 95 /// `f` will **not** be called on `self`. find_descendant( &self, move_paths: &IndexSlice<MovePathIndex, MovePath<'_>>, f: impl Fn(MovePathIndex) -> bool, ) -> Option<MovePathIndex>96 pub fn find_descendant( 97 &self, 98 move_paths: &IndexSlice<MovePathIndex, MovePath<'_>>, 99 f: impl Fn(MovePathIndex) -> bool, 100 ) -> Option<MovePathIndex> { 101 let mut todo = if let Some(child) = self.first_child { 102 vec![child] 103 } else { 104 return None; 105 }; 106 107 while let Some(mpi) = todo.pop() { 108 if f(mpi) { 109 return Some(mpi); 110 } 111 112 let move_path = &move_paths[mpi]; 113 if let Some(child) = move_path.first_child { 114 todo.push(child); 115 } 116 117 // After we've processed the original `mpi`, we should always 118 // traverse the siblings of any of its children. 119 if let Some(sibling) = move_path.next_sibling { 120 todo.push(sibling); 121 } 122 } 123 124 None 125 } 126 } 127 128 impl<'tcx> fmt::Debug for MovePath<'tcx> { fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result129 fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result { 130 write!(w, "MovePath {{")?; 131 if let Some(parent) = self.parent { 132 write!(w, " parent: {parent:?},")?; 133 } 134 if let Some(first_child) = self.first_child { 135 write!(w, " first_child: {first_child:?},")?; 136 } 137 if let Some(next_sibling) = self.next_sibling { 138 write!(w, " next_sibling: {next_sibling:?}")?; 139 } 140 write!(w, " place: {:?} }}", self.place) 141 } 142 } 143 144 impl<'tcx> fmt::Display for MovePath<'tcx> { fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result145 fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result { 146 write!(w, "{:?}", self.place) 147 } 148 } 149 150 struct MovePathLinearIter<'a, 'tcx, F> { 151 next: Option<(MovePathIndex, &'a MovePath<'tcx>)>, 152 fetch_next: F, 153 } 154 155 impl<'a, 'tcx, F> Iterator for MovePathLinearIter<'a, 'tcx, F> 156 where 157 F: FnMut(MovePathIndex, &'a MovePath<'tcx>) -> Option<(MovePathIndex, &'a MovePath<'tcx>)>, 158 { 159 type Item = (MovePathIndex, &'a MovePath<'tcx>); 160 next(&mut self) -> Option<Self::Item>161 fn next(&mut self) -> Option<Self::Item> { 162 let ret = self.next.take()?; 163 self.next = (self.fetch_next)(ret.0, ret.1); 164 Some(ret) 165 } 166 } 167 168 #[derive(Debug)] 169 pub struct MoveData<'tcx> { 170 pub move_paths: IndexVec<MovePathIndex, MovePath<'tcx>>, 171 pub moves: IndexVec<MoveOutIndex, MoveOut>, 172 /// Each Location `l` is mapped to the MoveOut's that are effects 173 /// of executing the code at `l`. (There can be multiple MoveOut's 174 /// for a given `l` because each MoveOut is associated with one 175 /// particular path being moved.) 176 pub loc_map: LocationMap<SmallVec<[MoveOutIndex; 4]>>, 177 pub path_map: IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>, 178 pub rev_lookup: MovePathLookup<'tcx>, 179 pub inits: IndexVec<InitIndex, Init>, 180 /// Each Location `l` is mapped to the Inits that are effects 181 /// of executing the code at `l`. 182 pub init_loc_map: LocationMap<SmallVec<[InitIndex; 4]>>, 183 pub init_path_map: IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>, 184 } 185 186 pub trait HasMoveData<'tcx> { move_data(&self) -> &MoveData<'tcx>187 fn move_data(&self) -> &MoveData<'tcx>; 188 } 189 190 #[derive(Debug)] 191 pub struct LocationMap<T> { 192 /// Location-indexed (BasicBlock for outer index, index within BB 193 /// for inner index) map. 194 pub(crate) map: IndexVec<BasicBlock, Vec<T>>, 195 } 196 197 impl<T> Index<Location> for LocationMap<T> { 198 type Output = T; index(&self, index: Location) -> &Self::Output199 fn index(&self, index: Location) -> &Self::Output { 200 &self.map[index.block][index.statement_index] 201 } 202 } 203 204 impl<T> IndexMut<Location> for LocationMap<T> { index_mut(&mut self, index: Location) -> &mut Self::Output205 fn index_mut(&mut self, index: Location) -> &mut Self::Output { 206 &mut self.map[index.block][index.statement_index] 207 } 208 } 209 210 impl<T> LocationMap<T> 211 where 212 T: Default + Clone, 213 { new(body: &Body<'_>) -> Self214 fn new(body: &Body<'_>) -> Self { 215 LocationMap { 216 map: body 217 .basic_blocks 218 .iter() 219 .map(|block| vec![T::default(); block.statements.len() + 1]) 220 .collect(), 221 } 222 } 223 } 224 225 /// `MoveOut` represents a point in a program that moves out of some 226 /// L-value; i.e., "creates" uninitialized memory. 227 /// 228 /// With respect to dataflow analysis: 229 /// - Generated by moves and declaration of uninitialized variables. 230 /// - Killed by assignments to the memory. 231 #[derive(Copy, Clone)] 232 pub struct MoveOut { 233 /// path being moved 234 pub path: MovePathIndex, 235 /// location of move 236 pub source: Location, 237 } 238 239 impl fmt::Debug for MoveOut { fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result240 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 241 write!(fmt, "{:?}@{:?}", self.path, self.source) 242 } 243 } 244 245 /// `Init` represents a point in a program that initializes some L-value; 246 #[derive(Copy, Clone)] 247 pub struct Init { 248 /// path being initialized 249 pub path: MovePathIndex, 250 /// location of initialization 251 pub location: InitLocation, 252 /// Extra information about this initialization 253 pub kind: InitKind, 254 } 255 256 /// Initializations can be from an argument or from a statement. Arguments 257 /// do not have locations, in those cases the `Local` is kept.. 258 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 259 pub enum InitLocation { 260 Argument(Local), 261 Statement(Location), 262 } 263 264 /// Additional information about the initialization. 265 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 266 pub enum InitKind { 267 /// Deep init, even on panic 268 Deep, 269 /// Only does a shallow init 270 Shallow, 271 /// This doesn't initialize the variable on panic (and a panic is possible). 272 NonPanicPathOnly, 273 } 274 275 impl fmt::Debug for Init { fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result276 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 277 write!(fmt, "{:?}@{:?} ({:?})", self.path, self.location, self.kind) 278 } 279 } 280 281 impl Init { span<'tcx>(&self, body: &Body<'tcx>) -> Span282 pub fn span<'tcx>(&self, body: &Body<'tcx>) -> Span { 283 match self.location { 284 InitLocation::Argument(local) => body.local_decls[local].source_info.span, 285 InitLocation::Statement(location) => body.source_info(location).span, 286 } 287 } 288 } 289 290 /// Tables mapping from a place to its MovePathIndex. 291 #[derive(Debug)] 292 pub struct MovePathLookup<'tcx> { 293 locals: FxIndexMap<Local, MovePathIndex>, 294 295 /// projections are made from a base-place and a projection 296 /// elem. The base-place will have a unique MovePathIndex; we use 297 /// the latter as the index into the outer vector (narrowing 298 /// subsequent search so that it is solely relative to that 299 /// base-place). For the remaining lookup, we map the projection 300 /// elem to the associated MovePathIndex. 301 projections: FxHashMap<(MovePathIndex, AbstractElem), MovePathIndex>, 302 303 /// Maps `DerefTemp` locals to the `Place`s assigned to them. 304 derefer_sidetable: FxHashMap<Local, Place<'tcx>>, 305 } 306 307 mod builder; 308 309 #[derive(Copy, Clone, Debug)] 310 pub enum LookupResult { 311 Exact(MovePathIndex), 312 Parent(Option<MovePathIndex>), 313 } 314 315 impl<'tcx> MovePathLookup<'tcx> { 316 // Unlike the builder `fn move_path_for` below, this lookup 317 // alternative will *not* create a MovePath on the fly for an 318 // unknown place, but will rather return the nearest available 319 // parent. find(&self, place: PlaceRef<'_>) -> LookupResult320 pub fn find(&self, place: PlaceRef<'_>) -> LookupResult { 321 let deref_chain = self.deref_chain(place); 322 323 let local = match deref_chain.first() { 324 Some(place) => place.local, 325 None => place.local, 326 }; 327 328 let mut result = *self.locals.get(&local).unwrap_or_else(|| { 329 bug!("base local ({local:?}) of deref_chain should not be a deref temp") 330 }); 331 332 // this needs to be a closure because `place` has a different lifetime than `prefix`'s places 333 let mut subpaths_for_place = |place: PlaceRef<'_>| { 334 for elem in place.projection.iter() { 335 if let Some(&subpath) = self.projections.get(&(result, elem.lift())) { 336 result = subpath; 337 } else { 338 return Some(result); 339 } 340 } 341 None 342 }; 343 344 for place in deref_chain { 345 if let Some(result) = subpaths_for_place(place.as_ref()) { 346 return LookupResult::Parent(Some(result)); 347 } 348 } 349 350 if let Some(result) = subpaths_for_place(place) { 351 return LookupResult::Parent(Some(result)); 352 } 353 354 LookupResult::Exact(result) 355 } 356 find_local(&self, local: Local) -> MovePathIndex357 pub fn find_local(&self, local: Local) -> MovePathIndex { 358 let deref_chain = self.deref_chain(Place::from(local).as_ref()); 359 360 let local = match deref_chain.last() { 361 Some(place) => place.local, 362 None => local, 363 }; 364 365 *self.locals.get(&local).unwrap_or_else(|| { 366 bug!("base local ({local:?}) of deref_chain should not be a deref temp") 367 }) 368 } 369 370 /// An enumerated iterator of `local`s and their associated 371 /// `MovePathIndex`es. iter_locals_enumerated( &self, ) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> + ExactSizeIterator + '_372 pub fn iter_locals_enumerated( 373 &self, 374 ) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> + ExactSizeIterator + '_ { 375 self.locals.iter().map(|(&l, &idx)| (l, idx)) 376 } 377 378 /// Returns the chain of places behind `DerefTemp` locals in `place` deref_chain(&self, place: PlaceRef<'_>) -> Vec<Place<'tcx>>379 pub fn deref_chain(&self, place: PlaceRef<'_>) -> Vec<Place<'tcx>> { 380 let mut prefix = Vec::new(); 381 let mut local = place.local; 382 383 while let Some(&reffed) = self.derefer_sidetable.get(&local) { 384 prefix.insert(0, reffed); 385 local = reffed.local; 386 } 387 388 debug!("deref_chain({place:?}) = {prefix:?}"); 389 390 prefix 391 } 392 } 393 394 #[derive(Debug)] 395 pub struct IllegalMoveOrigin<'tcx> { 396 pub location: Location, 397 pub kind: IllegalMoveOriginKind<'tcx>, 398 } 399 400 #[derive(Debug)] 401 pub enum IllegalMoveOriginKind<'tcx> { 402 /// Illegal move due to attempt to move from behind a reference. 403 BorrowedContent { 404 /// The place the reference refers to: if erroneous code was trying to 405 /// move from `(*x).f` this will be `*x`. 406 target_place: Place<'tcx>, 407 }, 408 409 /// Illegal move due to attempt to move from field of an ADT that 410 /// implements `Drop`. Rust maintains invariant that all `Drop` 411 /// ADT's remain fully-initialized so that user-defined destructor 412 /// can safely read from all of the ADT's fields. 413 InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> }, 414 415 /// Illegal move due to attempt to move out of a slice or array. 416 InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool }, 417 } 418 419 #[derive(Debug)] 420 pub enum MoveError<'tcx> { 421 IllegalMove { cannot_move_out_of: IllegalMoveOrigin<'tcx> }, 422 UnionMove { path: MovePathIndex }, 423 } 424 425 impl<'tcx> MoveError<'tcx> { cannot_move_out_of(location: Location, kind: IllegalMoveOriginKind<'tcx>) -> Self426 fn cannot_move_out_of(location: Location, kind: IllegalMoveOriginKind<'tcx>) -> Self { 427 let origin = IllegalMoveOrigin { location, kind }; 428 MoveError::IllegalMove { cannot_move_out_of: origin } 429 } 430 } 431 432 impl<'tcx> MoveData<'tcx> { gather_moves( body: &Body<'tcx>, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ) -> MoveDat<'tcx>433 pub fn gather_moves( 434 body: &Body<'tcx>, 435 tcx: TyCtxt<'tcx>, 436 param_env: ParamEnv<'tcx>, 437 ) -> MoveDat<'tcx> { 438 builder::gather_moves(body, tcx, param_env) 439 } 440 441 /// For the move path `mpi`, returns the root local variable (if any) that starts the path. 442 /// (e.g., for a path like `a.b.c` returns `Some(a)`) base_local(&self, mut mpi: MovePathIndex) -> Option<Local>443 pub fn base_local(&self, mut mpi: MovePathIndex) -> Option<Local> { 444 loop { 445 let path = &self.move_paths[mpi]; 446 if let Some(l) = path.place.as_local() { 447 return Some(l); 448 } 449 if let Some(parent) = path.parent { 450 mpi = parent; 451 continue; 452 } else { 453 return None; 454 } 455 } 456 } 457 find_in_move_path_or_its_descendants( &self, root: MovePathIndex, pred: impl Fn(MovePathIndex) -> bool, ) -> Option<MovePathIndex>458 pub fn find_in_move_path_or_its_descendants( 459 &self, 460 root: MovePathIndex, 461 pred: impl Fn(MovePathIndex) -> bool, 462 ) -> Option<MovePathIndex> { 463 if pred(root) { 464 return Some(root); 465 } 466 467 self.move_paths[root].find_descendant(&self.move_paths, pred) 468 } 469 } 470