]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/levels.rs
Rollup merge of #82826 - pierwill:fix-IPv, r=JohnTitor
[rust.git] / compiler / rustc_lint / src / levels.rs
1 use crate::context::{CheckLintNameResult, LintStore};
2 use crate::late::unerased_lint_store;
3 use rustc_ast as ast;
4 use rustc_ast::attr;
5 use rustc_ast::unwrap_or;
6 use rustc_ast_pretty::pprust;
7 use rustc_data_structures::fx::FxHashMap;
8 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
9 use rustc_hir as hir;
10 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
11 use rustc_hir::{intravisit, HirId};
12 use rustc_middle::hir::map::Map;
13 use rustc_middle::lint::LevelAndSource;
14 use rustc_middle::lint::LintDiagnosticBuilder;
15 use rustc_middle::lint::{
16     struct_lint_level, LintLevelMap, LintLevelSets, LintLevelSource, LintSet,
17 };
18 use rustc_middle::ty::query::Providers;
19 use rustc_middle::ty::TyCtxt;
20 use rustc_session::lint::{
21     builtin::{self, FORBIDDEN_LINT_GROUPS},
22     Level, Lint, LintId,
23 };
24 use rustc_session::parse::feature_err;
25 use rustc_session::Session;
26 use rustc_span::symbol::{sym, Symbol};
27 use rustc_span::{source_map::MultiSpan, Span, DUMMY_SP};
28 use tracing::debug;
29
30 use std::cmp;
31
32 fn lint_levels(tcx: TyCtxt<'_>, cnum: CrateNum) -> LintLevelMap {
33     assert_eq!(cnum, LOCAL_CRATE);
34     let store = unerased_lint_store(tcx);
35     let levels = LintLevelsBuilder::new(tcx.sess, false, &store);
36     let mut builder = LintLevelMapBuilder { levels, tcx, store };
37     let krate = tcx.hir().krate();
38
39     builder.levels.id_to_set.reserve(krate.exported_macros.len() + 1);
40
41     let push = builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), &store, true);
42     builder.levels.register_id(hir::CRATE_HIR_ID);
43     for macro_def in krate.exported_macros {
44         builder.levels.register_id(macro_def.hir_id());
45     }
46     intravisit::walk_crate(&mut builder, krate);
47     builder.levels.pop(push);
48
49     builder.levels.build_map()
50 }
51
52 pub struct LintLevelsBuilder<'s> {
53     sess: &'s Session,
54     sets: LintLevelSets,
55     id_to_set: FxHashMap<HirId, u32>,
56     cur: u32,
57     warn_about_weird_lints: bool,
58     store: &'s LintStore,
59 }
60
61 pub struct BuilderPush {
62     prev: u32,
63     pub changed: bool,
64 }
65
66 impl<'s> LintLevelsBuilder<'s> {
67     pub fn new(sess: &'s Session, warn_about_weird_lints: bool, store: &'s LintStore) -> Self {
68         let mut builder = LintLevelsBuilder {
69             sess,
70             sets: LintLevelSets::new(),
71             cur: 0,
72             id_to_set: Default::default(),
73             warn_about_weird_lints,
74             store,
75         };
76         builder.process_command_line(sess, store);
77         assert_eq!(builder.sets.list.len(), 1);
78         builder
79     }
80
81     fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
82         let mut specs = FxHashMap::default();
83         self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
84
85         for &(ref lint_name, level) in &sess.opts.lint_opts {
86             store.check_lint_name_cmdline(sess, &lint_name, level);
87             let orig_level = level;
88
89             // If the cap is less than this specified level, e.g., if we've got
90             // `--cap-lints allow` but we've also got `-D foo` then we ignore
91             // this specification as the lint cap will set it to allow anyway.
92             let level = cmp::min(level, self.sets.lint_cap);
93
94             let lint_flag_val = Symbol::intern(lint_name);
95
96             let ids = match store.find_lints(&lint_name) {
97                 Ok(ids) => ids,
98                 Err(_) => continue, // errors handled in check_lint_name_cmdline above
99             };
100             for id in ids {
101                 self.check_gated_lint(id, DUMMY_SP);
102                 let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
103                 specs.insert(id, (level, src));
104             }
105         }
106
107         self.sets.list.push(LintSet::CommandLine { specs });
108     }
109
110     /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
111     /// (e.g. if a forbid was already inserted on the same scope), then emits a
112     /// diagnostic with no change to `specs`.
113     fn insert_spec(
114         &mut self,
115         specs: &mut FxHashMap<LintId, LevelAndSource>,
116         id: LintId,
117         (level, src): LevelAndSource,
118     ) {
119         // Setting to a non-forbid level is an error if the lint previously had
120         // a forbid level. Note that this is not necessarily true even with a
121         // `#[forbid(..)]` attribute present, as that is overriden by `--cap-lints`.
122         //
123         // This means that this only errors if we're truly lowering the lint
124         // level from forbid.
125         if level != Level::Forbid {
126             if let (Level::Forbid, old_src) =
127                 self.sets.get_lint_level(id.lint, self.cur, Some(&specs), &self.sess)
128             {
129                 // Backwards compatibility check:
130                 //
131                 // We used to not consider `forbid(lint_group)`
132                 // as preventing `allow(lint)` for some lint `lint` in
133                 // `lint_group`. For now, issue a future-compatibility
134                 // warning for this case.
135                 let id_name = id.lint.name_lower();
136                 let fcw_warning = match old_src {
137                     LintLevelSource::Default => false,
138                     LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol),
139                     LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
140                 };
141                 debug!(
142                     "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
143                     fcw_warning, specs, old_src, id_name
144                 );
145
146                 let decorate_diag_builder = |mut diag_builder: DiagnosticBuilder<'_>| {
147                     diag_builder.span_label(src.span(), "overruled by previous forbid");
148                     match old_src {
149                         LintLevelSource::Default => {
150                             diag_builder.note(&format!(
151                                 "`forbid` lint level is the default for {}",
152                                 id.to_string()
153                             ));
154                         }
155                         LintLevelSource::Node(_, forbid_source_span, reason) => {
156                             diag_builder.span_label(forbid_source_span, "`forbid` level set here");
157                             if let Some(rationale) = reason {
158                                 diag_builder.note(&rationale.as_str());
159                             }
160                         }
161                         LintLevelSource::CommandLine(_, _) => {
162                             diag_builder.note("`forbid` lint level was set on command line");
163                         }
164                     }
165                     diag_builder.emit();
166                 };
167                 if !fcw_warning {
168                     let diag_builder = struct_span_err!(
169                         self.sess,
170                         src.span(),
171                         E0453,
172                         "{}({}) incompatible with previous forbid",
173                         level.as_str(),
174                         src.name(),
175                     );
176                     decorate_diag_builder(diag_builder);
177                 } else {
178                     self.struct_lint(
179                         FORBIDDEN_LINT_GROUPS,
180                         Some(src.span().into()),
181                         |diag_builder| {
182                             let diag_builder = diag_builder.build(&format!(
183                                 "{}({}) incompatible with previous forbid",
184                                 level.as_str(),
185                                 src.name(),
186                             ));
187                             decorate_diag_builder(diag_builder);
188                         },
189                     );
190                 }
191
192                 // Retain the forbid lint level, unless we are
193                 // issuing a FCW. In the FCW case, we want to
194                 // respect the new setting.
195                 if !fcw_warning {
196                     return;
197                 }
198             }
199         }
200         specs.insert(id, (level, src));
201     }
202
203     /// Pushes a list of AST lint attributes onto this context.
204     ///
205     /// This function will return a `BuilderPush` object which should be passed
206     /// to `pop` when this scope for the attributes provided is exited.
207     ///
208     /// This function will perform a number of tasks:
209     ///
210     /// * It'll validate all lint-related attributes in `attrs`
211     /// * It'll mark all lint-related attributes as used
212     /// * Lint levels will be updated based on the attributes provided
213     /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
214     ///   `#[allow]`
215     ///
216     /// Don't forget to call `pop`!
217     pub(crate) fn push(
218         &mut self,
219         attrs: &[ast::Attribute],
220         store: &LintStore,
221         is_crate_node: bool,
222     ) -> BuilderPush {
223         let mut specs = FxHashMap::default();
224         let sess = self.sess;
225         let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
226         for attr in attrs {
227             let level = match Level::from_symbol(attr.name_or_empty()) {
228                 None => continue,
229                 Some(lvl) => lvl,
230             };
231
232             let meta = unwrap_or!(attr.meta(), continue);
233             self.sess.mark_attr_used(attr);
234
235             let mut metas = unwrap_or!(meta.meta_item_list(), continue);
236
237             if metas.is_empty() {
238                 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
239                 continue;
240             }
241
242             // Before processing the lint names, look for a reason (RFC 2383)
243             // at the end.
244             let mut reason = None;
245             let tail_li = &metas[metas.len() - 1];
246             if let Some(item) = tail_li.meta_item() {
247                 match item.kind {
248                     ast::MetaItemKind::Word => {} // actual lint names handled later
249                     ast::MetaItemKind::NameValue(ref name_value) => {
250                         if item.path == sym::reason {
251                             // found reason, reslice meta list to exclude it
252                             metas = &metas[0..metas.len() - 1];
253                             // FIXME (#55112): issue unused-attributes lint if we thereby
254                             // don't have any lint names (`#[level(reason = "foo")]`)
255                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
256                                 if !self.sess.features_untracked().lint_reasons {
257                                     feature_err(
258                                         &self.sess.parse_sess,
259                                         sym::lint_reasons,
260                                         item.span,
261                                         "lint reasons are experimental",
262                                     )
263                                     .emit();
264                                 }
265                                 reason = Some(rationale);
266                             } else {
267                                 bad_attr(name_value.span)
268                                     .span_label(name_value.span, "reason must be a string literal")
269                                     .emit();
270                             }
271                         } else {
272                             bad_attr(item.span)
273                                 .span_label(item.span, "bad attribute argument")
274                                 .emit();
275                         }
276                     }
277                     ast::MetaItemKind::List(_) => {
278                         bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
279                     }
280                 }
281             }
282
283             for li in metas {
284                 let meta_item = match li.meta_item() {
285                     Some(meta_item) if meta_item.is_word() => meta_item,
286                     _ => {
287                         let sp = li.span();
288                         let mut err = bad_attr(sp);
289                         let mut add_label = true;
290                         if let Some(item) = li.meta_item() {
291                             if let ast::MetaItemKind::NameValue(_) = item.kind {
292                                 if item.path == sym::reason {
293                                     err.span_label(sp, "reason in lint attribute must come last");
294                                     add_label = false;
295                                 }
296                             }
297                         }
298                         if add_label {
299                             err.span_label(sp, "bad attribute argument");
300                         }
301                         err.emit();
302                         continue;
303                     }
304                 };
305                 let tool_name = if meta_item.path.segments.len() > 1 {
306                     let tool_ident = meta_item.path.segments[0].ident;
307                     if !attr::is_known_lint_tool(tool_ident) {
308                         struct_span_err!(
309                             sess,
310                             tool_ident.span,
311                             E0710,
312                             "an unknown tool name found in scoped lint: `{}`",
313                             pprust::path_to_string(&meta_item.path),
314                         )
315                         .emit();
316                         continue;
317                     }
318
319                     Some(tool_ident.name)
320                 } else {
321                     None
322                 };
323                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
324                 let lint_result = store.check_lint_name(&name.as_str(), tool_name);
325                 match &lint_result {
326                     CheckLintNameResult::Ok(ids) => {
327                         let src = LintLevelSource::Node(name, li.span(), reason);
328                         for &id in *ids {
329                             self.check_gated_lint(id, attr.span);
330                             self.insert_spec(&mut specs, id, (level, src));
331                         }
332                     }
333
334                     CheckLintNameResult::Tool(result) => {
335                         match *result {
336                             Ok(ids) => {
337                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
338                                 let src = LintLevelSource::Node(
339                                     Symbol::intern(complete_name),
340                                     li.span(),
341                                     reason,
342                                 );
343                                 for id in ids {
344                                     self.insert_spec(&mut specs, *id, (level, src));
345                                 }
346                             }
347                             Err((Some(ids), ref new_lint_name)) => {
348                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
349                                 let (lvl, src) =
350                                     self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
351                                 struct_lint_level(
352                                     self.sess,
353                                     lint,
354                                     lvl,
355                                     src,
356                                     Some(li.span().into()),
357                                     |lint| {
358                                         let msg = format!(
359                                             "lint name `{}` is deprecated \
360                                              and may not have an effect in the future.",
361                                             name
362                                         );
363                                         lint.build(&msg)
364                                             .span_suggestion(
365                                                 li.span(),
366                                                 "change it to",
367                                                 new_lint_name.to_string(),
368                                                 Applicability::MachineApplicable,
369                                             )
370                                             .emit();
371                                     },
372                                 );
373
374                                 let src = LintLevelSource::Node(
375                                     Symbol::intern(&new_lint_name),
376                                     li.span(),
377                                     reason,
378                                 );
379                                 for id in ids {
380                                     self.insert_spec(&mut specs, *id, (level, src));
381                                 }
382                             }
383                             Err((None, _)) => {
384                                 // If Tool(Err(None, _)) is returned, then either the lint does not
385                                 // exist in the tool or the code was not compiled with the tool and
386                                 // therefore the lint was never added to the `LintStore`. To detect
387                                 // this is the responsibility of the lint tool.
388                             }
389                         }
390                     }
391
392                     _ if !self.warn_about_weird_lints => {}
393
394                     CheckLintNameResult::Warning(msg, renamed) => {
395                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
396                         let (renamed_lint_level, src) =
397                             self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
398                         struct_lint_level(
399                             self.sess,
400                             lint,
401                             renamed_lint_level,
402                             src,
403                             Some(li.span().into()),
404                             |lint| {
405                                 let mut err = lint.build(&msg);
406                                 if let Some(new_name) = &renamed {
407                                     err.span_suggestion(
408                                         li.span(),
409                                         "use the new name",
410                                         new_name.to_string(),
411                                         Applicability::MachineApplicable,
412                                     );
413                                 }
414                                 err.emit();
415                             },
416                         );
417                     }
418                     CheckLintNameResult::NoLint(suggestion) => {
419                         let lint = builtin::UNKNOWN_LINTS;
420                         let (level, src) =
421                             self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
422                         struct_lint_level(
423                             self.sess,
424                             lint,
425                             level,
426                             src,
427                             Some(li.span().into()),
428                             |lint| {
429                                 let name = if let Some(tool_name) = tool_name {
430                                     format!("{}::{}", tool_name, name)
431                                 } else {
432                                     name.to_string()
433                                 };
434                                 let mut db = lint.build(&format!("unknown lint: `{}`", name));
435                                 if let Some(suggestion) = suggestion {
436                                     db.span_suggestion(
437                                         li.span(),
438                                         "did you mean",
439                                         suggestion.to_string(),
440                                         Applicability::MachineApplicable,
441                                     );
442                                 }
443                                 db.emit();
444                             },
445                         );
446                     }
447                 }
448                 // If this lint was renamed, apply the new lint instead of ignoring the attribute.
449                 // This happens outside of the match because the new lint should be applied even if
450                 // we don't warn about the name change.
451                 if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result {
452                     // Ignore any errors or warnings that happen because the new name is inaccurate
453                     if let CheckLintNameResult::Ok(ids) =
454                         store.check_lint_name(&new_name, tool_name)
455                     {
456                         let src =
457                             LintLevelSource::Node(Symbol::intern(&new_name), li.span(), reason);
458                         for &id in ids {
459                             self.check_gated_lint(id, attr.span);
460                             self.insert_spec(&mut specs, id, (level, src));
461                         }
462                     }
463                 }
464             }
465         }
466
467         if !is_crate_node {
468             for (id, &(level, ref src)) in specs.iter() {
469                 if !id.lint.crate_level_only {
470                     continue;
471                 }
472
473                 let (lint_attr_name, lint_attr_span) = match *src {
474                     LintLevelSource::Node(name, span, _) => (name, span),
475                     _ => continue,
476                 };
477
478                 let lint = builtin::UNUSED_ATTRIBUTES;
479                 let (lint_level, lint_src) =
480                     self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
481                 struct_lint_level(
482                     self.sess,
483                     lint,
484                     lint_level,
485                     lint_src,
486                     Some(lint_attr_span.into()),
487                     |lint| {
488                         let mut db = lint.build(&format!(
489                             "{}({}) is ignored unless specified at crate level",
490                             level.as_str(),
491                             lint_attr_name
492                         ));
493                         db.emit();
494                     },
495                 );
496                 // don't set a separate error for every lint in the group
497                 break;
498             }
499         }
500
501         let prev = self.cur;
502         if !specs.is_empty() {
503             self.cur = self.sets.list.len() as u32;
504             self.sets.list.push(LintSet::Node { specs, parent: prev });
505         }
506
507         BuilderPush { prev, changed: prev != self.cur }
508     }
509
510     /// Checks if the lint is gated on a feature that is not enabled.
511     fn check_gated_lint(&self, lint_id: LintId, span: Span) {
512         if let Some(feature) = lint_id.lint.feature_gate {
513             if !self.sess.features_untracked().enabled(feature) {
514                 feature_err(
515                     &self.sess.parse_sess,
516                     feature,
517                     span,
518                     &format!("the `{}` lint is unstable", lint_id.lint.name_lower()),
519                 )
520                 .emit();
521             }
522         }
523     }
524
525     /// Called after `push` when the scope of a set of attributes are exited.
526     pub fn pop(&mut self, push: BuilderPush) {
527         self.cur = push.prev;
528     }
529
530     /// Find the lint level for a lint.
531     pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) {
532         self.sets.get_lint_level(lint, self.cur, None, self.sess)
533     }
534
535     /// Used to emit a lint-related diagnostic based on the current state of
536     /// this lint context.
537     pub fn struct_lint(
538         &self,
539         lint: &'static Lint,
540         span: Option<MultiSpan>,
541         decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>),
542     ) {
543         let (level, src) = self.lint_level(lint);
544         struct_lint_level(self.sess, lint, level, src, span, decorate)
545     }
546
547     /// Registers the ID provided with the current set of lints stored in
548     /// this context.
549     pub fn register_id(&mut self, id: HirId) {
550         self.id_to_set.insert(id, self.cur);
551     }
552
553     pub fn build(self) -> LintLevelSets {
554         self.sets
555     }
556
557     pub fn build_map(self) -> LintLevelMap {
558         LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
559     }
560 }
561
562 struct LintLevelMapBuilder<'a, 'tcx> {
563     levels: LintLevelsBuilder<'tcx>,
564     tcx: TyCtxt<'tcx>,
565     store: &'a LintStore,
566 }
567
568 impl LintLevelMapBuilder<'_, '_> {
569     fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F)
570     where
571         F: FnOnce(&mut Self),
572     {
573         let is_crate_hir = id == hir::CRATE_HIR_ID;
574         let attrs = self.tcx.hir().attrs(id);
575         let push = self.levels.push(attrs, self.store, is_crate_hir);
576         if push.changed {
577             self.levels.register_id(id);
578         }
579         f(self);
580         self.levels.pop(push);
581     }
582 }
583
584 impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'_, 'tcx> {
585     type Map = Map<'tcx>;
586
587     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
588         intravisit::NestedVisitorMap::All(self.tcx.hir())
589     }
590
591     fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
592         self.with_lint_attrs(param.hir_id, |builder| {
593             intravisit::walk_param(builder, param);
594         });
595     }
596
597     fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
598         self.with_lint_attrs(it.hir_id(), |builder| {
599             intravisit::walk_item(builder, it);
600         });
601     }
602
603     fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
604         self.with_lint_attrs(it.hir_id(), |builder| {
605             intravisit::walk_foreign_item(builder, it);
606         })
607     }
608
609     fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
610         // We will call `with_lint_attrs` when we walk
611         // the `StmtKind`. The outer statement itself doesn't
612         // define the lint levels.
613         intravisit::walk_stmt(self, e);
614     }
615
616     fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
617         self.with_lint_attrs(e.hir_id, |builder| {
618             intravisit::walk_expr(builder, e);
619         })
620     }
621
622     fn visit_struct_field(&mut self, s: &'tcx hir::StructField<'tcx>) {
623         self.with_lint_attrs(s.hir_id, |builder| {
624             intravisit::walk_struct_field(builder, s);
625         })
626     }
627
628     fn visit_variant(
629         &mut self,
630         v: &'tcx hir::Variant<'tcx>,
631         g: &'tcx hir::Generics<'tcx>,
632         item_id: hir::HirId,
633     ) {
634         self.with_lint_attrs(v.id, |builder| {
635             intravisit::walk_variant(builder, v, g, item_id);
636         })
637     }
638
639     fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
640         self.with_lint_attrs(l.hir_id, |builder| {
641             intravisit::walk_local(builder, l);
642         })
643     }
644
645     fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
646         self.with_lint_attrs(a.hir_id, |builder| {
647             intravisit::walk_arm(builder, a);
648         })
649     }
650
651     fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
652         self.with_lint_attrs(trait_item.hir_id(), |builder| {
653             intravisit::walk_trait_item(builder, trait_item);
654         });
655     }
656
657     fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
658         self.with_lint_attrs(impl_item.hir_id(), |builder| {
659             intravisit::walk_impl_item(builder, impl_item);
660         });
661     }
662 }
663
664 pub fn provide(providers: &mut Providers) {
665     providers.lint_levels = lint_levels;
666 }