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