1use std::{borrow::Cow, collections::VecDeque};
4
5use log::{trace, warn};
6use rustc_abi::{FieldIdx, VariantIdx};
7use rustc_data_structures::fx::{FxHashMap as HashMap, FxHashSet as HashSet};
8use rustc_hir::def_id::DefId;
9use rustc_middle::{
10 mir::{
11 Body, HasLocalDecls, Local, Location, Mutability, Place, PlaceElem, PlaceRef,
12 ProjectionElem, RETURN_PLACE, VarDebugInfo, VarDebugInfoContents,
13 visit::{PlaceContext, Visitor},
14 },
15 ty::{self, AdtKind, Region, RegionKind, RegionVid, Ty, TyCtxt, TyKind, TypeVisitor},
16};
17use rustc_type_ir::Unnormalized;
18
19use crate::{AdtDefExt, SpanExt};
20
21#[derive(Default)]
23pub struct PlaceCollector<'tcx>(pub Vec<Place<'tcx>>);
24
25impl<'tcx> Visitor<'tcx> for PlaceCollector<'tcx> {
26 fn visit_place(
27 &mut self,
28 place: &Place<'tcx>,
29 _context: PlaceContext,
30 _location: Location,
31 ) {
32 self.0.push(*place);
33 }
34}
35
36pub trait PlaceExt<'tcx> {
38 fn make(local: Local, projection: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self;
40
41 fn from_ref(place: PlaceRef<'tcx>, tcx: TyCtxt<'tcx>) -> Self;
43
44 fn from_local(local: Local, tcx: TyCtxt<'tcx>) -> Self;
46
47 fn is_arg(&self, body: &Body<'tcx>) -> bool;
49
50 fn is_direct(&self, body: &Body<'tcx>, tcx: TyCtxt<'tcx>) -> bool;
57
58 fn refs_in_projection(
61 self,
62 body: &Body<'tcx>,
63 tcx: TyCtxt<'tcx>,
64 ) -> impl Iterator<Item = (PlaceRef<'tcx>, &'tcx [PlaceElem<'tcx>])>;
65
66 fn interior_pointers(
70 &self,
71 tcx: TyCtxt<'tcx>,
72 body: &Body<'tcx>,
73 def_id: DefId,
74 ) -> HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>>;
75
76 fn interior_places(
79 &self,
80 tcx: TyCtxt<'tcx>,
81 body: &Body<'tcx>,
82 def_id: DefId,
83 ) -> HashSet<Place<'tcx>>;
84
85 fn interior_paths(
87 &self,
88 tcx: TyCtxt<'tcx>,
89 body: &Body<'tcx>,
90 def_id: DefId,
91 ) -> HashSet<Place<'tcx>>;
92
93 fn to_string(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> Option<String>;
95
96 fn normalize(&self, tcx: TyCtxt<'tcx>, def_id: DefId) -> Place<'tcx>;
104
105 fn is_source_visible(&self, tcx: TyCtxt, body: &Body) -> bool;
107}
108
109impl<'tcx> PlaceExt<'tcx> for Place<'tcx> {
110 fn make(local: Local, projection: &[PlaceElem<'tcx>], tcx: TyCtxt<'tcx>) -> Self {
111 Place {
112 local,
113 projection: tcx.mk_place_elems(projection),
114 }
115 }
116
117 fn from_ref(place: PlaceRef<'tcx>, tcx: TyCtxt<'tcx>) -> Self {
118 Self::make(place.local, place.projection, tcx)
119 }
120
121 fn from_local(local: Local, tcx: TyCtxt<'tcx>) -> Self {
122 Place::make(local, &[], tcx)
123 }
124
125 fn is_arg(&self, body: &Body<'tcx>) -> bool {
126 let i = self.local.as_usize();
127 i > 0 && i - 1 < body.arg_count
128 }
129
130 fn is_direct(&self, body: &Body<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
131 !self.is_indirect()
132 || self.is_arg(body)
133 || self.refs_in_projection(body, tcx).next().is_none()
134 }
135
136 fn refs_in_projection(
137 self,
138 body: &Body<'tcx>,
139 tcx: TyCtxt<'tcx>,
140 ) -> impl Iterator<Item = (PlaceRef<'tcx>, &'tcx [PlaceElem<'tcx>])> {
141 self
142 .iter_projections()
143 .enumerate()
144 .filter_map(move |(i, (place_ref, elem))| match elem {
145 ProjectionElem::Deref => {
146 let ptr = PlaceRef {
147 local: self.local,
148 projection: &self.projection[.. i],
149 };
150 let after = &self.projection[i + 1 ..];
151 (!place_ref.ty(body.local_decls(), tcx).ty.is_box()).then_some((ptr, after))
152 }
153 _ => None,
154 })
155 }
156
157 fn interior_pointers(
158 &self,
159 tcx: TyCtxt<'tcx>,
160 body: &Body<'tcx>,
161 def_id: DefId,
162 ) -> HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>> {
163 let ty = self.ty(body.local_decls(), tcx).ty;
164 let mut region_collector = RegionVisitor::<RegionMemberCollector>::new(
165 tcx,
166 def_id,
167 *self,
168 if
169 false {
171 StoppingCondition::AfterRefs
172 } else {
173 StoppingCondition::None
174 },
175 );
176 region_collector.visit_ty(ty);
177 region_collector.into_inner().0
178 }
179
180 fn interior_places(
181 &self,
182 tcx: TyCtxt<'tcx>,
183 body: &Body<'tcx>,
184 def_id: DefId,
185 ) -> HashSet<Place<'tcx>> {
186 let ty = self.ty(body.local_decls(), tcx).ty;
187 let mut region_collector = RegionVisitor::<VisitedPlacesCollector>::new(
188 tcx,
189 def_id,
190 *self,
191 StoppingCondition::BeforeRefs,
192 );
193 region_collector.visit_ty(ty);
194 region_collector.into_inner().0
195 }
196
197 fn interior_paths(
198 &self,
199 tcx: TyCtxt<'tcx>,
200 body: &Body<'tcx>,
201 def_id: DefId,
202 ) -> HashSet<Place<'tcx>> {
203 let ty = self.ty(body.local_decls(), tcx).ty;
204 let mut region_collector = RegionVisitor::<VisitedPlacesCollector>::new(
205 tcx,
206 def_id,
207 *self,
208 StoppingCondition::None,
209 );
210 region_collector.visit_ty(ty);
211 region_collector.into_inner().0
212 }
213
214 fn to_string(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> Option<String> {
215 let local_name = if self.local == RETURN_PLACE {
217 Cow::Borrowed("RETURN")
218 } else {
219 let get_local_name = |info: &VarDebugInfo<'tcx>| match info.value {
220 VarDebugInfoContents::Place(place) if place.local == self.local => info
221 .source_info
222 .span
223 .as_local(body.span)
224 .map(|_| info.name.to_string()),
225 _ => None,
226 };
227 let local_name = body.var_debug_info.iter().find_map(get_local_name)?;
228 Cow::Owned(local_name)
229 };
230
231 #[derive(Copy, Clone)]
232 enum ElemPosition {
233 Prefix,
234 Suffix,
235 }
236
237 let elem_to_string = |(index, (place, elem)): (
240 usize,
241 (PlaceRef<'tcx>, PlaceElem<'tcx>),
242 )|
243 -> (ElemPosition, Cow<'static, str>) {
244 match elem {
245 ProjectionElem::Deref => (ElemPosition::Prefix, "*".into()),
246
247 ProjectionElem::Field(field, _) => {
248 let ty = place.ty(&body.local_decls, tcx).ty;
249
250 let field_name = match ty.kind() {
251 TyKind::Adt(def, _substs) => {
252 let fields = match def.adt_kind() {
253 AdtKind::Struct => &def.non_enum_variant().fields,
254 AdtKind::Enum => {
255 let Some(PlaceElem::Downcast(_, variant_idx)) =
256 self.projection.get(index - 1)
257 else {
258 unimplemented!()
259 };
260 &def.variant(*variant_idx).fields
261 }
262 kind => unimplemented!("{kind:?}"),
263 };
264
265 fields[field].ident(tcx).to_string()
266 }
267
268 TyKind::Tuple(_) => field.as_usize().to_string(),
269
270 TyKind::Closure(def_id, _substs) => match def_id.as_local() {
271 Some(local_def_id) => {
272 let captures = tcx.closure_captures(local_def_id);
273 captures[field.as_usize()].var_ident.to_string()
274 }
275 None => field.as_usize().to_string(),
276 },
277
278 kind => unimplemented!("{kind:?}"),
279 };
280
281 (ElemPosition::Suffix, format!(".{field_name}").into())
282 }
283 ProjectionElem::Downcast(sym, _) => {
284 let variant = sym.map(|s| s.to_string()).unwrap_or_else(|| "??".into());
285 (ElemPosition::Suffix, format!("@{variant}").into())
286 }
287
288 ProjectionElem::Index(_) => (ElemPosition::Suffix, "[_]".into()),
289 kind => unimplemented!("{kind:?}"),
290 }
291 };
292
293 let (positions, contents): (Vec<_>, Vec<_>) = self
294 .iter_projections()
295 .enumerate()
296 .map(elem_to_string)
297 .unzip();
298
299 let mut parts = VecDeque::from([local_name]);
301 for (i, string) in contents.into_iter().enumerate() {
302 match positions[i] {
303 ElemPosition::Prefix => {
304 parts.push_front(string);
305 if matches!(positions.get(i + 1), Some(ElemPosition::Suffix)) {
306 parts.push_front(Cow::Borrowed("("));
307 parts.push_back(Cow::Borrowed(")"));
308 }
309 }
310 ElemPosition::Suffix => parts.push_back(string),
311 }
312 }
313
314 let full = parts.make_contiguous().join("");
315 Some(full)
316 }
317
318 fn normalize(&self, tcx: TyCtxt<'tcx>, def_id: DefId) -> Place<'tcx> {
319 let place = tcx.normalize_erasing_regions(
320 tcx.typing_env_normalized_for_post_analysis(def_id),
321 Unnormalized::new_wip(*self),
322 );
323
324 let projection = place
325 .projection
326 .into_iter()
327 .filter_map(|elem| match elem {
328 ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => {
330 Some(ProjectionElem::Index(Local::from_usize(0)))
331 }
332 ProjectionElem::Subslice { .. } => None,
335 _ => Some(elem),
336 })
337 .collect::<Vec<_>>();
338
339 Place::make(place.local, &projection, tcx)
340 }
341
342 fn is_source_visible(&self, _tcx: TyCtxt, body: &Body) -> bool {
343 let local = self.local;
344 let local_info = &body.local_decls[local];
345 let is_loc = local_info.is_user_variable();
346 let from_desugaring = local_info.from_compiler_desugaring();
347 let from_expansion = local_info.source_info.span.from_expansion();
348
349 is_loc && !from_desugaring && !from_expansion
354 }
355}
356
357#[derive(Copy, Clone)]
358enum StoppingCondition {
359 None,
360 BeforeRefs,
361 AfterRefs,
362}
363
364trait RegionVisitorDispatcher<'tcx> {
365 fn on_visit_place(&mut self, _: Place<'tcx>) {}
366 fn on_visit_type(&mut self, _: Ty<'tcx>) {}
367 fn on_visit_region_member(&mut self, _: RegionVid, _: Place<'tcx>, _: Mutability) {}
368}
369
370#[derive(Default)]
371struct VisitedPlacesCollector<'tcx>(HashSet<Place<'tcx>>);
372
373impl<'tcx> RegionVisitorDispatcher<'tcx> for VisitedPlacesCollector<'tcx> {
374 fn on_visit_place(&mut self, place: Place<'tcx>) {
375 self.0.insert(place);
376 }
377}
378
379#[derive(Default)]
380struct RegionMemberCollector<'tcx>(HashMap<RegionVid, Vec<(Place<'tcx>, Mutability)>>);
381
382impl<'tcx> RegionVisitorDispatcher<'tcx> for RegionMemberCollector<'tcx> {
383 fn on_visit_region_member(
384 &mut self,
385 key: RegionVid,
386 place: Place<'tcx>,
387 mutbl: Mutability,
388 ) {
389 self.0.entry(key).or_default().push((place, mutbl));
390 }
391}
392
393struct RegionVisitor<'tcx, Dispatcher> {
394 tcx: TyCtxt<'tcx>,
395 def_id: DefId,
396 local: Local,
398 place_stack: Vec<PlaceElem<'tcx>>,
403 ty_stack: Vec<Ty<'tcx>>,
406 dispatcher: Dispatcher,
408 stop_at: StoppingCondition,
409}
410
411impl<'tcx, Dispatcher: Default> RegionVisitor<'tcx, Dispatcher> {
412 fn new(
417 tcx: TyCtxt<'tcx>,
418 def_id: DefId,
419 place: Place<'tcx>,
420 stop_at: StoppingCondition,
421 ) -> Self {
422 Self {
423 tcx,
424 def_id,
425 local: place.local,
426 place_stack: place.projection.to_vec(),
427 ty_stack: Vec::new(),
428 dispatcher: Default::default(),
429 stop_at,
430 }
431 }
432
433 fn into_inner(self) -> Dispatcher {
434 self.dispatcher
435 }
436}
437
438pub const UNKNOWN_REGION: RegionVid = RegionVid::MAX;
440
441impl<'tcx, Dispatcher: RegionVisitorDispatcher<'tcx>> TypeVisitor<TyCtxt<'tcx>>
442 for RegionVisitor<'tcx, Dispatcher>
443{
444 fn visit_ty(&mut self, ty: Ty<'tcx>) {
445 let tcx = self.tcx;
446 if self.ty_stack.contains(&ty) {
447 return;
448 }
449
450 trace!(
451 "exploring {:?} with {ty:?}",
452 Place::make(self.local, &self.place_stack, tcx)
453 );
454
455 self.ty_stack.push(ty);
456
457 match ty.kind() {
458 _ if ty.is_box() => {
459 self.place_stack.push(ProjectionElem::Deref);
460 self.visit_ty(ty.boxed_ty().expect("Cannot unbox boxed type??"));
461 self.place_stack.pop();
462 }
463
464 TyKind::Tuple(fields) => {
465 for (i, field) in fields.iter().enumerate() {
466 self
467 .place_stack
468 .push(ProjectionElem::Field(FieldIdx::from_usize(i), field));
469 self.visit_ty(field);
470 self.place_stack.pop();
471 }
472 }
473
474 TyKind::Adt(adt_def, subst) => match adt_def.adt_kind() {
475 ty::AdtKind::Struct => {
476 for (i, field) in adt_def.all_visible_fields(self.def_id, tcx).enumerate() {
477 let ty = field.ty(tcx, subst);
478 self
479 .place_stack
480 .push(ProjectionElem::Field(FieldIdx::from_usize(i), ty));
481 self.visit_ty(ty);
482 self.place_stack.pop();
483 }
484 }
485 ty::AdtKind::Union => {
486 }
488 ty::AdtKind::Enum => {
489 for (i, variant) in adt_def.variants().iter().enumerate() {
490 let variant_index = VariantIdx::from_usize(i);
491 let cast = PlaceElem::Downcast(
492 Some(adt_def.variant(variant_index).ident(tcx).name),
493 variant_index,
494 );
495 self.place_stack.push(cast);
496 for (j, field) in variant.fields.iter().enumerate() {
497 let ty = field.ty(tcx, subst);
498 let field = ProjectionElem::Field(FieldIdx::from_usize(j), ty);
499 self.place_stack.push(field);
500 self.visit_ty(ty);
501 self.place_stack.pop();
502 }
503 self.place_stack.pop();
504 }
505 }
506 },
507
508 TyKind::Array(elem_ty, _) | TyKind::Slice(elem_ty) => {
509 self
510 .place_stack
511 .push(ProjectionElem::Index(Local::from_usize(0)));
512 self.visit_ty(*elem_ty);
513 self.place_stack.pop();
514 }
515
516 TyKind::Ref(region, elem_ty, _) => match self.stop_at {
517 StoppingCondition::None => {
518 self.visit_region(*region);
519 self.place_stack.push(ProjectionElem::Deref);
520 self.visit_ty(*elem_ty);
521 self.place_stack.pop();
522 }
523 StoppingCondition::AfterRefs => {
524 self.visit_region(*region);
525 }
526 StoppingCondition::BeforeRefs => {}
527 },
528
529 TyKind::Closure(_, substs) | TyKind::Coroutine(_, substs) => {
530 self.visit_ty(substs.as_closure().tupled_upvars_ty());
531 }
532
533 TyKind::RawPtr(ty, _) => {
534 self.visit_region(Region::new_var(tcx, UNKNOWN_REGION));
535 self.place_stack.push(ProjectionElem::Deref);
536 self.visit_ty(*ty);
537 self.place_stack.pop();
538 }
539
540 TyKind::FnDef(..)
541 | TyKind::FnPtr(..)
542 | TyKind::Foreign(..)
543 | TyKind::Dynamic(..)
544 | TyKind::Param(..)
545 | TyKind::Never => {}
546
547 _ if ty.is_primitive_ty() => {}
548
549 _ => warn!("unimplemented {ty:?} ({:?})", ty.kind()),
550 }
551
552 self
582 .dispatcher
583 .on_visit_place(Place::make(self.local, &self.place_stack, tcx));
584
585 self.dispatcher.on_visit_type(ty);
586
587 self.ty_stack.pop();
588 }
589
590 fn visit_region(&mut self, region: ty::Region<'tcx>) -> Self::Result {
591 trace!("visiting region {region:?}");
592 let region = match region.kind() {
593 RegionKind::ReVar(region) => region,
594 RegionKind::ReStatic => RegionVid::from_usize(0),
595 RegionKind::ReErased | RegionKind::ReLateParam(_) => {
596 return;
597 }
598 _ => unreachable!("{:?}: {:?}", self.ty_stack.first().unwrap(), region),
599 };
600
601 let mutability = if self
602 .ty_stack
603 .iter()
604 .any(|ty| ty.is_ref() && ty.ref_mutability().unwrap() == Mutability::Not)
605 {
606 Mutability::Not
607 } else {
608 Mutability::Mut
609 };
610
611 let place = Place::make(self.local, &self.place_stack, self.tcx);
612
613 self
614 .dispatcher
615 .on_visit_region_member(region, place, mutability);
616
617 self
622 .dispatcher
623 .on_visit_place(self.tcx.mk_place_deref(place));
624 }
625}
626
627#[cfg(test)]
628mod test {
629 use rustc_borrowck::consumers::BodyWithBorrowckFacts;
630 use rustc_hir::BodyId;
631 use rustc_middle::{
632 mir::{Place, PlaceElem},
633 ty::TyCtxt,
634 };
635
636 use crate::{
637 BodyExt, PlaceExt,
638 test_utils::{self, Placer, compare_sets},
639 };
640
641 #[test]
642 fn test_place_arg_direct() {
643 let input = r"
644fn foobar(x: &i32) {
645 let y = 1;
646 let z = &y;
647 let k = Box::new(*x);
648 let ref_k = &k;
649 let box_ref = Box::new(x);
650}
651";
652 test_utils::compile_body(input, |tcx, _, body_with_facts| {
653 let body = &body_with_facts.body;
654 let name_map = body.debug_info_name_map();
655 let x = Place::from_local(name_map["x"], tcx);
656 assert!(x.is_arg(body));
657 assert!(x.is_direct(body, tcx));
658 assert!(Place::make(x.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
659
660 let y = Place::from_local(name_map["y"], tcx);
661 assert!(!y.is_arg(body));
662 assert!(y.is_direct(body, tcx));
663
664 let z = Place::from_local(name_map["z"], tcx);
665 assert!(!z.is_arg(body));
666 assert!(z.is_direct(body, tcx));
667 assert!(!Place::make(z.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
668
669 let k = Place::from_local(name_map["k"], tcx);
670 assert!(!k.is_arg(body));
671 assert!(k.is_direct(body, tcx));
672 assert!(Place::make(k.local, &[PlaceElem::Deref], tcx).is_direct(body, tcx));
673 let deref_k = Place::make(k.local, &[PlaceElem::Deref], tcx);
674 assert!(deref_k.is_direct(body, tcx));
675 assert!(!deref_k.is_arg(body));
676 assert_eq!(deref_k.refs_in_projection(body, tcx).count(), 0);
677
678 let ref_k = Place::from_local(name_map["ref_k"], tcx);
679 assert!(!ref_k.is_arg(body));
680 assert!(k.is_direct(body, tcx));
681 let deref_ref_k =
682 Place::make(ref_k.local, &[PlaceElem::Deref, PlaceElem::Deref], tcx);
683 assert!(!deref_ref_k.is_direct(body, tcx));
684 assert_eq!(deref_ref_k.refs_in_projection(body, tcx).count(), 1);
685
686 let box_ref = Place::from_local(name_map["box_ref"], tcx);
687 assert!(!box_ref.is_arg(body));
688 assert!(!box_ref.is_indirect());
689 let box_ref_deref = Place::make(box_ref.local, &[PlaceElem::Deref], tcx);
690 assert_eq!(box_ref_deref.refs_in_projection(body, tcx).count(), 0);
691 assert!(box_ref_deref.is_direct(body, tcx));
692 let box_ref_deref_deref = box_ref_deref.project_deeper(&[PlaceElem::Deref], tcx);
693 assert_eq!(box_ref_deref_deref.refs_in_projection(body, tcx).count(), 1);
694 assert!(!box_ref_deref_deref.is_direct(body, tcx));
695 });
696 }
697
698 #[test]
699 fn test_place_to_string() {
700 let input = r"
701struct Point { x: usize, y: usize }
702fn main() {
703 let x = (0, 0);
704 let y = Some(1);
705 let z = &[Some((0, 1))];
706 let w = (&y,);
707 let p = &Point { x: 0, y: 0 };
708}";
709 test_utils::compile_body(input, |tcx, _, body_with_facts| {
710 let body = &body_with_facts.body;
711 let p = Placer::new(tcx, body);
712
713 let x = p.local("x").mk();
714 let x_1 = p.local("x").field(1).mk();
715 let y_some_0 = p.local("y").downcast(1).field(0).mk();
716 let z_deref_some_0_1 = p
717 .local("z")
718 .deref()
719 .index(0)
720 .downcast(1)
721 .field(0)
722 .field(1)
723 .mk();
724 let w_0_deref = p.local("w").field(0).deref().mk();
725 let w_0_deref_some = p.local("w").field(0).deref().downcast(1).mk();
726 let p_deref_x = p.local("p").deref().field(0).mk();
727
728 let tests = [
729 (x, "x"),
730 (x_1, "x.1"),
731 (y_some_0, "y@Some.0"),
732 (z_deref_some_0_1, "(*z)[_]@Some.0.1"),
733 (w_0_deref, "*w.0"),
734 (w_0_deref_some, "(*w.0)@Some"),
735 (p_deref_x, "(*p).x"),
736 ];
737
738 for (place, expected) in tests {
739 assert_eq!(place.to_string(tcx, body).unwrap(), expected);
740 }
741 });
742 }
743
744 #[test]
745 fn test_place_visitors() {
746 let input = r"
747fn main() {
748 let x = 0;
749 let y = (0, &x);
750}
751";
752 fn callback<'tcx>(
753 tcx: TyCtxt<'tcx>,
754 body_id: BodyId,
755 body_with_facts: &BodyWithBorrowckFacts<'tcx>,
756 ) {
757 let body = &body_with_facts.body;
758 let def_id = tcx.hir_body_owner_def_id(body_id).to_def_id();
759 let p = Placer::new(tcx, body);
760
761 let y = p.local("y").mk();
762 let y0 = p.local("y").field(0).mk();
763 let y1 = p.local("y").field(1).mk();
764 let y1_deref = p.local("y").field(1).deref().mk();
765
766 compare_sets(y.interior_paths(tcx, body, def_id), [y, y0, y1, y1_deref]);
767
768 compare_sets(y.interior_places(tcx, body, def_id), [y, y0, y1]);
769
770 compare_sets(
771 y.interior_pointers(tcx, body, def_id)
772 .into_values()
773 .flat_map(|vs| vs.into_iter().map(|(p, _)| p)),
774 [y1],
775 );
776 }
777 test_utils::compile_body(input, callback);
778 }
779}