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