]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // This implements the dead-code warning pass. It follows middle::reachable |
2 | // closely. The idea is that all reachable symbols are live, codes called | |
3 | // from live codes are live, and everything else is dead. | |
4 | ||
dfeec247 XL |
5 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
6 | use rustc_hir as hir; | |
7 | use rustc_hir::def::{CtorOf, DefKind, Res}; | |
94222f64 | 8 | use rustc_hir::def_id::{DefId, LocalDefId}; |
dfeec247 XL |
9 | use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; |
10 | use rustc_hir::itemlikevisit::ItemLikeVisitor; | |
11 | use rustc_hir::{Node, PatKind, TyKind}; | |
ba9703b0 XL |
12 | use rustc_middle::hir::map::Map; |
13 | use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; | |
14 | use rustc_middle::middle::privacy; | |
15 | use rustc_middle::ty::{self, DefIdTree, TyCtxt}; | |
dfeec247 | 16 | use rustc_session::lint; |
f9f354fc | 17 | use rustc_span::symbol::{sym, Symbol}; |
94222f64 | 18 | use std::mem; |
1a4d82fc JJ |
19 | |
20 | // Any local node that may call something in its body block should be | |
b7449926 | 21 | // explored. For example, if it's a live Node::Item that is a |
1a4d82fc JJ |
22 | // function, then we should explore its block to check for codes that |
23 | // may need to be marked as live. | |
94222f64 | 24 | fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { |
5869c6ff | 25 | matches!( |
94222f64 | 26 | tcx.hir().find(tcx.hir().local_def_id_to_hir_id(def_id)), |
ba9703b0 XL |
27 | Some( |
28 | Node::Item(..) | |
5869c6ff XL |
29 | | Node::ImplItem(..) |
30 | | Node::ForeignItem(..) | |
31 | | Node::TraitItem(..) | |
32 | | Node::Variant(..) | |
33 | | Node::AnonConst(..) | |
5869c6ff XL |
34 | ) |
35 | ) | |
1a4d82fc JJ |
36 | } |
37 | ||
f035d41b | 38 | struct MarkSymbolVisitor<'tcx> { |
94222f64 | 39 | worklist: Vec<LocalDefId>, |
dc9dc135 | 40 | tcx: TyCtxt<'tcx>, |
3dfed10e | 41 | maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, |
94222f64 | 42 | live_symbols: FxHashSet<LocalDefId>, |
2c00a5a8 | 43 | repr_has_repr_c: bool, |
abe05a73 | 44 | in_pat: bool, |
1a4d82fc | 45 | inherited_pub_visibility: bool, |
17df50a5 | 46 | pub_visibility: bool, |
b039eaaf | 47 | ignore_variant_stack: Vec<DefId>, |
0731742a | 48 | // maps from tuple struct constructors to tuple struct items |
94222f64 | 49 | struct_constructors: FxHashMap<LocalDefId, LocalDefId>, |
1a4d82fc JJ |
50 | } |
51 | ||
f035d41b | 52 | impl<'tcx> MarkSymbolVisitor<'tcx> { |
3dfed10e | 53 | /// Gets the type-checking results for the current body. |
f035d41b XL |
54 | /// As this will ICE if called outside bodies, only call when working with |
55 | /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). | |
56 | #[track_caller] | |
3dfed10e XL |
57 | fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { |
58 | self.maybe_typeck_results | |
59 | .expect("`MarkSymbolVisitor::typeck_results` called outside of body") | |
f035d41b XL |
60 | } |
61 | ||
e9174d1e | 62 | fn check_def_id(&mut self, def_id: DefId) { |
f9f354fc | 63 | if let Some(def_id) = def_id.as_local() { |
94222f64 XL |
64 | if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) { |
65 | self.worklist.push(def_id); | |
b039eaaf | 66 | } |
94222f64 | 67 | self.live_symbols.insert(def_id); |
b039eaaf SL |
68 | } |
69 | } | |
70 | ||
71 | fn insert_def_id(&mut self, def_id: DefId) { | |
f9f354fc | 72 | if let Some(def_id) = def_id.as_local() { |
94222f64 XL |
73 | debug_assert!(!should_explore(self.tcx, def_id)); |
74 | self.live_symbols.insert(def_id); | |
1a4d82fc | 75 | } |
1a4d82fc JJ |
76 | } |
77 | ||
48663c56 XL |
78 | fn handle_res(&mut self, res: Res) { |
79 | match res { | |
ba9703b0 | 80 | Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, _) => { |
48663c56 | 81 | self.check_def_id(res.def_id()); |
3157f602 | 82 | } |
dfeec247 XL |
83 | _ if self.in_pat => {} |
84 | Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {} | |
48663c56 | 85 | Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => { |
532ac7d7 XL |
86 | let variant_id = self.tcx.parent(ctor_def_id).unwrap(); |
87 | let enum_id = self.tcx.parent(variant_id).unwrap(); | |
88 | self.check_def_id(enum_id); | |
89 | if !self.ignore_variant_stack.contains(&ctor_def_id) { | |
90 | self.check_def_id(variant_id); | |
9e0c209e | 91 | } |
532ac7d7 | 92 | } |
48663c56 | 93 | Res::Def(DefKind::Variant, variant_id) => { |
532ac7d7 XL |
94 | let enum_id = self.tcx.parent(variant_id).unwrap(); |
95 | self.check_def_id(enum_id); | |
3157f602 XL |
96 | if !self.ignore_variant_stack.contains(&variant_id) { |
97 | self.check_def_id(variant_id); | |
1a4d82fc JJ |
98 | } |
99 | } | |
e1599b0c XL |
100 | Res::SelfTy(t, i) => { |
101 | if let Some(t) = t { | |
102 | self.check_def_id(t); | |
103 | } | |
1b1a35ee | 104 | if let Some((i, _)) = i { |
e1599b0c XL |
105 | self.check_def_id(i); |
106 | } | |
107 | } | |
48663c56 | 108 | Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {} |
3157f602 | 109 | _ => { |
48663c56 | 110 | self.check_def_id(res.def_id()); |
3157f602 XL |
111 | } |
112 | } | |
1a4d82fc JJ |
113 | } |
114 | ||
3b2f2976 | 115 | fn lookup_and_handle_method(&mut self, id: hir::HirId) { |
3dfed10e | 116 | if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) { |
532ac7d7 | 117 | self.check_def_id(def_id); |
8faf50e0 XL |
118 | } else { |
119 | bug!("no type-dependent def for method"); | |
120 | } | |
1a4d82fc JJ |
121 | } |
122 | ||
dfeec247 | 123 | fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) { |
1b1a35ee | 124 | match self.typeck_results().expr_ty_adjusted(lhs).kind() { |
b7449926 | 125 | ty::Adt(def, _) => { |
3dfed10e | 126 | let index = self.tcx.field_index(hir_id, self.typeck_results()); |
83c7162d | 127 | self.insert_def_id(def.non_enum_variant().fields[index].did); |
9e0c209e | 128 | } |
b7449926 | 129 | ty::Tuple(..) => {} |
83c7162d | 130 | _ => span_bug!(lhs.span, "named field access on non-ADT"), |
1a4d82fc JJ |
131 | } |
132 | } | |
133 | ||
17df50a5 XL |
134 | #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands. |
135 | fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) { | |
136 | if self | |
137 | .typeck_results() | |
138 | .expr_adjustments(expr) | |
139 | .iter() | |
140 | .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) | |
141 | { | |
142 | self.visit_expr(expr); | |
143 | } else if let hir::ExprKind::Field(base, ..) = expr.kind { | |
144 | // Ignore write to field | |
145 | self.handle_assign(base); | |
146 | } else { | |
147 | self.visit_expr(expr); | |
148 | } | |
149 | } | |
150 | ||
136023e0 XL |
151 | #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands. |
152 | fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) { | |
a2a8927a | 153 | fn check_for_self_assign_helper<'tcx>( |
136023e0 XL |
154 | tcx: TyCtxt<'tcx>, |
155 | typeck_results: &'tcx ty::TypeckResults<'tcx>, | |
156 | lhs: &'tcx hir::Expr<'tcx>, | |
157 | rhs: &'tcx hir::Expr<'tcx>, | |
158 | ) -> bool { | |
159 | match (&lhs.kind, &rhs.kind) { | |
160 | (hir::ExprKind::Path(ref qpath_l), hir::ExprKind::Path(ref qpath_r)) => { | |
161 | if let (Res::Local(id_l), Res::Local(id_r)) = ( | |
162 | typeck_results.qpath_res(qpath_l, lhs.hir_id), | |
163 | typeck_results.qpath_res(qpath_r, rhs.hir_id), | |
164 | ) { | |
165 | if id_l == id_r { | |
166 | return true; | |
167 | } | |
168 | } | |
169 | return false; | |
170 | } | |
171 | (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => { | |
172 | if ident_l == ident_r { | |
173 | return check_for_self_assign_helper(tcx, typeck_results, lhs_l, lhs_r); | |
174 | } | |
175 | return false; | |
176 | } | |
177 | _ => { | |
178 | return false; | |
179 | } | |
180 | } | |
181 | } | |
182 | ||
183 | if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind { | |
184 | if check_for_self_assign_helper(self.tcx, self.typeck_results(), lhs, rhs) | |
185 | && !assign.span.from_expansion() | |
186 | { | |
187 | let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..)); | |
188 | self.tcx.struct_span_lint_hir( | |
189 | lint::builtin::DEAD_CODE, | |
190 | assign.hir_id, | |
191 | assign.span, | |
192 | |lint| { | |
193 | lint.build(&format!( | |
194 | "useless assignment of {} of type `{}` to itself", | |
195 | if is_field_assign { "field" } else { "variable" }, | |
196 | self.typeck_results().expr_ty(lhs), | |
197 | )) | |
198 | .emit(); | |
199 | }, | |
200 | ) | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
dfeec247 XL |
205 | fn handle_field_pattern_match( |
206 | &mut self, | |
207 | lhs: &hir::Pat<'_>, | |
208 | res: Res, | |
6a06907d | 209 | pats: &[hir::PatField<'_>], |
dfeec247 | 210 | ) { |
1b1a35ee | 211 | let variant = match self.typeck_results().node_type(lhs.hir_id).kind() { |
48663c56 | 212 | ty::Adt(adt, _) => adt.variant_of_res(res), |
dfeec247 | 213 | _ => span_bug!(lhs.span, "non-ADT in struct pattern"), |
1a4d82fc | 214 | }; |
85aaf69f | 215 | for pat in pats { |
e74abb32 | 216 | if let PatKind::Wild = pat.pat.kind { |
62682a34 SL |
217 | continue; |
218 | } | |
3dfed10e | 219 | let index = self.tcx.field_index(pat.hir_id, self.typeck_results()); |
83c7162d | 220 | self.insert_def_id(variant.fields[index].did); |
1a4d82fc JJ |
221 | } |
222 | } | |
223 | ||
224 | fn mark_live_symbols(&mut self) { | |
0bf4aa26 XL |
225 | let mut scanned = FxHashSet::default(); |
226 | while let Some(id) = self.worklist.pop() { | |
227 | if !scanned.insert(id) { | |
dfeec247 | 228 | continue; |
1a4d82fc | 229 | } |
1a4d82fc | 230 | |
0731742a XL |
231 | // in the case of tuple struct constructors we want to check the item, not the generated |
232 | // tuple struct constructor function | |
94222f64 | 233 | let id = self.struct_constructors.get(&id).copied().unwrap_or(id); |
0731742a | 234 | |
94222f64 | 235 | if let Some(node) = self.tcx.hir().find(self.tcx.hir().local_def_id_to_hir_id(id)) { |
3157f602 XL |
236 | self.live_symbols.insert(id); |
237 | self.visit_node(node); | |
1a4d82fc JJ |
238 | } |
239 | } | |
240 | } | |
241 | ||
c295e0f8 XL |
242 | /// Automatically generated items marked with `rustc_trivial_field_reads` |
243 | /// will be ignored for the purposes of dead code analysis (see PR #85200 | |
244 | /// for discussion). | |
245 | fn should_ignore_item(&self, def_id: DefId) -> bool { | |
246 | if let Some(impl_of) = self.tcx.impl_of_method(def_id) { | |
247 | if !self.tcx.has_attr(impl_of, sym::automatically_derived) { | |
248 | return false; | |
249 | } | |
250 | ||
251 | if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) { | |
252 | if self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) { | |
253 | return true; | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | return false; | |
259 | } | |
260 | ||
0731742a | 261 | fn visit_node(&mut self, node: Node<'tcx>) { |
c295e0f8 XL |
262 | if let Some(item_def_id) = match node { |
263 | Node::ImplItem(hir::ImplItem { def_id, .. }) => Some(def_id.to_def_id()), | |
264 | _ => None, | |
265 | } { | |
266 | if self.should_ignore_item(item_def_id) { | |
267 | return; | |
268 | } | |
269 | } | |
270 | ||
2c00a5a8 | 271 | let had_repr_c = self.repr_has_repr_c; |
1a4d82fc | 272 | let had_inherited_pub_visibility = self.inherited_pub_visibility; |
17df50a5 XL |
273 | let had_pub_visibility = self.pub_visibility; |
274 | self.repr_has_repr_c = false; | |
1a4d82fc | 275 | self.inherited_pub_visibility = false; |
17df50a5 | 276 | self.pub_visibility = false; |
0731742a | 277 | match node { |
17df50a5 XL |
278 | Node::Item(item) => { |
279 | self.pub_visibility = item.vis.node.is_pub(); | |
532ac7d7 | 280 | |
17df50a5 XL |
281 | match item.kind { |
282 | hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => { | |
283 | let def = self.tcx.adt_def(item.def_id); | |
284 | self.repr_has_repr_c = def.repr.c(); | |
dfeec247 | 285 | |
17df50a5 XL |
286 | intravisit::walk_item(self, &item); |
287 | } | |
288 | hir::ItemKind::Enum(..) => { | |
289 | self.inherited_pub_visibility = self.pub_visibility; | |
290 | ||
291 | intravisit::walk_item(self, &item); | |
292 | } | |
293 | hir::ItemKind::ForeignMod { .. } => {} | |
294 | _ => { | |
295 | intravisit::walk_item(self, &item); | |
296 | } | |
dfeec247 | 297 | } |
17df50a5 | 298 | } |
b7449926 | 299 | Node::TraitItem(trait_item) => { |
92a42be0 | 300 | intravisit::walk_trait_item(self, trait_item); |
1a4d82fc | 301 | } |
b7449926 | 302 | Node::ImplItem(impl_item) => { |
92a42be0 | 303 | intravisit::walk_impl_item(self, impl_item); |
1a4d82fc | 304 | } |
b7449926 | 305 | Node::ForeignItem(foreign_item) => { |
7453a54e | 306 | intravisit::walk_foreign_item(self, &foreign_item); |
1a4d82fc | 307 | } |
532ac7d7 | 308 | _ => {} |
1a4d82fc | 309 | } |
17df50a5 | 310 | self.pub_visibility = had_pub_visibility; |
1a4d82fc | 311 | self.inherited_pub_visibility = had_inherited_pub_visibility; |
17df50a5 | 312 | self.repr_has_repr_c = had_repr_c; |
1a4d82fc | 313 | } |
3b2f2976 | 314 | |
6a06907d | 315 | fn mark_as_used_if_union(&mut self, adt: &ty::AdtDef, fields: &[hir::ExprField<'_>]) { |
83c7162d XL |
316 | if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did.is_local() { |
317 | for field in fields { | |
3dfed10e | 318 | let index = self.tcx.field_index(field.hir_id, self.typeck_results()); |
83c7162d | 319 | self.insert_def_id(adt.non_enum_variant().fields[index].did); |
3b2f2976 XL |
320 | } |
321 | } | |
322 | } | |
1a4d82fc JJ |
323 | } |
324 | ||
f035d41b | 325 | impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { |
ba9703b0 | 326 | type Map = intravisit::ErasedMap<'tcx>; |
dfeec247 | 327 | |
ba9703b0 | 328 | fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { |
32a655c1 SL |
329 | NestedVisitorMap::None |
330 | } | |
331 | ||
332 | fn visit_nested_body(&mut self, body: hir::BodyId) { | |
3dfed10e XL |
333 | let old_maybe_typeck_results = |
334 | self.maybe_typeck_results.replace(self.tcx.typeck_body(body)); | |
0731742a | 335 | let body = self.tcx.hir().body(body); |
32a655c1 | 336 | self.visit_body(body); |
3dfed10e | 337 | self.maybe_typeck_results = old_maybe_typeck_results; |
476ff2be | 338 | } |
1a4d82fc | 339 | |
dfeec247 XL |
340 | fn visit_variant_data( |
341 | &mut self, | |
342 | def: &'tcx hir::VariantData<'tcx>, | |
f9f354fc | 343 | _: Symbol, |
dfeec247 XL |
344 | _: &hir::Generics<'_>, |
345 | _: hir::HirId, | |
346 | _: rustc_span::Span, | |
347 | ) { | |
2c00a5a8 | 348 | let has_repr_c = self.repr_has_repr_c; |
1a4d82fc | 349 | let inherited_pub_visibility = self.inherited_pub_visibility; |
17df50a5 XL |
350 | let pub_visibility = self.pub_visibility; |
351 | let live_fields = def.fields().iter().filter(|f| { | |
352 | has_repr_c || (pub_visibility && (inherited_pub_visibility || f.vis.node.is_pub())) | |
353 | }); | |
94222f64 XL |
354 | let hir = self.tcx.hir(); |
355 | self.live_symbols.extend(live_fields.map(|f| hir.local_def_id(f.hir_id))); | |
1a4d82fc | 356 | |
92a42be0 | 357 | intravisit::walk_struct_def(self, def); |
1a4d82fc JJ |
358 | } |
359 | ||
dfeec247 | 360 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { |
e74abb32 | 361 | match expr.kind { |
8faf50e0 | 362 | hir::ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(..)) => { |
3dfed10e | 363 | let res = self.typeck_results().qpath_res(qpath, expr.hir_id); |
48663c56 | 364 | self.handle_res(res); |
476ff2be | 365 | } |
8faf50e0 | 366 | hir::ExprKind::MethodCall(..) => { |
3b2f2976 | 367 | self.lookup_and_handle_method(expr.hir_id); |
1a4d82fc | 368 | } |
8faf50e0 | 369 | hir::ExprKind::Field(ref lhs, ..) => { |
532ac7d7 | 370 | self.handle_field_access(&lhs, expr.hir_id); |
1a4d82fc | 371 | } |
ba9703b0 | 372 | hir::ExprKind::Struct(ref qpath, ref fields, _) => { |
3dfed10e | 373 | let res = self.typeck_results().qpath_res(qpath, expr.hir_id); |
ba9703b0 | 374 | self.handle_res(res); |
1b1a35ee | 375 | if let ty::Adt(ref adt, _) = self.typeck_results().expr_ty(expr).kind() { |
83c7162d | 376 | self.mark_as_used_if_union(adt, fields); |
3b2f2976 XL |
377 | } |
378 | } | |
dfeec247 | 379 | _ => (), |
1a4d82fc JJ |
380 | } |
381 | ||
92a42be0 | 382 | intravisit::walk_expr(self, expr); |
1a4d82fc JJ |
383 | } |
384 | ||
dfeec247 | 385 | fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { |
e74abb32 XL |
386 | // Inside the body, ignore constructions of variants |
387 | // necessary for the pattern to match. Those construction sites | |
388 | // can't be reached unless the variant is constructed elsewhere. | |
389 | let len = self.ignore_variant_stack.len(); | |
390 | self.ignore_variant_stack.extend(arm.pat.necessary_variants()); | |
391 | intravisit::walk_arm(self, arm); | |
392 | self.ignore_variant_stack.truncate(len); | |
62682a34 SL |
393 | } |
394 | ||
dfeec247 | 395 | fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { |
5869c6ff | 396 | self.in_pat = true; |
e74abb32 | 397 | match pat.kind { |
416331ca | 398 | PatKind::Struct(ref path, ref fields, _) => { |
3dfed10e | 399 | let res = self.typeck_results().qpath_res(path, pat.hir_id); |
416331ca | 400 | self.handle_field_pattern_match(pat, res, fields); |
1a4d82fc | 401 | } |
e1599b0c | 402 | PatKind::Path(ref qpath) => { |
3dfed10e | 403 | let res = self.typeck_results().qpath_res(qpath, pat.hir_id); |
48663c56 | 404 | self.handle_res(res); |
1a4d82fc | 405 | } |
dfeec247 | 406 | _ => (), |
1a4d82fc JJ |
407 | } |
408 | ||
92a42be0 | 409 | intravisit::walk_pat(self, pat); |
abe05a73 | 410 | self.in_pat = false; |
1a4d82fc JJ |
411 | } |
412 | ||
dfeec247 | 413 | fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { |
48663c56 | 414 | self.handle_res(path.res); |
92a42be0 | 415 | intravisit::walk_path(self, path); |
1a4d82fc | 416 | } |
532ac7d7 | 417 | |
dfeec247 | 418 | fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) { |
f035d41b | 419 | if let TyKind::OpaqueDef(item_id, _) = ty.kind { |
6a06907d | 420 | let item = self.tcx.hir().item(item_id); |
ba9703b0 | 421 | intravisit::walk_item(self, item); |
532ac7d7 XL |
422 | } |
423 | intravisit::walk_ty(self, ty); | |
424 | } | |
e1599b0c XL |
425 | |
426 | fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) { | |
94222f64 XL |
427 | // When inline const blocks are used in pattern position, paths |
428 | // referenced by it should be considered as used. | |
429 | let in_pat = mem::replace(&mut self.in_pat, false); | |
430 | ||
431 | self.live_symbols.insert(self.tcx.hir().local_def_id(c.hir_id)); | |
e1599b0c | 432 | intravisit::walk_anon_const(self, c); |
94222f64 XL |
433 | |
434 | self.in_pat = in_pat; | |
e1599b0c | 435 | } |
1a4d82fc JJ |
436 | } |
437 | ||
6a06907d XL |
438 | fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { |
439 | let attrs = tcx.hir().attrs(id); | |
3dfed10e | 440 | if tcx.sess.contains_name(attrs, sym::lang) { |
8faf50e0 XL |
441 | return true; |
442 | } | |
443 | ||
a1dfa0c6 | 444 | // Stable attribute for #[lang = "panic_impl"] |
3dfed10e | 445 | if tcx.sess.contains_name(attrs, sym::panic_handler) { |
8faf50e0 XL |
446 | return true; |
447 | } | |
448 | ||
449 | // (To be) stable attribute for #[lang = "oom"] | |
3dfed10e | 450 | if tcx.sess.contains_name(attrs, sym::alloc_error_handler) { |
1a4d82fc JJ |
451 | return true; |
452 | } | |
453 | ||
416331ca | 454 | let def_id = tcx.hir().local_def_id(id); |
0bf4aa26 XL |
455 | let cg_attrs = tcx.codegen_fn_attrs(def_id); |
456 | ||
457 | // #[used], #[no_mangle], #[export_name], etc also keeps the item alive | |
0731742a | 458 | // forcefully, e.g., for placing it in a specific section. |
dfeec247 | 459 | if cg_attrs.contains_extern_indicator() || cg_attrs.flags.contains(CodegenFnAttrFlags::USED) { |
041b39d2 XL |
460 | return true; |
461 | } | |
462 | ||
3b2f2976 | 463 | tcx.lint_level_at_node(lint::builtin::DEAD_CODE, id).0 == lint::Allow |
1a4d82fc JJ |
464 | } |
465 | ||
466 | // This visitor seeds items that | |
467 | // 1) We want to explicitly consider as live: | |
468 | // * Item annotated with #[allow(dead_code)] | |
469 | // - This is done so that if we want to suppress warnings for a | |
470 | // group of dead functions, we only have to annotate the "root". | |
471 | // For example, if both `f` and `g` are dead and `f` calls `g`, | |
472 | // then annotating `f` with `#[allow(dead_code)]` will suppress | |
473 | // warning for both `f` and `g`. | |
474 | // * Item annotated with #[lang=".."] | |
475 | // - This is because lang items are always callable from elsewhere. | |
476 | // or | |
477 | // 2) We are not sure to be live or not | |
1b1a35ee | 478 | // * Implementations of traits and trait methods |
c295e0f8 | 479 | struct LifeSeeder<'tcx> { |
94222f64 | 480 | worklist: Vec<LocalDefId>, |
dc9dc135 | 481 | tcx: TyCtxt<'tcx>, |
0731742a | 482 | // see `MarkSymbolVisitor::struct_constructors` |
94222f64 | 483 | struct_constructors: FxHashMap<LocalDefId, LocalDefId>, |
1a4d82fc JJ |
484 | } |
485 | ||
c295e0f8 | 486 | impl<'v, 'tcx> ItemLikeVisitor<'v> for LifeSeeder<'tcx> { |
dfeec247 | 487 | fn visit_item(&mut self, item: &hir::Item<'_>) { |
6a06907d | 488 | let allow_dead_code = has_allow_dead_code_or_lang_attr(self.tcx, item.hir_id()); |
1a4d82fc | 489 | if allow_dead_code { |
94222f64 | 490 | self.worklist.push(item.def_id); |
1a4d82fc | 491 | } |
e74abb32 | 492 | match item.kind { |
532ac7d7 | 493 | hir::ItemKind::Enum(ref enum_def, _) => { |
94222f64 | 494 | let hir = self.tcx.hir(); |
532ac7d7 | 495 | if allow_dead_code { |
94222f64 XL |
496 | self.worklist.extend( |
497 | enum_def.variants.iter().map(|variant| hir.local_def_id(variant.id)), | |
498 | ); | |
532ac7d7 XL |
499 | } |
500 | ||
dfeec247 | 501 | for variant in enum_def.variants { |
e1599b0c | 502 | if let Some(ctor_hir_id) = variant.data.ctor_hir_id() { |
94222f64 XL |
503 | self.struct_constructors |
504 | .insert(hir.local_def_id(ctor_hir_id), hir.local_def_id(variant.id)); | |
532ac7d7 XL |
505 | } |
506 | } | |
1a4d82fc | 507 | } |
5869c6ff | 508 | hir::ItemKind::Impl(hir::Impl { ref of_trait, items, .. }) => { |
1b1a35ee | 509 | if of_trait.is_some() { |
94222f64 | 510 | self.worklist.push(item.def_id); |
1b1a35ee | 511 | } |
dfeec247 | 512 | for impl_item_ref in items { |
c295e0f8 | 513 | let impl_item = self.tcx.hir().impl_item(impl_item_ref.id); |
dfeec247 | 514 | if of_trait.is_some() |
6a06907d | 515 | || has_allow_dead_code_or_lang_attr(self.tcx, impl_item.hir_id()) |
dfeec247 | 516 | { |
94222f64 | 517 | self.worklist.push(impl_item_ref.id.def_id); |
1a4d82fc JJ |
518 | } |
519 | } | |
520 | } | |
0731742a | 521 | hir::ItemKind::Struct(ref variant_data, _) => { |
532ac7d7 | 522 | if let Some(ctor_hir_id) = variant_data.ctor_hir_id() { |
94222f64 XL |
523 | self.struct_constructors |
524 | .insert(self.tcx.hir().local_def_id(ctor_hir_id), item.def_id); | |
532ac7d7 | 525 | } |
0731742a | 526 | } |
dfeec247 | 527 | _ => (), |
1a4d82fc | 528 | } |
1a4d82fc | 529 | } |
476ff2be | 530 | |
fc512014 XL |
531 | fn visit_trait_item(&mut self, trait_item: &hir::TraitItem<'_>) { |
532 | use hir::TraitItemKind::{Const, Fn}; | |
533 | if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_))) | |
6a06907d | 534 | && has_allow_dead_code_or_lang_attr(self.tcx, trait_item.hir_id()) |
fc512014 | 535 | { |
94222f64 | 536 | self.worklist.push(trait_item.def_id); |
fc512014 | 537 | } |
32a655c1 SL |
538 | } |
539 | ||
dfeec247 | 540 | fn visit_impl_item(&mut self, _item: &hir::ImplItem<'_>) { |
476ff2be SL |
541 | // ignore: we are handling this in `visit_item` above |
542 | } | |
fc512014 XL |
543 | |
544 | fn visit_foreign_item(&mut self, foreign_item: &hir::ForeignItem<'_>) { | |
545 | use hir::ForeignItemKind::{Fn, Static}; | |
546 | if matches!(foreign_item.kind, Static(..) | Fn(..)) | |
6a06907d | 547 | && has_allow_dead_code_or_lang_attr(self.tcx, foreign_item.hir_id()) |
fc512014 | 548 | { |
94222f64 | 549 | self.worklist.push(foreign_item.def_id); |
fc512014 XL |
550 | } |
551 | } | |
1a4d82fc JJ |
552 | } |
553 | ||
dc9dc135 XL |
554 | fn create_and_seed_worklist<'tcx>( |
555 | tcx: TyCtxt<'tcx>, | |
0731742a | 556 | access_levels: &privacy::AccessLevels, |
94222f64 | 557 | ) -> (Vec<LocalDefId>, FxHashMap<LocalDefId, LocalDefId>) { |
dfeec247 XL |
558 | let worklist = access_levels |
559 | .map | |
560 | .iter() | |
561 | .filter_map( | |
29967ef6 XL |
562 | |(&id, &level)| { |
563 | if level >= privacy::AccessLevel::Reachable { Some(id) } else { None } | |
dfeec247 XL |
564 | }, |
565 | ) | |
94222f64 XL |
566 | // Seed entry point |
567 | .chain(tcx.entry_fn(()).and_then(|(def_id, _)| def_id.as_local())) | |
dfeec247 | 568 | .collect::<Vec<_>>(); |
1a4d82fc | 569 | |
d9579d0f | 570 | // Seed implemented trait items |
c295e0f8 XL |
571 | let mut life_seeder = LifeSeeder { worklist, tcx, struct_constructors: Default::default() }; |
572 | tcx.hir().visit_all_item_likes(&mut life_seeder); | |
1a4d82fc | 573 | |
0731742a | 574 | (life_seeder.worklist, life_seeder.struct_constructors) |
1a4d82fc JJ |
575 | } |
576 | ||
dc9dc135 XL |
577 | fn find_live<'tcx>( |
578 | tcx: TyCtxt<'tcx>, | |
579 | access_levels: &privacy::AccessLevels, | |
94222f64 | 580 | ) -> FxHashSet<LocalDefId> { |
c295e0f8 | 581 | let (worklist, struct_constructors) = create_and_seed_worklist(tcx, access_levels); |
32a655c1 | 582 | let mut symbol_visitor = MarkSymbolVisitor { |
041b39d2 XL |
583 | worklist, |
584 | tcx, | |
3dfed10e | 585 | maybe_typeck_results: None, |
0bf4aa26 | 586 | live_symbols: Default::default(), |
2c00a5a8 | 587 | repr_has_repr_c: false, |
abe05a73 | 588 | in_pat: false, |
32a655c1 | 589 | inherited_pub_visibility: false, |
17df50a5 | 590 | pub_visibility: false, |
32a655c1 | 591 | ignore_variant_stack: vec![], |
0731742a | 592 | struct_constructors, |
32a655c1 | 593 | }; |
1a4d82fc JJ |
594 | symbol_visitor.mark_live_symbols(); |
595 | symbol_visitor.live_symbols | |
596 | } | |
597 | ||
dc9dc135 XL |
598 | struct DeadVisitor<'tcx> { |
599 | tcx: TyCtxt<'tcx>, | |
94222f64 | 600 | live_symbols: FxHashSet<LocalDefId>, |
1a4d82fc JJ |
601 | } |
602 | ||
a2a8927a | 603 | impl<'tcx> DeadVisitor<'tcx> { |
dfeec247 | 604 | fn should_warn_about_item(&mut self, item: &hir::Item<'_>) -> bool { |
5869c6ff XL |
605 | let should_warn = matches!( |
606 | item.kind, | |
8faf50e0 | 607 | hir::ItemKind::Static(..) |
5869c6ff XL |
608 | | hir::ItemKind::Const(..) |
609 | | hir::ItemKind::Fn(..) | |
610 | | hir::ItemKind::TyAlias(..) | |
611 | | hir::ItemKind::Enum(..) | |
612 | | hir::ItemKind::Struct(..) | |
613 | | hir::ItemKind::Union(..) | |
614 | ); | |
94222f64 | 615 | should_warn && !self.symbol_is_live(item.def_id) |
1a4d82fc JJ |
616 | } |
617 | ||
6a06907d | 618 | fn should_warn_about_field(&mut self, field: &hir::FieldDef<'_>) -> bool { |
94222f64 XL |
619 | let def_id = self.tcx.hir().local_def_id(field.hir_id); |
620 | let field_type = self.tcx.type_of(def_id); | |
54a0048b | 621 | !field.is_positional() |
94222f64 | 622 | && !self.symbol_is_live(def_id) |
8faf50e0 | 623 | && !field_type.is_phantom_data() |
6a06907d | 624 | && !has_allow_dead_code_or_lang_attr(self.tcx, field.hir_id) |
1a4d82fc JJ |
625 | } |
626 | ||
dfeec247 | 627 | fn should_warn_about_variant(&mut self, variant: &hir::Variant<'_>) -> bool { |
94222f64 XL |
628 | let def_id = self.tcx.hir().local_def_id(variant.id); |
629 | !self.symbol_is_live(def_id) && !has_allow_dead_code_or_lang_attr(self.tcx, variant.id) | |
1a4d82fc JJ |
630 | } |
631 | ||
dfeec247 | 632 | fn should_warn_about_foreign_item(&mut self, fi: &hir::ForeignItem<'_>) -> bool { |
94222f64 | 633 | !self.symbol_is_live(fi.def_id) && !has_allow_dead_code_or_lang_attr(self.tcx, fi.hir_id()) |
32a655c1 SL |
634 | } |
635 | ||
532ac7d7 | 636 | // id := HIR id of an item's definition. |
94222f64 XL |
637 | fn symbol_is_live(&mut self, def_id: LocalDefId) -> bool { |
638 | if self.live_symbols.contains(&def_id) { | |
1a4d82fc JJ |
639 | return true; |
640 | } | |
d9579d0f | 641 | // If it's a type whose items are live, then it's live, too. |
1a4d82fc JJ |
642 | // This is done to handle the case where, for example, the static |
643 | // method of a private type is used, but the type itself is never | |
644 | // called directly. | |
7cac9316 XL |
645 | let inherent_impls = self.tcx.inherent_impls(def_id); |
646 | for &impl_did in inherent_impls.iter() { | |
29967ef6 | 647 | for item_did in self.tcx.associated_item_def_ids(impl_did) { |
94222f64 XL |
648 | if let Some(def_id) = item_did.as_local() { |
649 | if self.live_symbols.contains(&def_id) { | |
7cac9316 | 650 | return true; |
1a4d82fc JJ |
651 | } |
652 | } | |
653 | } | |
654 | } | |
655 | false | |
656 | } | |
657 | ||
dfeec247 XL |
658 | fn warn_dead_code( |
659 | &mut self, | |
660 | id: hir::HirId, | |
661 | span: rustc_span::Span, | |
f9f354fc | 662 | name: Symbol, |
dfeec247 XL |
663 | participle: &str, |
664 | ) { | |
74b04a01 XL |
665 | if !name.as_str().starts_with('_') { |
666 | self.tcx.struct_span_lint_hir(lint::builtin::DEAD_CODE, id, span, |lint| { | |
f9f354fc XL |
667 | let def_id = self.tcx.hir().local_def_id(id); |
668 | let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id()); | |
669 | lint.build(&format!("{} is never {}: `{}`", descr, participle, name)).emit() | |
74b04a01 | 670 | }); |
1a4d82fc JJ |
671 | } |
672 | } | |
673 | } | |
674 | ||
a2a8927a | 675 | impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> { |
dfeec247 XL |
676 | type Map = Map<'tcx>; |
677 | ||
92a42be0 SL |
678 | /// Walk nested items in place so that we don't report dead-code |
679 | /// on inner functions when the outer function is already getting | |
680 | /// an error. We could do this also by checking the parents, but | |
681 | /// this is how the code is setup and it seems harmless enough. | |
ba9703b0 XL |
682 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
683 | NestedVisitorMap::All(self.tcx.hir()) | |
92a42be0 SL |
684 | } |
685 | ||
dfeec247 | 686 | fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { |
1a4d82fc | 687 | if self.should_warn_about_item(item) { |
60c5eb7d | 688 | // For most items, we want to highlight its identifier |
e74abb32 | 689 | let span = match item.kind { |
dfeec247 XL |
690 | hir::ItemKind::Fn(..) |
691 | | hir::ItemKind::Mod(..) | |
692 | | hir::ItemKind::Enum(..) | |
693 | | hir::ItemKind::Struct(..) | |
694 | | hir::ItemKind::Union(..) | |
695 | | hir::ItemKind::Trait(..) | |
696 | | hir::ItemKind::Impl { .. } => { | |
60c5eb7d XL |
697 | // FIXME(66095): Because item.span is annotated with things |
698 | // like expansion data, and ident.span isn't, we use the | |
699 | // def_span method if it's part of a macro invocation | |
f9f354fc | 700 | // (and thus has a source_callee set). |
60c5eb7d XL |
701 | // We should probably annotate ident.span with the macro |
702 | // context, but that's a larger change. | |
703 | if item.span.source_callee().is_some() { | |
ba9703b0 | 704 | self.tcx.sess.source_map().guess_head_span(item.span) |
60c5eb7d XL |
705 | } else { |
706 | item.ident.span | |
707 | } | |
dfeec247 | 708 | } |
ea8adc8c XL |
709 | _ => item.span, |
710 | }; | |
e74abb32 | 711 | let participle = match item.kind { |
b7449926 | 712 | hir::ItemKind::Struct(..) => "constructed", // Issue #52325 |
dfeec247 | 713 | _ => "used", |
b7449926 | 714 | }; |
6a06907d | 715 | self.warn_dead_code(item.hir_id(), span, item.ident.name, participle); |
1a4d82fc | 716 | } else { |
92a42be0 SL |
717 | // Only continue if we didn't warn |
718 | intravisit::walk_item(self, item); | |
719 | } | |
720 | } | |
721 | ||
dfeec247 XL |
722 | fn visit_variant( |
723 | &mut self, | |
724 | variant: &'tcx hir::Variant<'tcx>, | |
725 | g: &'tcx hir::Generics<'tcx>, | |
726 | id: hir::HirId, | |
727 | ) { | |
e1599b0c | 728 | if self.should_warn_about_variant(&variant) { |
f9f354fc | 729 | self.warn_dead_code(variant.id, variant.span, variant.ident.name, "constructed"); |
92a42be0 SL |
730 | } else { |
731 | intravisit::walk_variant(self, variant, g, id); | |
1a4d82fc | 732 | } |
1a4d82fc JJ |
733 | } |
734 | ||
dfeec247 | 735 | fn visit_foreign_item(&mut self, fi: &'tcx hir::ForeignItem<'tcx>) { |
32a655c1 | 736 | if self.should_warn_about_foreign_item(fi) { |
6a06907d | 737 | self.warn_dead_code(fi.hir_id(), fi.span, fi.ident.name, "used"); |
1a4d82fc | 738 | } |
92a42be0 | 739 | intravisit::walk_foreign_item(self, fi); |
1a4d82fc JJ |
740 | } |
741 | ||
6a06907d | 742 | fn visit_field_def(&mut self, field: &'tcx hir::FieldDef<'tcx>) { |
54a0048b | 743 | if self.should_warn_about_field(&field) { |
f9f354fc | 744 | self.warn_dead_code(field.hir_id, field.span, field.ident.name, "read"); |
1a4d82fc | 745 | } |
6a06907d | 746 | intravisit::walk_field_def(self, field); |
1a4d82fc JJ |
747 | } |
748 | ||
dfeec247 | 749 | fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { |
e74abb32 | 750 | match impl_item.kind { |
32a655c1 | 751 | hir::ImplItemKind::Const(_, body_id) => { |
94222f64 | 752 | if !self.symbol_is_live(impl_item.def_id) { |
dfeec247 | 753 | self.warn_dead_code( |
6a06907d | 754 | impl_item.hir_id(), |
dfeec247 XL |
755 | impl_item.span, |
756 | impl_item.ident.name, | |
dfeec247 XL |
757 | "used", |
758 | ); | |
d9579d0f | 759 | } |
32a655c1 | 760 | self.visit_nested_body(body_id) |
d9579d0f | 761 | } |
ba9703b0 | 762 | hir::ImplItemKind::Fn(_, body_id) => { |
94222f64 | 763 | if !self.symbol_is_live(impl_item.def_id) { |
f9f354fc XL |
764 | // FIXME(66095): Because impl_item.span is annotated with things |
765 | // like expansion data, and ident.span isn't, we use the | |
766 | // def_span method if it's part of a macro invocation | |
767 | // (and thus has a source_callee set). | |
768 | // We should probably annotate ident.span with the macro | |
769 | // context, but that's a larger change. | |
770 | let span = if impl_item.span.source_callee().is_some() { | |
771 | self.tcx.sess.source_map().guess_head_span(impl_item.span) | |
772 | } else { | |
773 | impl_item.ident.span | |
774 | }; | |
6a06907d | 775 | self.warn_dead_code(impl_item.hir_id(), span, impl_item.ident.name, "used"); |
d9579d0f | 776 | } |
32a655c1 | 777 | self.visit_nested_body(body_id) |
d9579d0f | 778 | } |
f035d41b | 779 | hir::ImplItemKind::TyAlias(..) => {} |
d9579d0f AL |
780 | } |
781 | } | |
782 | ||
783 | // Overwrite so that we don't warn the trait item itself. | |
dfeec247 | 784 | fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { |
e74abb32 | 785 | match trait_item.kind { |
dfeec247 | 786 | hir::TraitItemKind::Const(_, Some(body_id)) |
ba9703b0 | 787 | | hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)) => { |
32a655c1 | 788 | self.visit_nested_body(body_id) |
1a4d82fc | 789 | } |
dfeec247 | 790 | hir::TraitItemKind::Const(_, None) |
ba9703b0 | 791 | | hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_)) |
dfeec247 | 792 | | hir::TraitItemKind::Type(..) => {} |
1a4d82fc JJ |
793 | } |
794 | } | |
795 | } | |
796 | ||
416331ca | 797 | pub fn check_crate(tcx: TyCtxt<'_>) { |
17df50a5 | 798 | let access_levels = &tcx.privacy_access_levels(()); |
c295e0f8 | 799 | let live_symbols = find_live(tcx, access_levels); |
dfeec247 | 800 | let mut visitor = DeadVisitor { tcx, live_symbols }; |
c295e0f8 | 801 | tcx.hir().walk_toplevel_module(&mut visitor); |
1a4d82fc | 802 | } |