]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_passes/src/dead.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / compiler / rustc_passes / src / dead.rs
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
5 use itertools::Itertools;
6 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7 use rustc_errors::{pluralize, MultiSpan};
8 use rustc_hir as hir;
9 use rustc_hir::def::{CtorOf, DefKind, Res};
10 use rustc_hir::def_id::{DefId, LocalDefId};
11 use rustc_hir::intravisit::{self, Visitor};
12 use rustc_hir::{Node, PatKind, TyKind};
13 use rustc_middle::hir::nested_filter;
14 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
15 use rustc_middle::middle::privacy;
16 use rustc_middle::ty::query::Providers;
17 use rustc_middle::ty::{self, DefIdTree, TyCtxt};
18 use rustc_session::lint;
19 use rustc_span::symbol::{sym, Symbol};
20 use rustc_span::Span;
21 use std::mem;
22
23 // Any local node that may call something in its body block should be
24 // explored. For example, if it's a live Node::Item that is a
25 // function, then we should explore its block to check for codes that
26 // may need to be marked as live.
27 fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
28 matches!(
29 tcx.hir().find_by_def_id(def_id),
30 Some(
31 Node::Item(..)
32 | Node::ImplItem(..)
33 | Node::ForeignItem(..)
34 | Node::TraitItem(..)
35 | Node::Variant(..)
36 | Node::AnonConst(..)
37 )
38 )
39 }
40
41 struct MarkSymbolVisitor<'tcx> {
42 worklist: Vec<LocalDefId>,
43 tcx: TyCtxt<'tcx>,
44 maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
45 live_symbols: FxHashSet<LocalDefId>,
46 repr_has_repr_c: bool,
47 in_pat: bool,
48 ignore_variant_stack: Vec<DefId>,
49 // maps from tuple struct constructors to tuple struct items
50 struct_constructors: FxHashMap<LocalDefId, LocalDefId>,
51 // maps from ADTs to ignored derived traits (e.g. Debug and Clone)
52 // and the span of their respective impl (i.e., part of the derive
53 // macro)
54 ignored_derived_traits: FxHashMap<LocalDefId, Vec<(DefId, DefId)>>,
55 }
56
57 impl<'tcx> MarkSymbolVisitor<'tcx> {
58 /// Gets the type-checking results for the current body.
59 /// As this will ICE if called outside bodies, only call when working with
60 /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
61 #[track_caller]
62 fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
63 self.maybe_typeck_results
64 .expect("`MarkSymbolVisitor::typeck_results` called outside of body")
65 }
66
67 fn check_def_id(&mut self, def_id: DefId) {
68 if let Some(def_id) = def_id.as_local() {
69 if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) {
70 self.worklist.push(def_id);
71 }
72 self.live_symbols.insert(def_id);
73 }
74 }
75
76 fn insert_def_id(&mut self, def_id: DefId) {
77 if let Some(def_id) = def_id.as_local() {
78 debug_assert!(!should_explore(self.tcx, def_id));
79 self.live_symbols.insert(def_id);
80 }
81 }
82
83 fn handle_res(&mut self, res: Res) {
84 match res {
85 Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, _) => {
86 self.check_def_id(res.def_id());
87 }
88 _ if self.in_pat => {}
89 Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {}
90 Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => {
91 let variant_id = self.tcx.parent(ctor_def_id);
92 let enum_id = self.tcx.parent(variant_id);
93 self.check_def_id(enum_id);
94 if !self.ignore_variant_stack.contains(&ctor_def_id) {
95 self.check_def_id(variant_id);
96 }
97 }
98 Res::Def(DefKind::Variant, variant_id) => {
99 let enum_id = self.tcx.parent(variant_id);
100 self.check_def_id(enum_id);
101 if !self.ignore_variant_stack.contains(&variant_id) {
102 self.check_def_id(variant_id);
103 }
104 }
105 Res::SelfTy { trait_: t, alias_to: i } => {
106 if let Some(t) = t {
107 self.check_def_id(t);
108 }
109 if let Some((i, _)) = i {
110 self.check_def_id(i);
111 }
112 }
113 Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {}
114 _ => {
115 self.check_def_id(res.def_id());
116 }
117 }
118 }
119
120 fn lookup_and_handle_method(&mut self, id: hir::HirId) {
121 if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) {
122 self.check_def_id(def_id);
123 } else {
124 bug!("no type-dependent def for method");
125 }
126 }
127
128 fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) {
129 match self.typeck_results().expr_ty_adjusted(lhs).kind() {
130 ty::Adt(def, _) => {
131 let index = self.tcx.field_index(hir_id, self.typeck_results());
132 self.insert_def_id(def.non_enum_variant().fields[index].did);
133 }
134 ty::Tuple(..) => {}
135 _ => span_bug!(lhs.span, "named field access on non-ADT"),
136 }
137 }
138
139 #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
140 fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) {
141 if self
142 .typeck_results()
143 .expr_adjustments(expr)
144 .iter()
145 .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)))
146 {
147 self.visit_expr(expr);
148 } else if let hir::ExprKind::Field(base, ..) = expr.kind {
149 // Ignore write to field
150 self.handle_assign(base);
151 } else {
152 self.visit_expr(expr);
153 }
154 }
155
156 #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
157 fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) {
158 fn check_for_self_assign_helper<'tcx>(
159 typeck_results: &'tcx ty::TypeckResults<'tcx>,
160 lhs: &'tcx hir::Expr<'tcx>,
161 rhs: &'tcx hir::Expr<'tcx>,
162 ) -> bool {
163 match (&lhs.kind, &rhs.kind) {
164 (hir::ExprKind::Path(ref qpath_l), hir::ExprKind::Path(ref qpath_r)) => {
165 if let (Res::Local(id_l), Res::Local(id_r)) = (
166 typeck_results.qpath_res(qpath_l, lhs.hir_id),
167 typeck_results.qpath_res(qpath_r, rhs.hir_id),
168 ) {
169 if id_l == id_r {
170 return true;
171 }
172 }
173 return false;
174 }
175 (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => {
176 if ident_l == ident_r {
177 return check_for_self_assign_helper(typeck_results, lhs_l, lhs_r);
178 }
179 return false;
180 }
181 _ => {
182 return false;
183 }
184 }
185 }
186
187 if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
188 && check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
189 && !assign.span.from_expansion()
190 {
191 let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
192 self.tcx.struct_span_lint_hir(
193 lint::builtin::DEAD_CODE,
194 assign.hir_id,
195 assign.span,
196 |lint| {
197 lint.build(&format!(
198 "useless assignment of {} of type `{}` to itself",
199 if is_field_assign { "field" } else { "variable" },
200 self.typeck_results().expr_ty(lhs),
201 ))
202 .emit();
203 },
204 )
205 }
206 }
207
208 fn handle_field_pattern_match(
209 &mut self,
210 lhs: &hir::Pat<'_>,
211 res: Res,
212 pats: &[hir::PatField<'_>],
213 ) {
214 let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
215 ty::Adt(adt, _) => adt.variant_of_res(res),
216 _ => span_bug!(lhs.span, "non-ADT in struct pattern"),
217 };
218 for pat in pats {
219 if let PatKind::Wild = pat.pat.kind {
220 continue;
221 }
222 let index = self.tcx.field_index(pat.hir_id, self.typeck_results());
223 self.insert_def_id(variant.fields[index].did);
224 }
225 }
226
227 fn mark_live_symbols(&mut self) {
228 let mut scanned = FxHashSet::default();
229 while let Some(id) = self.worklist.pop() {
230 if !scanned.insert(id) {
231 continue;
232 }
233
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).copied().unwrap_or(id);
237
238 if let Some(node) = self.tcx.hir().find_by_def_id(id) {
239 self.live_symbols.insert(id);
240 self.visit_node(node);
241 }
242 }
243 }
244
245 /// Automatically generated items marked with `rustc_trivial_field_reads`
246 /// will be ignored for the purposes of dead code analysis (see PR #85200
247 /// for discussion).
248 fn should_ignore_item(&mut self, def_id: DefId) -> bool {
249 if let Some(impl_of) = self.tcx.impl_of_method(def_id) {
250 if !self.tcx.has_attr(impl_of, sym::automatically_derived) {
251 return false;
252 }
253
254 if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
255 && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
256 {
257 let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
258 if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
259 && let Some(adt_def_id) = adt_def.did().as_local()
260 {
261 self.ignored_derived_traits
262 .entry(adt_def_id)
263 .or_default()
264 .push((trait_of, impl_of));
265 }
266 return true;
267 }
268 }
269
270 return false;
271 }
272
273 fn visit_node(&mut self, node: Node<'tcx>) {
274 if let Node::ImplItem(hir::ImplItem { def_id, .. }) = node
275 && self.should_ignore_item(def_id.to_def_id())
276 {
277 return;
278 }
279
280 let had_repr_c = self.repr_has_repr_c;
281 self.repr_has_repr_c = false;
282 match node {
283 Node::Item(item) => match item.kind {
284 hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
285 let def = self.tcx.adt_def(item.def_id);
286 self.repr_has_repr_c = def.repr().c();
287
288 intravisit::walk_item(self, &item);
289 }
290 hir::ItemKind::Enum(..) => {
291 intravisit::walk_item(self, &item);
292 }
293 hir::ItemKind::ForeignMod { .. } => {}
294 _ => {
295 intravisit::walk_item(self, &item);
296 }
297 },
298 Node::TraitItem(trait_item) => {
299 intravisit::walk_trait_item(self, trait_item);
300 }
301 Node::ImplItem(impl_item) => {
302 intravisit::walk_impl_item(self, impl_item);
303 }
304 Node::ForeignItem(foreign_item) => {
305 intravisit::walk_foreign_item(self, &foreign_item);
306 }
307 _ => {}
308 }
309 self.repr_has_repr_c = had_repr_c;
310 }
311
312 fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
313 if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
314 for field in fields {
315 let index = self.tcx.field_index(field.hir_id, self.typeck_results());
316 self.insert_def_id(adt.non_enum_variant().fields[index].did);
317 }
318 }
319 }
320 }
321
322 impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
323 fn visit_nested_body(&mut self, body: hir::BodyId) {
324 let old_maybe_typeck_results =
325 self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
326 let body = self.tcx.hir().body(body);
327 self.visit_body(body);
328 self.maybe_typeck_results = old_maybe_typeck_results;
329 }
330
331 fn visit_variant_data(
332 &mut self,
333 def: &'tcx hir::VariantData<'tcx>,
334 _: Symbol,
335 _: &hir::Generics<'_>,
336 _: hir::HirId,
337 _: rustc_span::Span,
338 ) {
339 let tcx = self.tcx;
340 let has_repr_c = self.repr_has_repr_c;
341 let live_fields = def.fields().iter().filter_map(|f| {
342 let def_id = tcx.hir().local_def_id(f.hir_id);
343 if has_repr_c {
344 return Some(def_id);
345 }
346 if !tcx.visibility(f.hir_id.owner).is_public() {
347 return None;
348 }
349 if tcx.visibility(def_id).is_public() { Some(def_id) } else { None }
350 });
351 self.live_symbols.extend(live_fields);
352
353 intravisit::walk_struct_def(self, def);
354 }
355
356 fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
357 match expr.kind {
358 hir::ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(..)) => {
359 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
360 self.handle_res(res);
361 }
362 hir::ExprKind::MethodCall(..) => {
363 self.lookup_and_handle_method(expr.hir_id);
364 }
365 hir::ExprKind::Field(ref lhs, ..) => {
366 self.handle_field_access(&lhs, expr.hir_id);
367 }
368 hir::ExprKind::Struct(ref qpath, ref fields, _) => {
369 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
370 self.handle_res(res);
371 if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
372 self.mark_as_used_if_union(*adt, fields);
373 }
374 }
375 _ => (),
376 }
377
378 intravisit::walk_expr(self, expr);
379 }
380
381 fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
382 // Inside the body, ignore constructions of variants
383 // necessary for the pattern to match. Those construction sites
384 // can't be reached unless the variant is constructed elsewhere.
385 let len = self.ignore_variant_stack.len();
386 self.ignore_variant_stack.extend(arm.pat.necessary_variants());
387 intravisit::walk_arm(self, arm);
388 self.ignore_variant_stack.truncate(len);
389 }
390
391 fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
392 self.in_pat = true;
393 match pat.kind {
394 PatKind::Struct(ref path, ref fields, _) => {
395 let res = self.typeck_results().qpath_res(path, pat.hir_id);
396 self.handle_field_pattern_match(pat, res, fields);
397 }
398 PatKind::Path(ref qpath) => {
399 let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
400 self.handle_res(res);
401 }
402 _ => (),
403 }
404
405 intravisit::walk_pat(self, pat);
406 self.in_pat = false;
407 }
408
409 fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
410 self.handle_res(path.res);
411 intravisit::walk_path(self, path);
412 }
413
414 fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) {
415 if let TyKind::OpaqueDef(item_id, _) = ty.kind {
416 let item = self.tcx.hir().item(item_id);
417 intravisit::walk_item(self, item);
418 }
419 intravisit::walk_ty(self, ty);
420 }
421
422 fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
423 // When inline const blocks are used in pattern position, paths
424 // referenced by it should be considered as used.
425 let in_pat = mem::replace(&mut self.in_pat, false);
426
427 self.live_symbols.insert(self.tcx.hir().local_def_id(c.hir_id));
428 intravisit::walk_anon_const(self, c);
429
430 self.in_pat = in_pat;
431 }
432 }
433
434 fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
435 let attrs = tcx.hir().attrs(id);
436 if tcx.sess.contains_name(attrs, sym::lang) {
437 return true;
438 }
439
440 // Stable attribute for #[lang = "panic_impl"]
441 if tcx.sess.contains_name(attrs, sym::panic_handler) {
442 return true;
443 }
444
445 // (To be) stable attribute for #[lang = "oom"]
446 if tcx.sess.contains_name(attrs, sym::alloc_error_handler) {
447 return true;
448 }
449
450 let def_id = tcx.hir().local_def_id(id);
451 if tcx.def_kind(def_id).has_codegen_attrs() {
452 let cg_attrs = tcx.codegen_fn_attrs(def_id);
453
454 // #[used], #[no_mangle], #[export_name], etc also keeps the item alive
455 // forcefully, e.g., for placing it in a specific section.
456 if cg_attrs.contains_extern_indicator()
457 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED)
458 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
459 {
460 return true;
461 }
462 }
463
464 tcx.lint_level_at_node(lint::builtin::DEAD_CODE, id).0 == lint::Allow
465 }
466
467 // These check_* functions seeds items that
468 // 1) We want to explicitly consider as live:
469 // * Item annotated with #[allow(dead_code)]
470 // - This is done so that if we want to suppress warnings for a
471 // group of dead functions, we only have to annotate the "root".
472 // For example, if both `f` and `g` are dead and `f` calls `g`,
473 // then annotating `f` with `#[allow(dead_code)]` will suppress
474 // warning for both `f` and `g`.
475 // * Item annotated with #[lang=".."]
476 // - This is because lang items are always callable from elsewhere.
477 // or
478 // 2) We are not sure to be live or not
479 // * Implementations of traits and trait methods
480 fn check_item<'tcx>(
481 tcx: TyCtxt<'tcx>,
482 worklist: &mut Vec<LocalDefId>,
483 struct_constructors: &mut FxHashMap<LocalDefId, LocalDefId>,
484 id: hir::ItemId,
485 ) {
486 let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.hir_id());
487 if allow_dead_code {
488 worklist.push(id.def_id);
489 }
490
491 match tcx.def_kind(id.def_id) {
492 DefKind::Enum => {
493 let item = tcx.hir().item(id);
494 if let hir::ItemKind::Enum(ref enum_def, _) = item.kind {
495 let hir = tcx.hir();
496 if allow_dead_code {
497 worklist.extend(
498 enum_def.variants.iter().map(|variant| hir.local_def_id(variant.id)),
499 );
500 }
501
502 for variant in enum_def.variants {
503 if let Some(ctor_hir_id) = variant.data.ctor_hir_id() {
504 struct_constructors
505 .insert(hir.local_def_id(ctor_hir_id), hir.local_def_id(variant.id));
506 }
507 }
508 }
509 }
510 DefKind::Impl => {
511 let of_trait = tcx.impl_trait_ref(id.def_id);
512
513 if of_trait.is_some() {
514 worklist.push(id.def_id);
515 }
516
517 // get DefIds from another query
518 let local_def_ids = tcx
519 .associated_item_def_ids(id.def_id)
520 .iter()
521 .filter_map(|def_id| def_id.as_local());
522
523 // And we access the Map here to get HirId from LocalDefId
524 for id in local_def_ids {
525 if of_trait.is_some()
526 || has_allow_dead_code_or_lang_attr(tcx, tcx.hir().local_def_id_to_hir_id(id))
527 {
528 worklist.push(id);
529 }
530 }
531 }
532 DefKind::Struct => {
533 let item = tcx.hir().item(id);
534 if let hir::ItemKind::Struct(ref variant_data, _) = item.kind
535 && let Some(ctor_hir_id) = variant_data.ctor_hir_id()
536 {
537 struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
538 }
539 }
540 DefKind::GlobalAsm => {
541 // global_asm! is always live.
542 worklist.push(id.def_id);
543 }
544 _ => {}
545 }
546 }
547
548 fn check_trait_item<'tcx>(tcx: TyCtxt<'tcx>, worklist: &mut Vec<LocalDefId>, id: hir::TraitItemId) {
549 use hir::TraitItemKind::{Const, Fn};
550 if matches!(tcx.def_kind(id.def_id), DefKind::AssocConst | DefKind::AssocFn) {
551 let trait_item = tcx.hir().trait_item(id);
552 if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_)))
553 && has_allow_dead_code_or_lang_attr(tcx, trait_item.hir_id())
554 {
555 worklist.push(trait_item.def_id);
556 }
557 }
558 }
559
560 fn check_foreign_item<'tcx>(
561 tcx: TyCtxt<'tcx>,
562 worklist: &mut Vec<LocalDefId>,
563 id: hir::ForeignItemId,
564 ) {
565 if matches!(tcx.def_kind(id.def_id), DefKind::Static(_) | DefKind::Fn)
566 && has_allow_dead_code_or_lang_attr(tcx, id.hir_id())
567 {
568 worklist.push(id.def_id);
569 }
570 }
571
572 fn create_and_seed_worklist<'tcx>(
573 tcx: TyCtxt<'tcx>,
574 ) -> (Vec<LocalDefId>, FxHashMap<LocalDefId, LocalDefId>) {
575 let access_levels = &tcx.privacy_access_levels(());
576 // see `MarkSymbolVisitor::struct_constructors`
577 let mut struct_constructors = Default::default();
578 let mut worklist = access_levels
579 .map
580 .iter()
581 .filter_map(
582 |(&id, &level)| {
583 if level >= privacy::AccessLevel::Reachable { Some(id) } else { None }
584 },
585 )
586 // Seed entry point
587 .chain(tcx.entry_fn(()).and_then(|(def_id, _)| def_id.as_local()))
588 .collect::<Vec<_>>();
589
590 let crate_items = tcx.hir_crate_items(());
591 for id in crate_items.items() {
592 check_item(tcx, &mut worklist, &mut struct_constructors, id);
593 }
594
595 for id in crate_items.trait_items() {
596 check_trait_item(tcx, &mut worklist, id);
597 }
598
599 for id in crate_items.foreign_items() {
600 check_foreign_item(tcx, &mut worklist, id);
601 }
602
603 (worklist, struct_constructors)
604 }
605
606 fn live_symbols_and_ignored_derived_traits<'tcx>(
607 tcx: TyCtxt<'tcx>,
608 (): (),
609 ) -> (FxHashSet<LocalDefId>, FxHashMap<LocalDefId, Vec<(DefId, DefId)>>) {
610 let (worklist, struct_constructors) = create_and_seed_worklist(tcx);
611 let mut symbol_visitor = MarkSymbolVisitor {
612 worklist,
613 tcx,
614 maybe_typeck_results: None,
615 live_symbols: Default::default(),
616 repr_has_repr_c: false,
617 in_pat: false,
618 ignore_variant_stack: vec![],
619 struct_constructors,
620 ignored_derived_traits: FxHashMap::default(),
621 };
622 symbol_visitor.mark_live_symbols();
623 (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
624 }
625
626 struct DeadVariant {
627 hir_id: hir::HirId,
628 span: Span,
629 name: Symbol,
630 level: lint::Level,
631 }
632
633 struct DeadVisitor<'tcx> {
634 tcx: TyCtxt<'tcx>,
635 live_symbols: &'tcx FxHashSet<LocalDefId>,
636 ignored_derived_traits: &'tcx FxHashMap<LocalDefId, Vec<(DefId, DefId)>>,
637 }
638
639 impl<'tcx> DeadVisitor<'tcx> {
640 fn should_warn_about_item(&mut self, item: &hir::Item<'_>) -> bool {
641 let should_warn = matches!(
642 item.kind,
643 hir::ItemKind::Static(..)
644 | hir::ItemKind::Const(..)
645 | hir::ItemKind::Fn(..)
646 | hir::ItemKind::TyAlias(..)
647 | hir::ItemKind::Enum(..)
648 | hir::ItemKind::Struct(..)
649 | hir::ItemKind::Union(..)
650 );
651 should_warn && !self.symbol_is_live(item.def_id)
652 }
653
654 fn should_warn_about_field(&mut self, field: &hir::FieldDef<'_>) -> bool {
655 let def_id = self.tcx.hir().local_def_id(field.hir_id);
656 let field_type = self.tcx.type_of(def_id);
657 !field.is_positional()
658 && !self.symbol_is_live(def_id)
659 && !field_type.is_phantom_data()
660 && !has_allow_dead_code_or_lang_attr(self.tcx, field.hir_id)
661 }
662
663 fn should_warn_about_variant(&mut self, variant: &hir::Variant<'_>) -> bool {
664 let def_id = self.tcx.hir().local_def_id(variant.id);
665 !self.symbol_is_live(def_id) && !has_allow_dead_code_or_lang_attr(self.tcx, variant.id)
666 }
667
668 fn should_warn_about_foreign_item(&mut self, fi: &hir::ForeignItem<'_>) -> bool {
669 !self.symbol_is_live(fi.def_id) && !has_allow_dead_code_or_lang_attr(self.tcx, fi.hir_id())
670 }
671
672 // id := HIR id of an item's definition.
673 fn symbol_is_live(&mut self, def_id: LocalDefId) -> bool {
674 if self.live_symbols.contains(&def_id) {
675 return true;
676 }
677 // If it's a type whose items are live, then it's live, too.
678 // This is done to handle the case where, for example, the static
679 // method of a private type is used, but the type itself is never
680 // called directly.
681 let inherent_impls = self.tcx.inherent_impls(def_id);
682 for &impl_did in inherent_impls.iter() {
683 for item_did in self.tcx.associated_item_def_ids(impl_did) {
684 if let Some(def_id) = item_did.as_local()
685 && self.live_symbols.contains(&def_id)
686 {
687 return true;
688 }
689 }
690 }
691 false
692 }
693
694 fn warn_multiple_dead_codes(
695 &self,
696 dead_codes: &[(hir::HirId, Span, Symbol)],
697 participle: &str,
698 parent_hir_id: Option<hir::HirId>,
699 ) {
700 if let Some((id, _, name)) = dead_codes.first()
701 && !name.as_str().starts_with('_')
702 {
703 self.tcx.struct_span_lint_hir(
704 lint::builtin::DEAD_CODE,
705 *id,
706 MultiSpan::from_spans(
707 dead_codes.iter().map(|(_, span, _)| *span).collect(),
708 ),
709 |lint| {
710 let def_id = self.tcx.hir().local_def_id(*id);
711 let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
712 let span_len = dead_codes.len();
713 let names = match &dead_codes.iter().map(|(_, _, n)| n.to_string()).collect::<Vec<_>>()[..]
714 {
715 _ if span_len > 6 => String::new(),
716 [name] => format!("`{name}` "),
717 [names @ .., last] => {
718 format!("{} and `{last}` ", names.iter().map(|name| format!("`{name}`")).join(", "))
719 }
720 [] => unreachable!(),
721 };
722 let mut err = lint.build(&format!(
723 "{these}{descr}{s} {names}{are} never {participle}",
724 these = if span_len > 6 { "multiple " } else { "" },
725 s = pluralize!(span_len),
726 are = pluralize!("is", span_len),
727 ));
728 let hir = self.tcx.hir();
729 if let Some(parent_hir_id) = parent_hir_id
730 && let Some(parent_node) = hir.find(parent_hir_id)
731 && let Node::Item(item) = parent_node
732 {
733 let def_id = self.tcx.hir().local_def_id(parent_hir_id);
734 let parent_descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
735 err.span_label(
736 item.ident.span,
737 format!(
738 "{descr}{s} in this {parent_descr}",
739 s = pluralize!(span_len)
740 ),
741 );
742 }
743 if let Some(encl_scope) = hir.get_enclosing_scope(*id)
744 && let Some(encl_def_id) = hir.opt_local_def_id(encl_scope)
745 && let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id)
746 {
747 let traits_str = ign_traits
748 .iter()
749 .map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
750 .collect::<Vec<_>>()
751 .join(" and ");
752 let plural_s = pluralize!(ign_traits.len());
753 let article = if ign_traits.len() > 1 { "" } else { "a " };
754 let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
755 let msg = format!(
756 "`{}` has {}derived impl{} for the trait{} {}, but {} \
757 intentionally ignored during dead code analysis",
758 self.tcx.item_name(encl_def_id.to_def_id()),
759 article,
760 plural_s,
761 plural_s,
762 traits_str,
763 is_are
764 );
765 err.note(&msg);
766 }
767 err.emit();
768 },
769 );
770 }
771 }
772
773 fn warn_dead_fields_and_variants(
774 &self,
775 hir_id: hir::HirId,
776 participle: &str,
777 dead_codes: Vec<DeadVariant>,
778 ) {
779 let mut dead_codes = dead_codes
780 .iter()
781 .filter(|v| !v.name.as_str().starts_with('_'))
782 .map(|v| v)
783 .collect::<Vec<&DeadVariant>>();
784 if dead_codes.is_empty() {
785 return;
786 }
787 dead_codes.sort_by_key(|v| v.level);
788 for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) {
789 self.warn_multiple_dead_codes(
790 &group
791 .map(|v| (v.hir_id, v.span, v.name))
792 .collect::<Vec<(hir::HirId, Span, Symbol)>>(),
793 participle,
794 Some(hir_id),
795 );
796 }
797 }
798
799 fn warn_dead_code(
800 &mut self,
801 id: hir::HirId,
802 span: rustc_span::Span,
803 name: Symbol,
804 participle: &str,
805 ) {
806 self.warn_multiple_dead_codes(&[(id, span, name)], participle, None);
807 }
808 }
809
810 impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
811 type NestedFilter = nested_filter::All;
812
813 /// Walk nested items in place so that we don't report dead-code
814 /// on inner functions when the outer function is already getting
815 /// an error. We could do this also by checking the parents, but
816 /// this is how the code is setup and it seems harmless enough.
817 fn nested_visit_map(&mut self) -> Self::Map {
818 self.tcx.hir()
819 }
820
821 fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
822 if self.should_warn_about_item(item) {
823 // For most items, we want to highlight its identifier
824 let span = match item.kind {
825 hir::ItemKind::Fn(..)
826 | hir::ItemKind::Mod(..)
827 | hir::ItemKind::Enum(..)
828 | hir::ItemKind::Struct(..)
829 | hir::ItemKind::Union(..)
830 | hir::ItemKind::Trait(..)
831 | hir::ItemKind::Impl { .. } => {
832 // FIXME(66095): Because item.span is annotated with things
833 // like expansion data, and ident.span isn't, we use the
834 // def_span method if it's part of a macro invocation
835 // (and thus has a source_callee set).
836 // We should probably annotate ident.span with the macro
837 // context, but that's a larger change.
838 if item.span.source_callee().is_some() {
839 self.tcx.sess.source_map().guess_head_span(item.span)
840 } else {
841 item.ident.span
842 }
843 }
844 _ => item.span,
845 };
846 let participle = match item.kind {
847 hir::ItemKind::Struct(..) => "constructed", // Issue #52325
848 _ => "used",
849 };
850 self.warn_dead_code(item.hir_id(), span, item.ident.name, participle);
851 } else {
852 // Only continue if we didn't warn
853 intravisit::walk_item(self, item);
854 }
855 }
856
857 // This visitor should only visit a single module at a time.
858 fn visit_mod(&mut self, _: &'tcx hir::Mod<'tcx>, _: Span, _: hir::HirId) {}
859
860 fn visit_enum_def(
861 &mut self,
862 enum_definition: &'tcx hir::EnumDef<'tcx>,
863 generics: &'tcx hir::Generics<'tcx>,
864 item_id: hir::HirId,
865 _: Span,
866 ) {
867 intravisit::walk_enum_def(self, enum_definition, generics, item_id);
868 let dead_variants = enum_definition
869 .variants
870 .iter()
871 .filter_map(|variant| {
872 if self.should_warn_about_variant(&variant) {
873 Some(DeadVariant {
874 hir_id: variant.id,
875 span: variant.span,
876 name: variant.ident.name,
877 level: self.tcx.lint_level_at_node(lint::builtin::DEAD_CODE, variant.id).0,
878 })
879 } else {
880 None
881 }
882 })
883 .collect();
884 self.warn_dead_fields_and_variants(item_id, "constructed", dead_variants)
885 }
886
887 fn visit_variant(
888 &mut self,
889 variant: &'tcx hir::Variant<'tcx>,
890 g: &'tcx hir::Generics<'tcx>,
891 id: hir::HirId,
892 ) {
893 if !self.should_warn_about_variant(&variant) {
894 intravisit::walk_variant(self, variant, g, id);
895 }
896 }
897
898 fn visit_foreign_item(&mut self, fi: &'tcx hir::ForeignItem<'tcx>) {
899 if self.should_warn_about_foreign_item(fi) {
900 self.warn_dead_code(fi.hir_id(), fi.span, fi.ident.name, "used");
901 }
902 intravisit::walk_foreign_item(self, fi);
903 }
904
905 fn visit_variant_data(
906 &mut self,
907 def: &'tcx hir::VariantData<'tcx>,
908 _: Symbol,
909 _: &hir::Generics<'_>,
910 id: hir::HirId,
911 _: rustc_span::Span,
912 ) {
913 intravisit::walk_struct_def(self, def);
914 let dead_fields = def
915 .fields()
916 .iter()
917 .filter_map(|field| {
918 if self.should_warn_about_field(&field) {
919 Some(DeadVariant {
920 hir_id: field.hir_id,
921 span: field.span,
922 name: field.ident.name,
923 level: self
924 .tcx
925 .lint_level_at_node(lint::builtin::DEAD_CODE, field.hir_id)
926 .0,
927 })
928 } else {
929 None
930 }
931 })
932 .collect();
933 self.warn_dead_fields_and_variants(id, "read", dead_fields)
934 }
935
936 fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
937 match impl_item.kind {
938 hir::ImplItemKind::Const(_, body_id) => {
939 if !self.symbol_is_live(impl_item.def_id) {
940 self.warn_dead_code(
941 impl_item.hir_id(),
942 impl_item.span,
943 impl_item.ident.name,
944 "used",
945 );
946 }
947 self.visit_nested_body(body_id)
948 }
949 hir::ImplItemKind::Fn(_, body_id) => {
950 if !self.symbol_is_live(impl_item.def_id) {
951 // FIXME(66095): Because impl_item.span is annotated with things
952 // like expansion data, and ident.span isn't, we use the
953 // def_span method if it's part of a macro invocation
954 // (and thus has a source_callee set).
955 // We should probably annotate ident.span with the macro
956 // context, but that's a larger change.
957 let span = if impl_item.span.source_callee().is_some() {
958 self.tcx.sess.source_map().guess_head_span(impl_item.span)
959 } else {
960 impl_item.ident.span
961 };
962 self.warn_dead_code(impl_item.hir_id(), span, impl_item.ident.name, "used");
963 }
964 self.visit_nested_body(body_id)
965 }
966 hir::ImplItemKind::TyAlias(..) => {}
967 }
968 }
969
970 // Overwrite so that we don't warn the trait item itself.
971 fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
972 match trait_item.kind {
973 hir::TraitItemKind::Const(_, Some(body_id))
974 | hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)) => {
975 self.visit_nested_body(body_id)
976 }
977 hir::TraitItemKind::Const(_, None)
978 | hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_))
979 | hir::TraitItemKind::Type(..) => {}
980 }
981 }
982 }
983
984 fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) {
985 let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(());
986 let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
987 let (module, _, module_id) = tcx.hir().get_module(module);
988 // Do not use an ItemLikeVisitor since we may want to skip visiting some items
989 // when a surrounding one is warned against or `_`.
990 intravisit::walk_mod(&mut visitor, module, module_id);
991 }
992
993 pub(crate) fn provide(providers: &mut Providers) {
994 *providers =
995 Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
996 }