1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Implementation of lint checking.
13 //! The lint checking is mostly consolidated into one pass which runs just
14 //! before translation to LLVM bytecode. Throughout compilation, lint warnings
15 //! can be added via the `add_lint` method on the Session structure. This
16 //! requires a span and an id of the node that the lint is being added to. The
17 //! lint isn't actually emitted at that time because it is unknown what the
18 //! actual lint level at that location is.
20 //! To actually emit lint warnings/errors, a separate pass is used just before
21 //! translation. A context keeps track of the current state of all lint levels.
22 //! Upon entering a node of the ast which can modify the lint settings, the
23 //! previous lint state is pushed onto a stack and the ast is then recursed
24 //! upon. As the ast is traversed, this keeps track of the current lint level
25 //! for all lint attributes.
26 use self::TargetLint
::*;
28 use middle
::privacy
::ExportedItems
;
29 use middle
::ty
::{self, Ty}
;
30 use session
::{early_error, Session}
;
31 use lint
::{Level, LevelSource, Lint, LintId, LintArray, LintPass, LintPassObject}
;
32 use lint
::{Default, CommandLine, Node, Allow, Warn, Deny, Forbid}
;
34 use util
::nodemap
::FnvHashMap
;
36 use std
::cell
::RefCell
;
38 use syntax
::ast_util
::IdVisitingOperation
;
39 use syntax
::attr
::AttrMetaMethods
;
41 use syntax
::codemap
::Span
;
42 use syntax
::visit
::{Visitor, FnKind}
;
43 use syntax
::parse
::token
::InternedString
;
44 use syntax
::{ast, ast_util, visit}
;
46 /// Information about the registered lints.
48 /// This is basically the subset of `Context` that we can
49 /// build early in the compile pipeline.
50 pub struct LintStore
{
51 /// Registered lints. The bool is true if the lint was
52 /// added by a plugin.
53 lints
: Vec
<(&'
static Lint
, bool
)>,
55 /// Trait objects for each lint pass.
56 /// This is only `None` while iterating over the objects. See the definition
58 passes
: Option
<Vec
<LintPassObject
>>,
60 /// Lints indexed by name.
61 by_name
: FnvHashMap
<String
, TargetLint
>,
63 /// Current levels of each lint, and where they were set.
64 levels
: FnvHashMap
<LintId
, LevelSource
>,
66 /// Map of registered lint groups to what lints they expand to. The bool
67 /// is true if the lint group was added by a plugin.
68 lint_groups
: FnvHashMap
<&'
static str, (Vec
<LintId
>, bool
)>,
71 /// The targed of the `by_name` map, which accounts for renaming/deprecation.
73 /// A direct lint target
76 /// Temporary renaming, used for easing migration pain; see #16545
77 Renamed(String
, LintId
),
81 fn get_level_source(&self, lint
: LintId
) -> LevelSource
{
82 match self.levels
.get(&lint
) {
84 None
=> (Allow
, Default
),
88 fn set_level(&mut self, lint
: LintId
, lvlsrc
: LevelSource
) {
89 if lvlsrc
.0 == Allow
{
90 self.levels
.remove(&lint
);
92 self.levels
.insert(lint
, lvlsrc
);
96 pub fn new() -> LintStore
{
100 by_name
: FnvHashMap(),
101 levels
: FnvHashMap(),
102 lint_groups
: FnvHashMap(),
106 pub fn get_lints
<'t
>(&'t
self) -> &'t
[(&'
static Lint
, bool
)] {
110 pub fn get_lint_groups
<'t
>(&'t
self) -> Vec
<(&'
static str, Vec
<LintId
>, bool
)> {
111 self.lint_groups
.iter().map(|(k
, v
)| (*k
,
116 pub fn register_pass(&mut self, sess
: Option
<&Session
>,
117 from_plugin
: bool
, pass
: LintPassObject
) {
118 for &lint
in pass
.get_lints() {
119 self.lints
.push((*lint
, from_plugin
));
121 let id
= LintId
::of(*lint
);
122 if self.by_name
.insert(lint
.name_lower(), Id(id
)).is_some() {
123 let msg
= format
!("duplicate specification of lint {}", lint
.name_lower());
124 match (sess
, from_plugin
) {
125 // We load builtin lints first, so a duplicate is a compiler bug.
126 // Use early_error when handling -W help with no crate.
127 (None
, _
) => early_error(&msg
[..]),
128 (Some(sess
), false) => sess
.bug(&msg
[..]),
130 // A duplicate name from a plugin is a user error.
131 (Some(sess
), true) => sess
.err(&msg
[..]),
135 if lint
.default_level
!= Allow
{
136 self.levels
.insert(id
, (lint
.default_level
, Default
));
139 self.passes
.as_mut().unwrap().push(pass
);
142 pub fn register_group(&mut self, sess
: Option
<&Session
>,
143 from_plugin
: bool
, name
: &'
static str,
145 let new
= self.lint_groups
.insert(name
, (to
, from_plugin
)).is_none();
148 let msg
= format
!("duplicate specification of lint group {}", name
);
149 match (sess
, from_plugin
) {
150 // We load builtin lints first, so a duplicate is a compiler bug.
151 // Use early_error when handling -W help with no crate.
152 (None
, _
) => early_error(&msg
[..]),
153 (Some(sess
), false) => sess
.bug(&msg
[..]),
155 // A duplicate name from a plugin is a user error.
156 (Some(sess
), true) => sess
.err(&msg
[..]),
161 pub fn register_renamed(&mut self, old_name
: &str, new_name
: &str) {
162 let target
= match self.by_name
.get(new_name
) {
163 Some(&Id(lint_id
)) => lint_id
.clone(),
164 _
=> panic
!("invalid lint renaming of {} to {}", old_name
, new_name
)
166 self.by_name
.insert(old_name
.to_string(), Renamed(new_name
.to_string(), target
));
169 #[allow(unused_variables)]
170 fn find_lint(&self, lint_name
: &str, sess
: &Session
, span
: Option
<Span
>)
173 match self.by_name
.get(lint_name
) {
174 Some(&Id(lint_id
)) => Some(lint_id
),
175 Some(&Renamed(ref new_name
, lint_id
)) => {
176 let warning
= format
!("lint {} has been renamed to {}",
177 lint_name
, new_name
);
179 Some(span
) => sess
.span_warn(span
, &warning
[..]),
180 None
=> sess
.warn(&warning
[..]),
188 pub fn process_command_line(&mut self, sess
: &Session
) {
189 for &(ref lint_name
, level
) in &sess
.opts
.lint_opts
{
190 match self.find_lint(&lint_name
[..], sess
, None
) {
191 Some(lint_id
) => self.set_level(lint_id
, (level
, CommandLine
)),
193 match self.lint_groups
.iter().map(|(&x
, pair
)| (x
, pair
.0.clone()))
194 .collect
::<FnvHashMap
<&'
static str,
196 .get(&lint_name
[..]) {
199 .map(|lint_id
: &LintId
|
200 self.set_level(*lint_id
, (level
, CommandLine
)))
201 .collect
::<Vec
<()>>();
203 None
=> sess
.err(&format
!("unknown {} flag: {}",
204 level
.as_str(), lint_name
)),
212 /// Context for lint checking.
213 pub struct Context
<'a
, 'tcx
: 'a
> {
214 /// Type context we're checking in.
215 pub tcx
: &'a ty
::ctxt
<'tcx
>,
217 /// The crate being checked.
218 pub krate
: &'a ast
::Crate
,
220 /// Items exported from the crate being checked.
221 pub exported_items
: &'a ExportedItems
,
223 /// The store of registered lints.
226 /// When recursing into an attributed node of the ast which modifies lint
227 /// levels, this stack keeps track of the previous lint levels of whatever
229 level_stack
: Vec
<(LintId
, LevelSource
)>,
231 /// Level of lints for certain NodeIds, stored here because the body of
232 /// the lint needs to run in trans.
233 node_levels
: RefCell
<FnvHashMap
<(ast
::NodeId
, LintId
), LevelSource
>>,
236 /// Convenience macro for calling a `LintPass` method on every pass in the context.
237 macro_rules
! run_lints
{ ($cx
:expr
, $f
:ident
, $
($args
:expr
),*) => ({
238 // Move the vector of passes out of `$cx` so that we can
239 // iterate over it mutably while passing `$cx` to the methods.
240 let mut passes
= $cx
.lints
.passes
.take().unwrap();
241 for obj
in &mut passes
{
242 obj
.$
f($cx
, $
($args
),*);
244 $cx
.lints
.passes
= Some(passes
);
247 /// Parse the lint attributes into a vector, with `Err`s for malformed lint
248 /// attributes. Writing this as an iterator is an enormous mess.
249 pub fn gather_attrs(attrs
: &[ast
::Attribute
])
250 -> Vec
<Result
<(InternedString
, Level
, Span
), Span
>> {
251 let mut out
= vec
!();
253 let level
= match Level
::from_str(&attr
.name()) {
258 attr
::mark_used(attr
);
260 let meta
= &attr
.node
.value
;
261 let metas
= match meta
.node
{
262 ast
::MetaList(_
, ref metas
) => metas
,
264 out
.push(Err(meta
.span
));
270 out
.push(match meta
.node
{
271 ast
::MetaWord(ref lint_name
) => Ok((lint_name
.clone(), level
, meta
.span
)),
279 /// Emit a lint as a warning or an error (or not at all)
280 /// according to `level`.
282 /// This lives outside of `Context` so it can be used by checks
283 /// in trans that run after the main lint pass is finished. Most
284 /// lints elsewhere in the compiler should call
285 /// `Session::add_lint()` instead.
286 pub fn raw_emit_lint(sess
: &Session
, lint
: &'
static Lint
,
287 lvlsrc
: LevelSource
, span
: Option
<Span
>, msg
: &str) {
288 let (mut level
, source
) = lvlsrc
;
289 if level
== Allow { return }
291 let name
= lint
.name_lower();
293 let msg
= match source
{
295 format
!("{}, #[{}({})] on by default", msg
,
296 level
.as_str(), name
)
299 format
!("{} [-{} {}]", msg
,
301 Warn
=> 'W'
, Deny
=> 'D'
, Forbid
=> 'F'
,
303 }, name
.replace("_", "-"))
311 // For purposes of printing, we can treat forbid as deny.
312 if level
== Forbid { level = Deny; }
314 match (level
, span
) {
315 (Warn
, Some(sp
)) => sess
.span_warn(sp
, &msg
[..]),
316 (Warn
, None
) => sess
.warn(&msg
[..]),
317 (Deny
, Some(sp
)) => sess
.span_err(sp
, &msg
[..]),
318 (Deny
, None
) => sess
.err(&msg
[..]),
319 _
=> sess
.bug("impossible level in raw_emit_lint"),
322 if let Some(span
) = def
{
323 sess
.span_note(span
, "lint level defined here");
327 impl<'a
, 'tcx
> Context
<'a
, 'tcx
> {
328 fn new(tcx
: &'a ty
::ctxt
<'tcx
>,
329 krate
: &'a ast
::Crate
,
330 exported_items
: &'a ExportedItems
) -> Context
<'a
, 'tcx
> {
331 // We want to own the lint store, so move it out of the session.
332 let lint_store
= mem
::replace(&mut *tcx
.sess
.lint_store
.borrow_mut(),
338 exported_items
: exported_items
,
341 node_levels
: RefCell
::new(FnvHashMap()),
345 /// Get the overall compiler `Session` object.
346 pub fn sess(&'a
self) -> &'a Session
{
350 /// Get the level of `lint` at the current position of the lint
352 pub fn current_level(&self, lint
: &'
static Lint
) -> Level
{
353 self.lints
.levels
.get(&LintId
::of(lint
)).map_or(Allow
, |&(lvl
, _
)| lvl
)
356 fn lookup_and_emit(&self, lint
: &'
static Lint
, span
: Option
<Span
>, msg
: &str) {
357 let (level
, src
) = match self.lints
.levels
.get(&LintId
::of(lint
)) {
359 Some(&(Warn
, src
)) => {
360 let lint_id
= LintId
::of(builtin
::WARNINGS
);
361 (self.lints
.get_level_source(lint_id
).0, src
)
366 raw_emit_lint(&self.tcx
.sess
, lint
, (level
, src
), span
, msg
);
369 /// Emit a lint at the appropriate level, with no associated span.
370 pub fn lint(&self, lint
: &'
static Lint
, msg
: &str) {
371 self.lookup_and_emit(lint
, None
, msg
);
374 /// Emit a lint at the appropriate level, for a particular span.
375 pub fn span_lint(&self, lint
: &'
static Lint
, span
: Span
, msg
: &str) {
376 self.lookup_and_emit(lint
, Some(span
), msg
);
379 /// Merge the lints specified by any lint attributes into the
380 /// current lint context, call the provided function, then reset the
381 /// lints in effect to their previous state.
382 fn with_lint_attrs
<F
>(&mut self,
383 attrs
: &[ast
::Attribute
],
385 F
: FnOnce(&mut Context
),
387 // Parse all of the lint attributes, and then add them all to the
388 // current dictionary of lint information. Along the way, keep a history
389 // of what we changed so we can roll everything back after invoking the
393 for result
in gather_attrs(attrs
) {
394 let v
= match result
{
396 self.tcx
.sess
.span_err(span
, "malformed lint attribute");
399 Ok((lint_name
, level
, span
)) => {
400 match self.lints
.find_lint(&lint_name
, &self.tcx
.sess
, Some(span
)) {
401 Some(lint_id
) => vec
![(lint_id
, level
, span
)],
403 match self.lints
.lint_groups
.get(&lint_name
[..]) {
404 Some(&(ref v
, _
)) => v
.iter()
405 .map(|lint_id
: &LintId
|
406 (*lint_id
, level
, span
))
409 self.span_lint(builtin
::UNKNOWN_LINTS
, span
,
410 &format
!("unknown `{}` attribute: `{}`",
411 level
.as_str(), lint_name
));
420 for (lint_id
, level
, span
) in v
{
421 let now
= self.lints
.get_level_source(lint_id
).0;
422 if now
== Forbid
&& level
!= Forbid
{
423 let lint_name
= lint_id
.as_str();
424 self.tcx
.sess
.span_err(span
,
425 &format
!("{}({}) overruled by outer forbid({})",
426 level
.as_str(), lint_name
,
428 } else if now
!= level
{
429 let src
= self.lints
.get_level_source(lint_id
).1;
430 self.level_stack
.push((lint_id
, (now
, src
)));
432 self.lints
.set_level(lint_id
, (level
, Node(span
)));
437 run_lints
!(self, enter_lint_attrs
, attrs
);
439 run_lints
!(self, exit_lint_attrs
, attrs
);
443 let (lint
, lvlsrc
) = self.level_stack
.pop().unwrap();
444 self.lints
.set_level(lint
, lvlsrc
);
448 fn visit_ids
<F
>(&mut self, f
: F
) where
449 F
: FnOnce(&mut ast_util
::IdVisitor
<Context
>)
451 let mut v
= ast_util
::IdVisitor
{
453 pass_through_items
: false,
454 visited_outermost
: false,
460 impl<'a
, 'tcx
, 'v
> Visitor
<'v
> for Context
<'a
, 'tcx
> {
461 fn visit_item(&mut self, it
: &ast
::Item
) {
462 self.with_lint_attrs(&it
.attrs
, |cx
| {
463 run_lints
!(cx
, check_item
, it
);
464 cx
.visit_ids(|v
| v
.visit_item(it
));
465 visit
::walk_item(cx
, it
);
469 fn visit_foreign_item(&mut self, it
: &ast
::ForeignItem
) {
470 self.with_lint_attrs(&it
.attrs
, |cx
| {
471 run_lints
!(cx
, check_foreign_item
, it
);
472 visit
::walk_foreign_item(cx
, it
);
476 fn visit_pat(&mut self, p
: &ast
::Pat
) {
477 run_lints
!(self, check_pat
, p
);
478 visit
::walk_pat(self, p
);
481 fn visit_expr(&mut self, e
: &ast
::Expr
) {
482 run_lints
!(self, check_expr
, e
);
483 visit
::walk_expr(self, e
);
486 fn visit_stmt(&mut self, s
: &ast
::Stmt
) {
487 run_lints
!(self, check_stmt
, s
);
488 visit
::walk_stmt(self, s
);
491 fn visit_fn(&mut self, fk
: FnKind
<'v
>, decl
: &'v ast
::FnDecl
,
492 body
: &'v ast
::Block
, span
: Span
, id
: ast
::NodeId
) {
493 run_lints
!(self, check_fn
, fk
, decl
, body
, span
, id
);
494 visit
::walk_fn(self, fk
, decl
, body
, span
);
497 fn visit_struct_def(&mut self,
502 run_lints
!(self, check_struct_def
, s
, ident
, g
, id
);
503 visit
::walk_struct_def(self, s
);
504 run_lints
!(self, check_struct_def_post
, s
, ident
, g
, id
);
507 fn visit_struct_field(&mut self, s
: &ast
::StructField
) {
508 self.with_lint_attrs(&s
.node
.attrs
, |cx
| {
509 run_lints
!(cx
, check_struct_field
, s
);
510 visit
::walk_struct_field(cx
, s
);
514 fn visit_variant(&mut self, v
: &ast
::Variant
, g
: &ast
::Generics
) {
515 self.with_lint_attrs(&v
.node
.attrs
, |cx
| {
516 run_lints
!(cx
, check_variant
, v
, g
);
517 visit
::walk_variant(cx
, v
, g
);
518 run_lints
!(cx
, check_variant_post
, v
, g
);
522 fn visit_ty(&mut self, t
: &ast
::Ty
) {
523 run_lints
!(self, check_ty
, t
);
524 visit
::walk_ty(self, t
);
527 fn visit_ident(&mut self, sp
: Span
, id
: ast
::Ident
) {
528 run_lints
!(self, check_ident
, sp
, id
);
531 fn visit_mod(&mut self, m
: &ast
::Mod
, s
: Span
, n
: ast
::NodeId
) {
532 run_lints
!(self, check_mod
, m
, s
, n
);
533 visit
::walk_mod(self, m
);
536 fn visit_local(&mut self, l
: &ast
::Local
) {
537 run_lints
!(self, check_local
, l
);
538 visit
::walk_local(self, l
);
541 fn visit_block(&mut self, b
: &ast
::Block
) {
542 run_lints
!(self, check_block
, b
);
543 visit
::walk_block(self, b
);
546 fn visit_arm(&mut self, a
: &ast
::Arm
) {
547 run_lints
!(self, check_arm
, a
);
548 visit
::walk_arm(self, a
);
551 fn visit_decl(&mut self, d
: &ast
::Decl
) {
552 run_lints
!(self, check_decl
, d
);
553 visit
::walk_decl(self, d
);
556 fn visit_expr_post(&mut self, e
: &ast
::Expr
) {
557 run_lints
!(self, check_expr_post
, e
);
560 fn visit_generics(&mut self, g
: &ast
::Generics
) {
561 run_lints
!(self, check_generics
, g
);
562 visit
::walk_generics(self, g
);
565 fn visit_trait_item(&mut self, trait_item
: &ast
::TraitItem
) {
566 self.with_lint_attrs(&trait_item
.attrs
, |cx
| {
567 run_lints
!(cx
, check_trait_item
, trait_item
);
568 cx
.visit_ids(|v
| v
.visit_trait_item(trait_item
));
569 visit
::walk_trait_item(cx
, trait_item
);
573 fn visit_impl_item(&mut self, impl_item
: &ast
::ImplItem
) {
574 self.with_lint_attrs(&impl_item
.attrs
, |cx
| {
575 run_lints
!(cx
, check_impl_item
, impl_item
);
576 cx
.visit_ids(|v
| v
.visit_impl_item(impl_item
));
577 visit
::walk_impl_item(cx
, impl_item
);
581 fn visit_opt_lifetime_ref(&mut self, sp
: Span
, lt
: &Option
<ast
::Lifetime
>) {
582 run_lints
!(self, check_opt_lifetime_ref
, sp
, lt
);
585 fn visit_lifetime_ref(&mut self, lt
: &ast
::Lifetime
) {
586 run_lints
!(self, check_lifetime_ref
, lt
);
589 fn visit_lifetime_def(&mut self, lt
: &ast
::LifetimeDef
) {
590 run_lints
!(self, check_lifetime_def
, lt
);
593 fn visit_explicit_self(&mut self, es
: &ast
::ExplicitSelf
) {
594 run_lints
!(self, check_explicit_self
, es
);
595 visit
::walk_explicit_self(self, es
);
598 fn visit_mac(&mut self, mac
: &ast
::Mac
) {
599 run_lints
!(self, check_mac
, mac
);
600 visit
::walk_mac(self, mac
);
603 fn visit_path(&mut self, p
: &ast
::Path
, id
: ast
::NodeId
) {
604 run_lints
!(self, check_path
, p
, id
);
605 visit
::walk_path(self, p
);
608 fn visit_attribute(&mut self, attr
: &ast
::Attribute
) {
609 run_lints
!(self, check_attribute
, attr
);
613 // Output any lints that were previously added to the session.
614 impl<'a
, 'tcx
> IdVisitingOperation
for Context
<'a
, 'tcx
> {
615 fn visit_id(&mut self, id
: ast
::NodeId
) {
616 match self.tcx
.sess
.lints
.borrow_mut().remove(&id
) {
619 for (lint_id
, span
, msg
) in lints
{
620 self.span_lint(lint_id
.lint
, span
, &msg
[..])
627 // This lint pass is defined here because it touches parts of the `Context`
628 // that we don't want to expose. It records the lint level at certain AST
629 // nodes, so that the variant size difference check in trans can call
632 pub struct GatherNodeLevels
;
634 impl LintPass
for GatherNodeLevels
{
635 fn get_lints(&self) -> LintArray
{
639 fn check_item(&mut self, cx
: &Context
, it
: &ast
::Item
) {
641 ast
::ItemEnum(..) => {
642 let lint_id
= LintId
::of(builtin
::VARIANT_SIZE_DIFFERENCES
);
643 let lvlsrc
= cx
.lints
.get_level_source(lint_id
);
645 (lvl
, _
) if lvl
!= Allow
=> {
646 cx
.node_levels
.borrow_mut()
647 .insert((it
.id
, lint_id
), lvlsrc
);
657 /// Perform lint checking on a crate.
659 /// Consumes the `lint_store` field of the `Session`.
660 pub fn check_crate(tcx
: &ty
::ctxt
,
661 exported_items
: &ExportedItems
) {
663 let krate
= tcx
.map
.krate();
664 let mut cx
= Context
::new(tcx
, krate
, exported_items
);
666 // Visit the whole crate.
667 cx
.with_lint_attrs(&krate
.attrs
, |cx
| {
668 cx
.visit_id(ast
::CRATE_NODE_ID
);
670 v
.visited_outermost
= true;
671 visit
::walk_crate(v
, krate
);
674 // since the root module isn't visited as an item (because it isn't an
675 // item), warn for it here.
676 run_lints
!(cx
, check_crate
, krate
);
678 visit
::walk_crate(cx
, krate
);
681 // If we missed any lints added to the session, then there's a bug somewhere
682 // in the iteration code.
683 for (id
, v
) in tcx
.sess
.lints
.borrow().iter() {
684 for &(lint
, span
, ref msg
) in v
{
685 tcx
.sess
.span_bug(span
,
686 &format
!("unprocessed lint {} at {}: {}",
687 lint
.as_str(), tcx
.map
.node_to_string(*id
), *msg
))
691 *tcx
.node_lint_levels
.borrow_mut() = cx
.node_levels
.into_inner();