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