1use std::borrow::Cow;
6
7use rustc_data_structures::fx::FxHashSet;
8use rustc_index::bit_set::DenseBitSet;
9use rustc_middle::mir::visit::{PlaceContext, VisitPlacesWith, Visitor};
10use rustc_middle::mir::*;
11use rustc_middle::ty::TyCtxt;
12use rustc_mir_dataflow::impls::{MaybeStorageDead, MaybeStorageLive, always_storage_live_locals};
13use rustc_mir_dataflow::{Analysis, ResultsCursor};
14
15pub(super) fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
16 let always_live_locals = &always_storage_live_locals(body);
17
18 let maybe_storage_live = MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
19 .iterate_to_fixpoint(tcx, body, None)
20 .into_results_cursor(body);
21
22 let maybe_storage_dead = MaybeStorageDead::new(Cow::Borrowed(always_live_locals))
23 .iterate_to_fixpoint(tcx, body, None)
24 .into_results_cursor(body);
25
26 let mut lint = Lint {
27 tcx,
28 when,
29 body,
30 is_fn_like: tcx.def_kind(body.source.def_id()).is_fn_like(),
31 always_live_locals,
32 maybe_storage_live,
33 maybe_storage_dead,
34 places: Default::default(),
35 };
36 for (bb, data) in traversal::reachable(body) {
37 lint.visit_basic_block_data(bb, data);
38 }
39}
40
41struct Lint<'a, 'tcx> {
42 tcx: TyCtxt<'tcx>,
43 when: String,
44 body: &'a Body<'tcx>,
45 is_fn_like: bool,
46 always_live_locals: &'a DenseBitSet<Local>,
47 maybe_storage_live: ResultsCursor<'a, 'tcx, MaybeStorageLive<'a>>,
48 maybe_storage_dead: ResultsCursor<'a, 'tcx, MaybeStorageDead<'a>>,
49 places: FxHashSet<PlaceRef<'tcx>>,
50}
51
52impl<'a, 'tcx> Lint<'a, 'tcx> {
53 #[track_caller]
54 fn fail(&self, location: Location, msg: impl AsRef<str>) {
55 let span = self.body.source_info(location).span;
56 self.tcx.sess.dcx().span_delayed_bug(
57 span,
58 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("broken MIR in {0:?} ({1}) at {2:?}:\n{3}",
self.body.source.instance, self.when, location, msg.as_ref()))
})format!(
59 "broken MIR in {:?} ({}) at {:?}:\n{}",
60 self.body.source.instance,
61 self.when,
62 location,
63 msg.as_ref()
64 ),
65 );
66 }
67}
68
69fn places_conflict_for_assignment<'tcx>(dest: Place<'tcx>, src: Place<'tcx>) -> bool {
72 dest == src || (dest.local == src.local && !dest.is_indirect() && !src.is_indirect())
73}
74
75impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
76 fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
77 if context.is_use() {
78 self.maybe_storage_dead.seek_after_primary_effect(location);
79 if self.maybe_storage_dead.get().contains(local) {
80 self.fail(location, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use of local {0:?}, which has no storage here",
local))
})format!("use of local {local:?}, which has no storage here"));
81 }
82 }
83 }
84
85 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
86 match &statement.kind {
87 StatementKind::Assign(box (dest, rvalue)) => {
88 let forbid_aliasing = match rvalue {
89 Rvalue::Use(..)
90 | Rvalue::CopyForDeref(..)
91 | Rvalue::Repeat(..)
92 | Rvalue::Aggregate(..)
93 | Rvalue::Cast(..)
94 | Rvalue::WrapUnsafeBinder(..) => true,
95 Rvalue::ThreadLocalRef(..)
96 | Rvalue::UnaryOp(..)
97 | Rvalue::BinaryOp(..)
98 | Rvalue::Ref(..)
99 | Rvalue::Reborrow(..)
100 | Rvalue::RawPtr(..)
101 | Rvalue::Discriminant(..) => false,
102 };
103 if forbid_aliasing {
105 VisitPlacesWith(|src: Place<'tcx>, _| {
106 if places_conflict_for_assignment(*dest, src) {
107 self.fail(
108 location,
109 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("encountered `{0:?}` statement with overlapping memory",
statement))
})format!(
110 "encountered `{statement:?}` statement with overlapping memory"
111 ),
112 );
113 }
114 })
115 .visit_rvalue(rvalue, location);
116 }
117 }
118 StatementKind::StorageLive(local) => {
119 self.maybe_storage_live.seek_before_primary_effect(location);
120 if self.maybe_storage_live.get().contains(*local) {
121 self.fail(
122 location,
123 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("StorageLive({0:?}) which already has storage here",
local))
})format!("StorageLive({local:?}) which already has storage here"),
124 );
125 }
126 }
127 _ => {}
128 }
129
130 self.super_statement(statement, location);
131 }
132
133 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
134 match &terminator.kind {
135 TerminatorKind::Return => {
136 if self.is_fn_like {
137 self.maybe_storage_live.seek_after_primary_effect(location);
138 for local in self.maybe_storage_live.get().iter() {
139 if !self.always_live_locals.contains(local) {
140 self.fail(
141 location,
142 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("local {0:?} still has storage when returning from function",
local))
})format!(
143 "local {local:?} still has storage when returning from function"
144 ),
145 );
146 }
147 }
148 }
149 }
150 TerminatorKind::Call { args, destination, .. } => {
151 self.places.clear();
155 self.places.insert(destination.as_ref());
156 let mut has_duplicates = false;
157 for arg in args {
158 if let Operand::Move(place) = &arg.node {
159 has_duplicates |= !self.places.insert(place.as_ref());
160 }
161 }
162 if has_duplicates {
163 self.fail(
164 location,
165 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("encountered overlapping memory in `Move` arguments to `Call` terminator: {0:?}",
terminator.kind))
})format!(
166 "encountered overlapping memory in `Move` arguments to `Call` terminator: {:?}",
167 terminator.kind,
168 ),
169 );
170 }
171 }
172 _ => {}
173 }
174
175 self.super_terminator(terminator, location);
176 }
177}