]> git.lizzy.rs Git - rust.git/blob - src/librustc/lint/levels.rs
27bf9649324b4730f560049bd814ca2b43bd40b6
[rust.git] / src / librustc / lint / levels.rs
1 use std::cmp;
2
3 use crate::hir::HirId;
4 use crate::ich::StableHashingContext;
5 use crate::lint::builtin;
6 use crate::lint::context::{LintStore, CheckLintNameResult};
7 use crate::lint::{self, Lint, LintId, Level, LintSource};
8 use crate::session::Session;
9 use crate::util::nodemap::FxHashMap;
10 use errors::{Applicability, DiagnosticBuilder};
11 use rustc_data_structures::stable_hasher::{HashStable, ToStableHashKey, StableHasher};
12 use syntax::ast;
13 use syntax::attr;
14 use syntax::feature_gate;
15 use syntax::print::pprust;
16 use syntax::source_map::MultiSpan;
17 use syntax::symbol::{Symbol, sym};
18
19 use rustc_error_codes::*;
20
21 pub struct LintLevelSets {
22     list: Vec<LintSet>,
23     lint_cap: Level,
24 }
25
26 enum LintSet {
27     CommandLine {
28         // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
29         // flag.
30         specs: FxHashMap<LintId, (Level, LintSource)>,
31     },
32
33     Node {
34         specs: FxHashMap<LintId, (Level, LintSource)>,
35         parent: u32,
36     },
37 }
38
39 impl LintLevelSets {
40     pub fn new(sess: &Session, lint_store: &LintStore) -> LintLevelSets {
41         let mut me = LintLevelSets {
42             list: Vec::new(),
43             lint_cap: Level::Forbid,
44         };
45         me.process_command_line(sess, lint_store);
46         return me
47     }
48
49     pub fn builder<'a>(
50         sess: &'a Session,
51         warn_about_weird_lints: bool,
52         store: &LintStore,
53     ) -> LintLevelsBuilder<'a> {
54         LintLevelsBuilder::new(sess, warn_about_weird_lints, LintLevelSets::new(sess, store))
55     }
56
57     fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
58         let mut specs = FxHashMap::default();
59         self.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
60
61         for &(ref lint_name, level) in &sess.opts.lint_opts {
62             store.check_lint_name_cmdline(sess, &lint_name, level);
63
64             // If the cap is less than this specified level, e.g., if we've got
65             // `--cap-lints allow` but we've also got `-D foo` then we ignore
66             // this specification as the lint cap will set it to allow anyway.
67             let level = cmp::min(level, self.lint_cap);
68
69             let lint_flag_val = Symbol::intern(lint_name);
70             let ids = match store.find_lints(&lint_name) {
71                 Ok(ids) => ids,
72                 Err(_) => continue, // errors handled in check_lint_name_cmdline above
73             };
74             for id in ids {
75                 let src = LintSource::CommandLine(lint_flag_val);
76                 specs.insert(id, (level, src));
77             }
78         }
79
80         self.list.push(LintSet::CommandLine {
81             specs: specs,
82         });
83     }
84
85     fn get_lint_level(&self,
86                       lint: &'static Lint,
87                       idx: u32,
88                       aux: Option<&FxHashMap<LintId, (Level, LintSource)>>,
89                       sess: &Session)
90         -> (Level, LintSource)
91     {
92         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
93
94         // If `level` is none then we actually assume the default level for this
95         // lint.
96         let mut level = level.unwrap_or_else(|| lint.default_level(sess));
97
98         // If we're about to issue a warning, check at the last minute for any
99         // directives against the warnings "lint". If, for example, there's an
100         // `allow(warnings)` in scope then we want to respect that instead.
101         if level == Level::Warn {
102             let (warnings_level, warnings_src) =
103                 self.get_lint_id_level(LintId::of(lint::builtin::WARNINGS),
104                                        idx,
105                                        aux);
106             if let Some(configured_warning_level) = warnings_level {
107                 if configured_warning_level != Level::Warn {
108                     level = configured_warning_level;
109                     src = warnings_src;
110                 }
111             }
112         }
113
114         // Ensure that we never exceed the `--cap-lints` argument.
115         level = cmp::min(level, self.lint_cap);
116
117         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
118             // Ensure that we never exceed driver level.
119             level = cmp::min(*driver_level, level);
120         }
121
122         return (level, src)
123     }
124
125     fn get_lint_id_level(&self,
126                          id: LintId,
127                          mut idx: u32,
128                          aux: Option<&FxHashMap<LintId, (Level, LintSource)>>)
129         -> (Option<Level>, LintSource)
130     {
131         if let Some(specs) = aux {
132             if let Some(&(level, src)) = specs.get(&id) {
133                 return (Some(level), src)
134             }
135         }
136         loop {
137             match self.list[idx as usize] {
138                 LintSet::CommandLine { ref specs } => {
139                     if let Some(&(level, src)) = specs.get(&id) {
140                         return (Some(level), src)
141                     }
142                     return (None, LintSource::Default)
143                 }
144                 LintSet::Node { ref specs, parent } => {
145                     if let Some(&(level, src)) = specs.get(&id) {
146                         return (Some(level), src)
147                     }
148                     idx = parent;
149                 }
150             }
151         }
152     }
153 }
154
155 pub struct LintLevelsBuilder<'a> {
156     sess: &'a Session,
157     sets: LintLevelSets,
158     id_to_set: FxHashMap<HirId, u32>,
159     cur: u32,
160     warn_about_weird_lints: bool,
161 }
162
163 pub struct BuilderPush {
164     prev: u32,
165     pub(super) changed: bool,
166 }
167
168 impl<'a> LintLevelsBuilder<'a> {
169     pub fn new(
170         sess: &'a Session,
171         warn_about_weird_lints: bool,
172         sets: LintLevelSets,
173     ) -> LintLevelsBuilder<'a> {
174         assert_eq!(sets.list.len(), 1);
175         LintLevelsBuilder {
176             sess,
177             sets,
178             cur: 0,
179             id_to_set: Default::default(),
180             warn_about_weird_lints,
181         }
182     }
183
184     /// Pushes a list of AST lint attributes onto this context.
185     ///
186     /// This function will return a `BuilderPush` object which should be passed
187     /// to `pop` when this scope for the attributes provided is exited.
188     ///
189     /// This function will perform a number of tasks:
190     ///
191     /// * It'll validate all lint-related attributes in `attrs`
192     /// * It'll mark all lint-related attributes as used
193     /// * Lint levels will be updated based on the attributes provided
194     /// * Lint attributes are validated, e.g., a #[forbid] can't be switched to
195     ///   #[allow]
196     ///
197     /// Don't forget to call `pop`!
198     pub fn push(&mut self, attrs: &[ast::Attribute], store: &LintStore) -> BuilderPush {
199         let mut specs = FxHashMap::default();
200         let sess = self.sess;
201         let bad_attr = |span| {
202             struct_span_err!(sess, span, E0452, "malformed lint attribute input")
203         };
204         for attr in attrs {
205             let level = match Level::from_symbol(attr.name_or_empty()) {
206                 None => continue,
207                 Some(lvl) => lvl,
208             };
209
210             let meta = unwrap_or!(attr.meta(), continue);
211             attr::mark_used(attr);
212
213             let mut metas = unwrap_or!(meta.meta_item_list(), continue);
214
215             if metas.is_empty() {
216                 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
217                 continue;
218             }
219
220             // Before processing the lint names, look for a reason (RFC 2383)
221             // at the end.
222             let mut reason = None;
223             let tail_li = &metas[metas.len()-1];
224             if let Some(item) = tail_li.meta_item() {
225                 match item.kind {
226                     ast::MetaItemKind::Word => {}  // actual lint names handled later
227                     ast::MetaItemKind::NameValue(ref name_value) => {
228                         if item.path == sym::reason {
229                             // found reason, reslice meta list to exclude it
230                             metas = &metas[0..metas.len()-1];
231                             // FIXME (#55112): issue unused-attributes lint if we thereby
232                             // don't have any lint names (`#[level(reason = "foo")]`)
233                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
234                                 if !self.sess.features_untracked().lint_reasons {
235                                     feature_gate::emit_feature_err(
236                                         &self.sess.parse_sess,
237                                         sym::lint_reasons,
238                                         item.span,
239                                         feature_gate::GateIssue::Language,
240                                         "lint reasons are experimental"
241                                     );
242                                 }
243                                 reason = Some(rationale);
244                             } else {
245                                 bad_attr(name_value.span)
246                                     .span_label(name_value.span, "reason must be a string literal")
247                                     .emit();
248                             }
249                         } else {
250                             bad_attr(item.span)
251                                 .span_label(item.span, "bad attribute argument")
252                                 .emit();
253                         }
254                     },
255                     ast::MetaItemKind::List(_) => {
256                         bad_attr(item.span)
257                             .span_label(item.span, "bad attribute argument")
258                             .emit();
259                     }
260                 }
261             }
262
263             for li in metas {
264                 let meta_item = match li.meta_item() {
265                     Some(meta_item) if meta_item.is_word() => meta_item,
266                     _ => {
267                         let sp = li.span();
268                         let mut err = bad_attr(sp);
269                         let mut add_label = true;
270                         if let Some(item) = li.meta_item() {
271                             if let ast::MetaItemKind::NameValue(_) = item.kind {
272                                 if item.path == sym::reason {
273                                     err.span_label(sp, "reason in lint attribute must come last");
274                                     add_label = false;
275                                 }
276                             }
277                         }
278                         if add_label {
279                             err.span_label(sp, "bad attribute argument");
280                         }
281                         err.emit();
282                         continue;
283                     }
284                 };
285                 let tool_name = if meta_item.path.segments.len() > 1 {
286                     let tool_ident = meta_item.path.segments[0].ident;
287                     if !attr::is_known_lint_tool(tool_ident) {
288                         span_err!(
289                             sess,
290                             tool_ident.span,
291                             E0710,
292                             "an unknown tool name found in scoped lint: `{}`",
293                             pprust::path_to_string(&meta_item.path),
294                         );
295                         continue;
296                     }
297
298                     Some(tool_ident.name)
299                 } else {
300                     None
301                 };
302                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
303                 match store.check_lint_name(&name.as_str(), tool_name) {
304                     CheckLintNameResult::Ok(ids) => {
305                         let src = LintSource::Node(name, li.span(), reason);
306                         for id in ids {
307                             specs.insert(*id, (level, src));
308                         }
309                     }
310
311                     CheckLintNameResult::Tool(result) => {
312                         match result {
313                             Ok(ids) => {
314                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
315                                 let src = LintSource::Node(
316                                     Symbol::intern(complete_name), li.span(), reason
317                                 );
318                                 for id in ids {
319                                     specs.insert(*id, (level, src));
320                                 }
321                             }
322                             Err((Some(ids), new_lint_name)) => {
323                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
324                                 let (lvl, src) =
325                                     self.sets
326                                         .get_lint_level(lint, self.cur, Some(&specs), &sess);
327                                 let msg = format!(
328                                     "lint name `{}` is deprecated \
329                                      and may not have an effect in the future. \
330                                      Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
331                                     name
332                                 );
333                                 lint::struct_lint_level(
334                                     self.sess,
335                                     lint,
336                                     lvl,
337                                     src,
338                                     Some(li.span().into()),
339                                     &msg,
340                                 ).span_suggestion(
341                                     li.span(),
342                                     "change it to",
343                                     new_lint_name.to_string(),
344                                     Applicability::MachineApplicable,
345                                 ).emit();
346
347                                 let src = LintSource::Node(
348                                     Symbol::intern(&new_lint_name), li.span(), reason
349                                 );
350                                 for id in ids {
351                                     specs.insert(*id, (level, src));
352                                 }
353                             }
354                             Err((None, _)) => {
355                                 // If Tool(Err(None, _)) is returned, then either the lint does not
356                                 // exist in the tool or the code was not compiled with the tool and
357                                 // therefore the lint was never added to the `LintStore`. To detect
358                                 // this is the responsibility of the lint tool.
359                             }
360                         }
361                     }
362
363                     _ if !self.warn_about_weird_lints => {}
364
365                     CheckLintNameResult::Warning(msg, renamed) => {
366                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
367                         let (level, src) = self.sets.get_lint_level(lint,
368                                                                     self.cur,
369                                                                     Some(&specs),
370                                                                     &sess);
371                         let mut err = lint::struct_lint_level(self.sess,
372                                                               lint,
373                                                               level,
374                                                               src,
375                                                               Some(li.span().into()),
376                                                               &msg);
377                         if let Some(new_name) = renamed {
378                             err.span_suggestion(
379                                 li.span(),
380                                 "use the new name",
381                                 new_name,
382                                 Applicability::MachineApplicable
383                             );
384                         }
385                         err.emit();
386                     }
387                     CheckLintNameResult::NoLint(suggestion) => {
388                         let lint = builtin::UNKNOWN_LINTS;
389                         let (level, src) = self.sets.get_lint_level(lint,
390                                                                     self.cur,
391                                                                     Some(&specs),
392                                                                     self.sess);
393                         let msg = format!("unknown lint: `{}`", name);
394                         let mut db = lint::struct_lint_level(self.sess,
395                                                 lint,
396                                                 level,
397                                                 src,
398                                                 Some(li.span().into()),
399                                                 &msg);
400
401                         if let Some(suggestion) = suggestion {
402                             db.span_suggestion(
403                                 li.span(),
404                                 "did you mean",
405                                 suggestion.to_string(),
406                                 Applicability::MachineApplicable,
407                             );
408                         }
409
410                         db.emit();
411                     }
412                 }
413             }
414         }
415
416         for (id, &(level, ref src)) in specs.iter() {
417             if level == Level::Forbid {
418                 continue
419             }
420             let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
421                 (Some(Level::Forbid), src) => src,
422                 _ => continue,
423             };
424             let forbidden_lint_name = match forbid_src {
425                 LintSource::Default => id.to_string(),
426                 LintSource::Node(name, _, _) => name.to_string(),
427                 LintSource::CommandLine(name) => name.to_string(),
428             };
429             let (lint_attr_name, lint_attr_span) = match *src {
430                 LintSource::Node(name, span, _) => (name, span),
431                 _ => continue,
432             };
433             let mut diag_builder = struct_span_err!(self.sess,
434                                                     lint_attr_span,
435                                                     E0453,
436                                                     "{}({}) overruled by outer forbid({})",
437                                                     level.as_str(),
438                                                     lint_attr_name,
439                                                     forbidden_lint_name);
440             diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
441             match forbid_src {
442                 LintSource::Default => {},
443                 LintSource::Node(_, forbid_source_span, reason) => {
444                     diag_builder.span_label(forbid_source_span,
445                                             "`forbid` level set here");
446                     if let Some(rationale) = reason {
447                         diag_builder.note(&rationale.as_str());
448                     }
449                 },
450                 LintSource::CommandLine(_) => {
451                     diag_builder.note("`forbid` lint level was set on command line");
452                 }
453             }
454             diag_builder.emit();
455             // don't set a separate error for every lint in the group
456             break
457         }
458
459         let prev = self.cur;
460         if specs.len() > 0 {
461             self.cur = self.sets.list.len() as u32;
462             self.sets.list.push(LintSet::Node {
463                 specs: specs,
464                 parent: prev,
465             });
466         }
467
468         BuilderPush {
469             prev: prev,
470             changed: prev != self.cur,
471         }
472     }
473
474     /// Called after `push` when the scope of a set of attributes are exited.
475     pub fn pop(&mut self, push: BuilderPush) {
476         self.cur = push.prev;
477     }
478
479     /// Used to emit a lint-related diagnostic based on the current state of
480     /// this lint context.
481     pub fn struct_lint(&self,
482                        lint: &'static Lint,
483                        span: Option<MultiSpan>,
484                        msg: &str)
485         -> DiagnosticBuilder<'a>
486     {
487         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
488         lint::struct_lint_level(self.sess, lint, level, src, span, msg)
489     }
490
491     /// Registers the ID provided with the current set of lints stored in
492     /// this context.
493     pub fn register_id(&mut self, id: HirId) {
494         self.id_to_set.insert(id, self.cur);
495     }
496
497     pub fn build(self) -> LintLevelSets {
498         self.sets
499     }
500
501     pub fn build_map(self) -> LintLevelMap {
502         LintLevelMap {
503             sets: self.sets,
504             id_to_set: self.id_to_set,
505         }
506     }
507 }
508
509 pub struct LintLevelMap {
510     sets: LintLevelSets,
511     id_to_set: FxHashMap<HirId, u32>,
512 }
513
514 impl LintLevelMap {
515     /// If the `id` was previously registered with `register_id` when building
516     /// this `LintLevelMap` this returns the corresponding lint level and source
517     /// of the lint level for the lint provided.
518     ///
519     /// If the `id` was not previously registered, returns `None`. If `None` is
520     /// returned then the parent of `id` should be acquired and this function
521     /// should be called again.
522     pub fn level_and_source(&self, lint: &'static Lint, id: HirId, session: &Session)
523         -> Option<(Level, LintSource)>
524     {
525         self.id_to_set.get(&id).map(|idx| {
526             self.sets.get_lint_level(lint, *idx, None, session)
527         })
528     }
529 }
530
531 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
532     #[inline]
533     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
534         let LintLevelMap {
535             ref sets,
536             ref id_to_set,
537         } = *self;
538
539         id_to_set.hash_stable(hcx, hasher);
540
541         let LintLevelSets {
542             ref list,
543             lint_cap,
544         } = *sets;
545
546         lint_cap.hash_stable(hcx, hasher);
547
548         hcx.while_hashing_spans(true, |hcx| {
549             list.len().hash_stable(hcx, hasher);
550
551             // We are working under the assumption here that the list of
552             // lint-sets is built in a deterministic order.
553             for lint_set in list {
554                 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
555
556                 match *lint_set {
557                     LintSet::CommandLine { ref specs } => {
558                         specs.hash_stable(hcx, hasher);
559                     }
560                     LintSet::Node { ref specs, parent } => {
561                         specs.hash_stable(hcx, hasher);
562                         parent.hash_stable(hcx, hasher);
563                     }
564                 }
565             }
566         })
567     }
568 }
569
570 impl<HCX> HashStable<HCX> for LintId {
571     #[inline]
572     fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
573         self.lint_name_raw().hash_stable(hcx, hasher);
574     }
575 }
576
577 impl<HCX> ToStableHashKey<HCX> for LintId {
578     type KeyType = &'static str;
579
580     #[inline]
581     fn to_stable_hash_key(&self, _: &HCX) -> &'static str {
582         self.lint_name_raw()
583     }
584 }