]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/lint.rs
add `#[thread_local]` attribute
[rust.git] / src / librustc / middle / lint.rs
1 // Copyright 2012-2013 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.
4 //
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.
10
11 //! A 'lint' check is a kind of miscellaneous constraint that a user _might_
12 //! want to enforce, but might reasonably want to permit as well, on a
13 //! module-by-module basis. They contrast with static constraints enforced by
14 //! other phases of the compiler, which are generally required to hold in order
15 //! to compile the program at all.
16 //!
17 //! The lint checking is all consolidated into one pass which runs just before
18 //! translation to LLVM bytecode. Throughout compilation, lint warnings can be
19 //! added via the `add_lint` method on the Session structure. This requires a
20 //! span and an id of the node that the lint is being added to. The lint isn't
21 //! actually emitted at that time because it is unknown what the actual lint
22 //! level at that location is.
23 //!
24 //! To actually emit lint warnings/errors, a separate pass is used just before
25 //! translation. A context keeps track of the current state of all lint levels.
26 //! Upon entering a node of the ast which can modify the lint settings, the
27 //! previous lint state is pushed onto a stack and the ast is then recursed
28 //! upon.  As the ast is traversed, this keeps track of the current lint level
29 //! for all lint attributes.
30 //!
31 //! To add a new lint warning, all you need to do is to either invoke `add_lint`
32 //! on the session at the appropriate time, or write a few linting functions and
33 //! modify the Context visitor appropriately. If you're adding lints from the
34 //! Context itself, span_lint should be used instead of add_lint.
35
36 use driver::session;
37 use middle::privacy;
38 use middle::trans::adt; // for `adt::is_ffi_safe`
39 use middle::ty;
40 use middle::pat_util;
41 use metadata::csearch;
42 use util::ppaux::{ty_to_str};
43
44 use std::cmp;
45 use std::hashmap::HashMap;
46 use std::i16;
47 use std::i32;
48 use std::i64;
49 use std::i8;
50 use std::u16;
51 use std::u32;
52 use std::u64;
53 use std::u8;
54 use extra::smallintmap::SmallIntMap;
55 use syntax::ast_map;
56 use syntax::attr;
57 use syntax::attr::{AttrMetaMethods, AttributeMethods};
58 use syntax::codemap::Span;
59 use syntax::codemap;
60 use syntax::parse::token;
61 use syntax::{ast, ast_util, visit};
62 use syntax::visit::Visitor;
63
64 #[deriving(Clone, Eq)]
65 pub enum lint {
66     ctypes,
67     unused_imports,
68     unnecessary_qualification,
69     while_true,
70     path_statement,
71     unrecognized_lint,
72     non_camel_case_types,
73     non_uppercase_statics,
74     non_uppercase_pattern_statics,
75     type_limits,
76     type_overflow,
77     unused_unsafe,
78     unsafe_block,
79     attribute_usage,
80
81     managed_heap_memory,
82     owned_heap_memory,
83     heap_memory,
84
85     unused_variable,
86     dead_assignment,
87     unused_mut,
88     unnecessary_allocation,
89
90     missing_doc,
91     unreachable_code,
92
93     deprecated,
94     experimental,
95     unstable,
96
97     warnings,
98 }
99
100 pub fn level_to_str(lv: level) -> &'static str {
101     match lv {
102       allow => "allow",
103       warn => "warn",
104       deny => "deny",
105       forbid => "forbid"
106     }
107 }
108
109 #[deriving(Clone, Eq, Ord)]
110 pub enum level {
111     allow, warn, deny, forbid
112 }
113
114 #[deriving(Clone, Eq)]
115 pub struct LintSpec {
116     lint: lint,
117     desc: &'static str,
118     default: level
119 }
120
121 impl Ord for LintSpec {
122     fn lt(&self, other: &LintSpec) -> bool { self.default < other.default }
123 }
124
125 pub type LintDict = HashMap<&'static str, LintSpec>;
126
127 #[deriving(Eq)]
128 enum LintSource {
129     Node(Span),
130     Default,
131     CommandLine
132 }
133
134 static lint_table: &'static [(&'static str, LintSpec)] = &[
135     ("ctypes",
136      LintSpec {
137         lint: ctypes,
138         desc: "proper use of std::libc types in foreign modules",
139         default: warn
140      }),
141
142     ("unused_imports",
143      LintSpec {
144         lint: unused_imports,
145         desc: "imports that are never used",
146         default: warn
147      }),
148
149     ("unnecessary_qualification",
150      LintSpec {
151         lint: unnecessary_qualification,
152         desc: "detects unnecessarily qualified names",
153         default: allow
154      }),
155
156     ("while_true",
157      LintSpec {
158         lint: while_true,
159         desc: "suggest using loop { } instead of while(true) { }",
160         default: warn
161      }),
162
163     ("path_statement",
164      LintSpec {
165         lint: path_statement,
166         desc: "path statements with no effect",
167         default: warn
168      }),
169
170     ("unrecognized_lint",
171      LintSpec {
172         lint: unrecognized_lint,
173         desc: "unrecognized lint attribute",
174         default: warn
175      }),
176
177     ("non_camel_case_types",
178      LintSpec {
179         lint: non_camel_case_types,
180         desc: "types, variants and traits should have camel case names",
181         default: allow
182      }),
183
184     ("non_uppercase_statics",
185      LintSpec {
186          lint: non_uppercase_statics,
187          desc: "static constants should have uppercase identifiers",
188          default: allow
189      }),
190
191     ("non_uppercase_pattern_statics",
192      LintSpec {
193          lint: non_uppercase_pattern_statics,
194          desc: "static constants in match patterns should be all caps",
195          default: warn
196      }),
197
198     ("managed_heap_memory",
199      LintSpec {
200         lint: managed_heap_memory,
201         desc: "use of managed (@ type) heap memory",
202         default: allow
203      }),
204
205     ("owned_heap_memory",
206      LintSpec {
207         lint: owned_heap_memory,
208         desc: "use of owned (~ type) heap memory",
209         default: allow
210      }),
211
212     ("heap_memory",
213      LintSpec {
214         lint: heap_memory,
215         desc: "use of any (~ type or @ type) heap memory",
216         default: allow
217      }),
218
219     ("type_limits",
220      LintSpec {
221         lint: type_limits,
222         desc: "comparisons made useless by limits of the types involved",
223         default: warn
224      }),
225
226     ("type_overflow",
227      LintSpec {
228         lint: type_overflow,
229         desc: "literal out of range for its type",
230         default: warn
231      }),
232
233
234     ("unused_unsafe",
235      LintSpec {
236         lint: unused_unsafe,
237         desc: "unnecessary use of an `unsafe` block",
238         default: warn
239     }),
240
241     ("unsafe_block",
242      LintSpec {
243         lint: unsafe_block,
244         desc: "usage of an `unsafe` block",
245         default: allow
246     }),
247
248     ("attribute_usage",
249      LintSpec {
250         lint: attribute_usage,
251         desc: "detects bad use of attributes",
252         default: warn
253     }),
254
255     ("unused_variable",
256      LintSpec {
257         lint: unused_variable,
258         desc: "detect variables which are not used in any way",
259         default: warn
260     }),
261
262     ("dead_assignment",
263      LintSpec {
264         lint: dead_assignment,
265         desc: "detect assignments that will never be read",
266         default: warn
267     }),
268
269     ("unused_mut",
270      LintSpec {
271         lint: unused_mut,
272         desc: "detect mut variables which don't need to be mutable",
273         default: warn
274     }),
275
276     ("unnecessary_allocation",
277      LintSpec {
278         lint: unnecessary_allocation,
279         desc: "detects unnecessary allocations that can be eliminated",
280         default: warn
281     }),
282
283     ("missing_doc",
284      LintSpec {
285         lint: missing_doc,
286         desc: "detects missing documentation for public members",
287         default: allow
288     }),
289
290     ("unreachable_code",
291      LintSpec {
292         lint: unreachable_code,
293         desc: "detects unreachable code",
294         default: warn
295     }),
296
297     ("deprecated",
298      LintSpec {
299         lint: deprecated,
300         desc: "detects use of #[deprecated] items",
301         default: warn
302     }),
303
304     ("experimental",
305      LintSpec {
306         lint: experimental,
307         desc: "detects use of #[experimental] items",
308         default: warn
309     }),
310
311     ("unstable",
312      LintSpec {
313         lint: unstable,
314         desc: "detects use of #[unstable] items (incl. items with no stability attribute)",
315         default: allow
316     }),
317
318     ("warnings",
319      LintSpec {
320         lint: warnings,
321         desc: "mass-change the level for lints which produce warnings",
322         default: warn
323     }),
324 ];
325
326 /*
327   Pass names should not contain a '-', as the compiler normalizes
328   '-' to '_' in command-line flags
329  */
330 pub fn get_lint_dict() -> LintDict {
331     let mut map = HashMap::new();
332     for &(k, v) in lint_table.iter() {
333         map.insert(k, v);
334     }
335     return map;
336 }
337
338 struct Context<'self> {
339     // All known lint modes (string versions)
340     dict: @LintDict,
341     // Current levels of each lint warning
342     cur: SmallIntMap<(level, LintSource)>,
343     // context we're checking in (used to access fields like sess)
344     tcx: ty::ctxt,
345     // Items exported by the crate; used by the missing_doc lint.
346     exported_items: &'self privacy::ExportedItems,
347     // The id of the current `ast::struct_def` being walked.
348     cur_struct_def_id: ast::NodeId,
349     // Whether some ancestor of the current node was marked
350     // #[doc(hidden)].
351     is_doc_hidden: bool,
352
353     // When recursing into an attributed node of the ast which modifies lint
354     // levels, this stack keeps track of the previous lint levels of whatever
355     // was modified.
356     lint_stack: ~[(lint, level, LintSource)],
357
358     // id of the last visited negated expression
359     negated_expr_id: ast::NodeId
360 }
361
362 impl<'self> Context<'self> {
363     fn get_level(&self, lint: lint) -> level {
364         match self.cur.find(&(lint as uint)) {
365           Some(&(lvl, _)) => lvl,
366           None => allow
367         }
368     }
369
370     fn get_source(&self, lint: lint) -> LintSource {
371         match self.cur.find(&(lint as uint)) {
372           Some(&(_, src)) => src,
373           None => Default
374         }
375     }
376
377     fn set_level(&mut self, lint: lint, level: level, src: LintSource) {
378         if level == allow {
379             self.cur.remove(&(lint as uint));
380         } else {
381             self.cur.insert(lint as uint, (level, src));
382         }
383     }
384
385     fn lint_to_str(&self, lint: lint) -> &'static str {
386         for (k, v) in self.dict.iter() {
387             if v.lint == lint {
388                 return *k;
389             }
390         }
391         fail!("unregistered lint {:?}", lint);
392     }
393
394     fn span_lint(&self, lint: lint, span: Span, msg: &str) {
395         let (level, src) = match self.cur.find(&(lint as uint)) {
396             None => { return }
397             Some(&(warn, src)) => (self.get_level(warnings), src),
398             Some(&pair) => pair,
399         };
400         if level == allow { return }
401
402         let mut note = None;
403         let msg = match src {
404             Default => {
405                 format!("{}, \\#[{}({})] on by default", msg,
406                     level_to_str(level), self.lint_to_str(lint))
407             },
408             CommandLine => {
409                 format!("{} [-{} {}]", msg,
410                     match level {
411                         warn => 'W', deny => 'D', forbid => 'F',
412                         allow => fail!()
413                     }, self.lint_to_str(lint).replace("_", "-"))
414             },
415             Node(src) => {
416                 note = Some(src);
417                 msg.to_str()
418             }
419         };
420         match level {
421             warn =>          { self.tcx.sess.span_warn(span, msg); }
422             deny | forbid => { self.tcx.sess.span_err(span, msg);  }
423             allow => fail!(),
424         }
425
426         for &span in note.iter() {
427             self.tcx.sess.span_note(span, "lint level defined here");
428         }
429     }
430
431     /**
432      * Merge the lints specified by any lint attributes into the
433      * current lint context, call the provided function, then reset the
434      * lints in effect to their previous state.
435      */
436     fn with_lint_attrs(&mut self,
437                        attrs: &[ast::Attribute],
438                        f: |&mut Context|) {
439         // Parse all of the lint attributes, and then add them all to the
440         // current dictionary of lint information. Along the way, keep a history
441         // of what we changed so we can roll everything back after invoking the
442         // specified closure
443         let mut pushed = 0u;
444         do each_lint(self.tcx.sess, attrs) |meta, level, lintname| {
445             match self.dict.find_equiv(&lintname) {
446                 None => {
447                     self.span_lint(
448                         unrecognized_lint,
449                         meta.span,
450                         format!("unknown `{}` attribute: `{}`",
451                         level_to_str(level), lintname));
452                 }
453                 Some(lint) => {
454                     let lint = lint.lint;
455                     let now = self.get_level(lint);
456                     if now == forbid && level != forbid {
457                         self.tcx.sess.span_err(meta.span,
458                         format!("{}({}) overruled by outer forbid({})",
459                         level_to_str(level),
460                         lintname, lintname));
461                     } else if now != level {
462                         let src = self.get_source(lint);
463                         self.lint_stack.push((lint, now, src));
464                         pushed += 1;
465                         self.set_level(lint, level, Node(meta.span));
466                     }
467                 }
468             }
469             true
470         };
471
472         let old_is_doc_hidden = self.is_doc_hidden;
473         self.is_doc_hidden = self.is_doc_hidden ||
474             attrs.iter().any(|attr| ("doc" == attr.name() && match attr.meta_item_list()
475                                      { None => false,
476                                        Some(l) => attr::contains_name(l, "hidden") }));
477
478         f(self);
479
480         // rollback
481         self.is_doc_hidden = old_is_doc_hidden;
482         do pushed.times {
483             let (lint, lvl, src) = self.lint_stack.pop();
484             self.set_level(lint, lvl, src);
485         }
486     }
487
488     fn visit_ids(&self, f: |&mut ast_util::IdVisitor<Context>|) {
489         let mut v = ast_util::IdVisitor {
490             operation: self,
491             pass_through_items: false,
492             visited_outermost: false,
493         };
494         f(&mut v);
495     }
496 }
497
498 pub fn each_lint(sess: session::Session,
499                  attrs: &[ast::Attribute],
500                  f: |@ast::MetaItem, level, @str| -> bool)
501                  -> bool {
502     let xs = [allow, warn, deny, forbid];
503     for &level in xs.iter() {
504         let level_name = level_to_str(level);
505         for attr in attrs.iter().filter(|m| level_name == m.name()) {
506             let meta = attr.node.value;
507             let metas = match meta.node {
508                 ast::MetaList(_, ref metas) => metas,
509                 _ => {
510                     sess.span_err(meta.span, "malformed lint attribute");
511                     continue;
512                 }
513             };
514             for meta in metas.iter() {
515                 match meta.node {
516                     ast::MetaWord(lintname) => {
517                         if !f(*meta, level, lintname) {
518                             return false;
519                         }
520                     }
521                     _ => {
522                         sess.span_err(meta.span, "malformed lint attribute");
523                     }
524                 }
525             }
526         }
527     }
528     true
529 }
530
531 fn check_while_true_expr(cx: &Context, e: &ast::Expr) {
532     match e.node {
533         ast::ExprWhile(cond, _) => {
534             match cond.node {
535                 ast::ExprLit(@codemap::Spanned {
536                     node: ast::lit_bool(true), _}) =>
537                 {
538                     cx.span_lint(while_true, e.span,
539                                  "denote infinite loops with loop { ... }");
540                 }
541                 _ => ()
542             }
543         }
544         _ => ()
545     }
546 }
547
548 fn check_type_limits(cx: &Context, e: &ast::Expr) {
549     return match e.node {
550         ast::ExprBinary(_, binop, l, r) => {
551             if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) {
552                 cx.span_lint(type_limits, e.span,
553                              "comparison is useless due to type limits");
554             }
555         },
556         ast::ExprLit(lit) => {
557             match ty::get(ty::expr_ty(cx.tcx, e)).sty {
558                 ty::ty_int(t) => {
559                     let int_type = if t == ast::ty_i {
560                         cx.tcx.sess.targ_cfg.int_type
561                     } else { t };
562                     let (min, max) = int_ty_range(int_type);
563                     let mut lit_val: i64 = match lit.node {
564                         ast::lit_int(v, _) => v,
565                         ast::lit_uint(v, _) => v as i64,
566                         ast::lit_int_unsuffixed(v) => v,
567                         _ => fail!()
568                     };
569                     if cx.negated_expr_id == e.id {
570                         lit_val *= -1;
571                     }
572                     if  lit_val < min || lit_val > max {
573                         cx.span_lint(type_overflow, e.span,
574                                      "literal out of range for its type");
575                     }
576                 },
577                 ty::ty_uint(t) => {
578                     let uint_type = if t == ast::ty_u {
579                         cx.tcx.sess.targ_cfg.uint_type
580                     } else { t };
581                     let (min, max) = uint_ty_range(uint_type);
582                     let lit_val: u64 = match lit.node {
583                         ast::lit_int(v, _) => v as u64,
584                         ast::lit_uint(v, _) => v,
585                         ast::lit_int_unsuffixed(v) => v as u64,
586                         _ => fail!()
587                     };
588                     if  lit_val < min || lit_val > max {
589                         cx.span_lint(type_overflow, e.span,
590                                      "literal out of range for its type");
591                     }
592                 },
593
594                 _ => ()
595             };
596         },
597         _ => ()
598     };
599
600     fn is_valid<T:cmp::Ord>(binop: ast::BinOp, v: T,
601                             min: T, max: T) -> bool {
602         match binop {
603             ast::BiLt => v <= max,
604             ast::BiLe => v < max,
605             ast::BiGt => v >= min,
606             ast::BiGe => v > min,
607             ast::BiEq | ast::BiNe => v >= min && v <= max,
608             _ => fail!()
609         }
610     }
611
612     fn rev_binop(binop: ast::BinOp) -> ast::BinOp {
613         match binop {
614             ast::BiLt => ast::BiGt,
615             ast::BiLe => ast::BiGe,
616             ast::BiGt => ast::BiLt,
617             ast::BiGe => ast::BiLe,
618             _ => binop
619         }
620     }
621
622     // for int & uint, be conservative with the warnings, so that the
623     // warnings are consistent between 32- and 64-bit platforms
624     fn int_ty_range(int_ty: ast::int_ty) -> (i64, i64) {
625         match int_ty {
626             ast::ty_i =>    (i64::min_value,        i64::max_value),
627             ast::ty_i8 =>   (i8::min_value  as i64, i8::max_value  as i64),
628             ast::ty_i16 =>  (i16::min_value as i64, i16::max_value as i64),
629             ast::ty_i32 =>  (i32::min_value as i64, i32::max_value as i64),
630             ast::ty_i64 =>  (i64::min_value,        i64::max_value)
631         }
632     }
633
634     fn uint_ty_range(uint_ty: ast::uint_ty) -> (u64, u64) {
635         match uint_ty {
636             ast::ty_u =>   (u64::min_value,         u64::max_value),
637             ast::ty_u8 =>  (u8::min_value   as u64, u8::max_value   as u64),
638             ast::ty_u16 => (u16::min_value  as u64, u16::max_value  as u64),
639             ast::ty_u32 => (u32::min_value  as u64, u32::max_value  as u64),
640             ast::ty_u64 => (u64::min_value,         u64::max_value)
641         }
642     }
643
644     fn check_limits(tcx: ty::ctxt, binop: ast::BinOp,
645                     l: &ast::Expr, r: &ast::Expr) -> bool {
646         let (lit, expr, swap) = match (&l.node, &r.node) {
647             (&ast::ExprLit(_), _) => (l, r, true),
648             (_, &ast::ExprLit(_)) => (r, l, false),
649             _ => return true
650         };
651         // Normalize the binop so that the literal is always on the RHS in
652         // the comparison
653         let norm_binop = if swap { rev_binop(binop) } else { binop };
654         match ty::get(ty::expr_ty(tcx, expr)).sty {
655             ty::ty_int(int_ty) => {
656                 let (min, max) = int_ty_range(int_ty);
657                 let lit_val: i64 = match lit.node {
658                     ast::ExprLit(li) => match li.node {
659                         ast::lit_int(v, _) => v,
660                         ast::lit_uint(v, _) => v as i64,
661                         ast::lit_int_unsuffixed(v) => v,
662                         _ => return true
663                     },
664                     _ => fail!()
665                 };
666                 is_valid(norm_binop, lit_val, min, max)
667             }
668             ty::ty_uint(uint_ty) => {
669                 let (min, max): (u64, u64) = uint_ty_range(uint_ty);
670                 let lit_val: u64 = match lit.node {
671                     ast::ExprLit(li) => match li.node {
672                         ast::lit_int(v, _) => v as u64,
673                         ast::lit_uint(v, _) => v,
674                         ast::lit_int_unsuffixed(v) => v as u64,
675                         _ => return true
676                     },
677                     _ => fail!()
678                 };
679                 is_valid(norm_binop, lit_val, min, max)
680             }
681             _ => true
682         }
683     }
684
685     fn is_comparison(binop: ast::BinOp) -> bool {
686         match binop {
687             ast::BiEq | ast::BiLt | ast::BiLe |
688             ast::BiNe | ast::BiGe | ast::BiGt => true,
689             _ => false
690         }
691     }
692 }
693
694 fn check_item_ctypes(cx: &Context, it: &ast::item) {
695     fn check_ty(cx: &Context, ty: &ast::Ty) {
696         match ty.node {
697             ast::ty_path(_, _, id) => {
698                 match cx.tcx.def_map.get_copy(&id) {
699                     ast::DefPrimTy(ast::ty_int(ast::ty_i)) => {
700                         cx.span_lint(ctypes, ty.span,
701                                 "found rust type `int` in foreign module, while \
702                                 libc::c_int or libc::c_long should be used");
703                     }
704                     ast::DefPrimTy(ast::ty_uint(ast::ty_u)) => {
705                         cx.span_lint(ctypes, ty.span,
706                                 "found rust type `uint` in foreign module, while \
707                                 libc::c_uint or libc::c_ulong should be used");
708                     }
709                     ast::DefTy(def_id) => {
710                         if !adt::is_ffi_safe(cx.tcx, def_id) {
711                             cx.span_lint(ctypes, ty.span,
712                                          "found enum type without foreign-function-safe \
713                                           representation annotation in foreign module");
714                             // NOTE this message could be more helpful
715                         }
716                     }
717                     _ => ()
718                 }
719             }
720             ast::ty_ptr(ref mt) => { check_ty(cx, mt.ty) }
721             _ => ()
722         }
723     }
724
725     fn check_foreign_fn(cx: &Context, decl: &ast::fn_decl) {
726         for input in decl.inputs.iter() {
727             check_ty(cx, &input.ty);
728         }
729         check_ty(cx, &decl.output)
730     }
731
732     match it.node {
733       ast::item_foreign_mod(ref nmod) if !nmod.abis.is_intrinsic() => {
734         for ni in nmod.items.iter() {
735             match ni.node {
736                 ast::foreign_item_fn(ref decl, _) => {
737                     check_foreign_fn(cx, decl);
738                 }
739                 ast::foreign_item_static(ref t, _) => { check_ty(cx, t); }
740             }
741         }
742       }
743       _ => {/* nothing to do */ }
744     }
745 }
746
747 fn check_heap_type(cx: &Context, span: Span, ty: ty::t) {
748     let xs = [managed_heap_memory, owned_heap_memory, heap_memory];
749     for &lint in xs.iter() {
750         if cx.get_level(lint) == allow { continue }
751
752         let mut n_box = 0;
753         let mut n_uniq = 0;
754         ty::fold_ty(cx.tcx, ty, |t| {
755             match ty::get(t).sty {
756               ty::ty_box(_) => n_box += 1,
757               ty::ty_uniq(_) => n_uniq += 1,
758               _ => ()
759             };
760             t
761         });
762
763         if n_uniq > 0 && lint != managed_heap_memory {
764             let s = ty_to_str(cx.tcx, ty);
765             let m = format!("type uses owned (~ type) pointers: {}", s);
766             cx.span_lint(lint, span, m);
767         }
768
769         if n_box > 0 && lint != owned_heap_memory {
770             let s = ty_to_str(cx.tcx, ty);
771             let m = format!("type uses managed (@ type) pointers: {}", s);
772             cx.span_lint(lint, span, m);
773         }
774     }
775 }
776
777 fn check_heap_item(cx: &Context, it: &ast::item) {
778     match it.node {
779         ast::item_fn(*) |
780         ast::item_ty(*) |
781         ast::item_enum(*) |
782         ast::item_struct(*) => check_heap_type(cx, it.span,
783                                                ty::node_id_to_type(cx.tcx,
784                                                                    it.id)),
785         _ => ()
786     }
787
788     // If it's a struct, we also have to check the fields' types
789     match it.node {
790         ast::item_struct(struct_def, _) => {
791             for struct_field in struct_def.fields.iter() {
792                 check_heap_type(cx, struct_field.span,
793                                 ty::node_id_to_type(cx.tcx,
794                                                     struct_field.node.id));
795             }
796         }
797         _ => ()
798     }
799 }
800
801 static crate_attrs: &'static [&'static str] = &[
802     "crate_type", "link", "feature", "no_uv", "no_main", "no_std",
803     "desc", "comment", "license", "copyright", // not used in rustc now
804 ];
805
806
807 static obsolete_attrs: &'static [(&'static str, &'static str)] = &[
808     ("abi", "Use `extern \"abi\" fn` instead"),
809     ("auto_encode", "Use `#[deriving(Encodable)]` instead"),
810     ("auto_decode", "Use `#[deriving(Decodable)]` instead"),
811     ("fast_ffi", "Remove it"),
812     ("fixed_stack_segment", "Remove it"),
813     ("rust_stack", "Remove it"),
814 ];
815
816 static other_attrs: &'static [&'static str] = &[
817     // item-level
818     "address_insignificant", // can be crate-level too
819     "thread_local", // for statics
820     "allow", "deny", "forbid", "warn", // lint options
821     "deprecated", "experimental", "unstable", "stable", "locked", "frozen", //item stability
822     "crate_map", "cfg", "doc", "export_name", "link_section", "no_freeze",
823     "no_mangle", "no_send", "static_assert", "unsafe_no_drop_flag",
824     "packed", "simd", "repr", "deriving", "unsafe_destructor",
825
826     //mod-level
827     "path", "link_name", "link_args", "nolink", "macro_escape", "no_implicit_prelude",
828
829     // fn-level
830     "test", "bench", "should_fail", "ignore", "inline", "lang", "main", "start",
831     "no_split_stack", "cold",
832
833     // internal attribute: bypass privacy inside items
834     "!resolve_unexported",
835 ];
836
837 fn check_crate_attrs_usage(cx: &Context, attrs: &[ast::Attribute]) {
838
839     for attr in attrs.iter() {
840         let name = attr.node.value.name();
841         let mut iter = crate_attrs.iter().chain(other_attrs.iter());
842         if !iter.any(|other_attr| { name.equiv(other_attr) }) {
843             cx.span_lint(attribute_usage, attr.span, "unknown crate attribute");
844         }
845     }
846 }
847
848 fn check_attrs_usage(cx: &Context, attrs: &[ast::Attribute]) {
849     // check if element has crate-level, obsolete, or any unknown attributes.
850
851     for attr in attrs.iter() {
852         let name = attr.node.value.name();
853         for crate_attr in crate_attrs.iter() {
854             if name.equiv(crate_attr) {
855                 let msg = match attr.node.style {
856                     ast::AttrOuter => "crate-level attribute should be an inner attribute: \
857                                        add semicolon at end",
858                     ast::AttrInner => "crate-level attribute should be in the root module",
859                 };
860                 cx.span_lint(attribute_usage, attr.span, msg);
861                 return;
862             }
863         }
864
865         for &(obs_attr, obs_alter) in obsolete_attrs.iter() {
866             if name.equiv(&obs_attr) {
867                 cx.span_lint(attribute_usage, attr.span,
868                              format!("obsolete attribute: {:s}", obs_alter));
869                 return;
870             }
871         }
872
873         if !other_attrs.iter().any(|other_attr| { name.equiv(other_attr) }) {
874             cx.span_lint(attribute_usage, attr.span, "unknown attribute");
875         }
876     }
877 }
878
879 fn check_heap_expr(cx: &Context, e: &ast::Expr) {
880     let ty = ty::expr_ty(cx.tcx, e);
881     check_heap_type(cx, e.span, ty);
882 }
883
884 fn check_path_statement(cx: &Context, s: &ast::Stmt) {
885     match s.node {
886         ast::StmtSemi(@ast::Expr { node: ast::ExprPath(_), _ }, _) => {
887             cx.span_lint(path_statement, s.span,
888                          "path statement with no effect");
889         }
890         _ => ()
891     }
892 }
893
894 fn check_item_non_camel_case_types(cx: &Context, it: &ast::item) {
895     fn is_camel_case(cx: ty::ctxt, ident: ast::Ident) -> bool {
896         let ident = cx.sess.str_of(ident);
897         assert!(!ident.is_empty());
898         let ident = ident.trim_chars(&'_');
899
900         // start with a non-lowercase letter rather than non-uppercase
901         // ones (some scripts don't have a concept of upper/lowercase)
902         !ident.char_at(0).is_lowercase() &&
903             !ident.contains_char('_')
904     }
905
906     fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) {
907         if !is_camel_case(cx.tcx, ident) {
908             cx.span_lint(
909                 non_camel_case_types, span,
910                 format!("{} `{}` should have a camel case identifier",
911                     sort, cx.tcx.sess.str_of(ident)));
912         }
913     }
914
915     match it.node {
916         ast::item_ty(*) | ast::item_struct(*) => {
917             check_case(cx, "type", it.ident, it.span)
918         }
919         ast::item_trait(*) => {
920             check_case(cx, "trait", it.ident, it.span)
921         }
922         ast::item_enum(ref enum_definition, _) => {
923             check_case(cx, "type", it.ident, it.span);
924             for variant in enum_definition.variants.iter() {
925                 check_case(cx, "variant", variant.node.name, variant.span);
926             }
927         }
928         _ => ()
929     }
930 }
931
932 fn check_item_non_uppercase_statics(cx: &Context, it: &ast::item) {
933     match it.node {
934         // only check static constants
935         ast::item_static(_, ast::MutImmutable, _) => {
936             let s = cx.tcx.sess.str_of(it.ident);
937             // check for lowercase letters rather than non-uppercase
938             // ones (some scripts don't have a concept of
939             // upper/lowercase)
940             if s.chars().any(|c| c.is_lowercase()) {
941                 cx.span_lint(non_uppercase_statics, it.span,
942                              "static constant should have an uppercase identifier");
943             }
944         }
945         _ => {}
946     }
947 }
948
949 fn check_pat_non_uppercase_statics(cx: &Context, p: &ast::Pat) {
950     // Lint for constants that look like binding identifiers (#7526)
951     match (&p.node, cx.tcx.def_map.find(&p.id)) {
952         (&ast::PatIdent(_, ref path, _), Some(&ast::DefStatic(_, false))) => {
953             // last identifier alone is right choice for this lint.
954             let ident = path.segments.last().identifier;
955             let s = cx.tcx.sess.str_of(ident);
956             if s.chars().any(|c| c.is_lowercase()) {
957                 cx.span_lint(non_uppercase_pattern_statics, path.span,
958                              "static constant in pattern should be all caps");
959             }
960         }
961         _ => {}
962     }
963 }
964
965 fn check_unused_unsafe(cx: &Context, e: &ast::Expr) {
966     match e.node {
967         // Don't warn about generated blocks, that'll just pollute the output.
968         ast::ExprBlock(ref blk) => {
969             if blk.rules == ast::UnsafeBlock(ast::UserProvided) &&
970                 !cx.tcx.used_unsafe.contains(&blk.id) {
971                 cx.span_lint(unused_unsafe, blk.span,
972                              "unnecessary `unsafe` block");
973             }
974         }
975         _ => ()
976     }
977 }
978
979 fn check_unsafe_block(cx: &Context, e: &ast::Expr) {
980     match e.node {
981         // Don't warn about generated blocks, that'll just pollute the output.
982         ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => {
983             cx.span_lint(unsafe_block, blk.span, "usage of an `unsafe` block");
984         }
985         _ => ()
986     }
987 }
988
989 fn check_unused_mut_pat(cx: &Context, p: &ast::Pat) {
990     match p.node {
991         ast::PatIdent(ast::BindByValue(ast::MutMutable),
992                       ref path, _) if pat_util::pat_is_binding(cx.tcx.def_map, p)=> {
993             // `let mut _a = 1;` doesn't need a warning.
994             let initial_underscore = match path.segments {
995                 [ast::PathSegment { identifier: id, _ }] => {
996                     cx.tcx.sess.str_of(id).starts_with("_")
997                 }
998                 _ => {
999                     cx.tcx.sess.span_bug(p.span,
1000                                          "mutable binding that doesn't \
1001                                          consist of exactly one segment");
1002                 }
1003             };
1004
1005             if !initial_underscore && !cx.tcx.used_mut_nodes.contains(&p.id) {
1006                 cx.span_lint(unused_mut, p.span,
1007                              "variable does not need to be mutable");
1008             }
1009         }
1010         _ => ()
1011     }
1012 }
1013
1014 fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) {
1015     // Warn if string and vector literals with sigils are immediately borrowed.
1016     // Those can have the sigil removed.
1017     match e.node {
1018         ast::ExprVstore(e2, ast::ExprVstoreUniq) |
1019         ast::ExprVstore(e2, ast::ExprVstoreBox) => {
1020             match e2.node {
1021                 ast::ExprLit(@codemap::Spanned{node: ast::lit_str(*), _}) |
1022                 ast::ExprVec(*) => {}
1023                 _ => return
1024             }
1025         }
1026
1027         _ => return
1028     }
1029
1030     match cx.tcx.adjustments.find_copy(&e.id) {
1031         Some(@ty::AutoDerefRef(ty::AutoDerefRef {
1032             autoref: Some(ty::AutoBorrowVec(*)), _ })) => {
1033             cx.span_lint(unnecessary_allocation, e.span,
1034                          "unnecessary allocation, the sigil can be removed");
1035         }
1036
1037         _ => ()
1038     }
1039 }
1040
1041 fn check_missing_doc_attrs(cx: &Context,
1042                            id: ast::NodeId,
1043                            attrs: &[ast::Attribute],
1044                            sp: Span,
1045                            desc: &'static str) {
1046     // If we're building a test harness, then warning about
1047     // documentation is probably not really relevant right now.
1048     if cx.tcx.sess.opts.test { return }
1049
1050     // `#[doc(hidden)]` disables missing_doc check.
1051     if cx.is_doc_hidden { return }
1052
1053     // Only check publicly-visible items, using the result from the
1054     // privacy pass.
1055     if !cx.exported_items.contains(&id) { return }
1056
1057     if !attrs.iter().any(|a| a.node.is_sugared_doc) {
1058         cx.span_lint(missing_doc, sp,
1059                      format!("missing documentation for {}", desc));
1060     }
1061 }
1062
1063 fn check_missing_doc_item(cx: &mut Context, it: &ast::item) { // XXX doesn't need to be mut
1064     let desc = match it.node {
1065         ast::item_fn(*) => "a function",
1066         ast::item_mod(*) => "a module",
1067         ast::item_enum(*) => "an enum",
1068         ast::item_struct(*) => "a struct",
1069         ast::item_trait(*) => "a trait",
1070         _ => return
1071     };
1072     check_missing_doc_attrs(cx, it.id, it.attrs, it.span, desc);
1073 }
1074
1075 fn check_missing_doc_method(cx: &Context, m: &ast::method) {
1076     let did = ast::DefId {
1077         crate: ast::LOCAL_CRATE,
1078         node: m.id
1079     };
1080     match cx.tcx.methods.find(&did) {
1081         None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"),
1082         Some(md) => {
1083             match md.container {
1084                 // Always check default methods defined on traits.
1085                 ty::TraitContainer(*) => {}
1086                 // For methods defined on impls, it depends on whether
1087                 // it is an implementation for a trait or is a plain
1088                 // impl.
1089                 ty::ImplContainer(cid) => {
1090                     match ty::impl_trait_ref(cx.tcx, cid) {
1091                         Some(*) => return, // impl for trait: don't doc
1092                         None => {} // plain impl: doc according to privacy
1093                     }
1094                 }
1095             }
1096         }
1097     }
1098     check_missing_doc_attrs(cx, m.id, m.attrs, m.span, "a method");
1099 }
1100
1101 fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) {
1102     check_missing_doc_attrs(cx, tm.id, tm.attrs, tm.span, "a type method");
1103 }
1104
1105 fn check_missing_doc_struct_field(cx: &Context, sf: &ast::struct_field) {
1106     match sf.node.kind {
1107         ast::named_field(_, vis) if vis != ast::private =>
1108             check_missing_doc_attrs(cx, cx.cur_struct_def_id, sf.node.attrs,
1109                                     sf.span, "a struct field"),
1110         _ => {}
1111     }
1112 }
1113
1114 fn check_missing_doc_variant(cx: &Context, v: &ast::variant) {
1115     check_missing_doc_attrs(cx, v.node.id, v.node.attrs, v.span, "a variant");
1116 }
1117
1118 /// Checks for use of items with #[deprecated], #[experimental] and
1119 /// #[unstable] (or none of them) attributes.
1120 fn check_stability(cx: &Context, e: &ast::Expr) {
1121     let def = match e.node {
1122         ast::ExprMethodCall(*) |
1123         ast::ExprPath(*) |
1124         ast::ExprStruct(*) => {
1125             match cx.tcx.def_map.find(&e.id) {
1126                 Some(&def) => def,
1127                 None => return
1128             }
1129         }
1130         _ => return
1131     };
1132
1133     let id = ast_util::def_id_of_def(def);
1134
1135     let stability = if ast_util::is_local(id) {
1136         // this crate
1137         match cx.tcx.items.find(&id.node) {
1138             Some(ast_node) => {
1139                 let s = do ast_node.with_attrs |attrs| {
1140                     do attrs.map |a| {
1141                         attr::find_stability(a.iter().map(|a| a.meta()))
1142                     }
1143                 };
1144                 match s {
1145                     Some(s) => s,
1146
1147                     // no possibility of having attributes
1148                     // (e.g. it's a local variable), so just
1149                     // ignore it.
1150                     None => return
1151                 }
1152             }
1153             _ => cx.tcx.sess.bug(format!("handle_def: {:?} not found", id))
1154         }
1155     } else {
1156         // cross-crate
1157
1158         let mut s = None;
1159         // run through all the attributes and take the first
1160         // stability one.
1161         do csearch::get_item_attrs(cx.tcx.cstore, id) |meta_items| {
1162             if s.is_none() {
1163                 s = attr::find_stability(meta_items.move_iter())
1164             }
1165         }
1166         s
1167     };
1168
1169     let (lint, label) = match stability {
1170         // no stability attributes == Unstable
1171         None => (unstable, "unmarked"),
1172         Some(attr::Stability { level: attr::Unstable, _ }) =>
1173                 (unstable, "unstable"),
1174         Some(attr::Stability { level: attr::Experimental, _ }) =>
1175                 (experimental, "experimental"),
1176         Some(attr::Stability { level: attr::Deprecated, _ }) =>
1177                 (deprecated, "deprecated"),
1178         _ => return
1179     };
1180
1181     let msg = match stability {
1182         Some(attr::Stability { text: Some(ref s), _ }) => {
1183             format!("use of {} item: {}", label, *s)
1184         }
1185         _ => format!("use of {} item", label)
1186     };
1187
1188     cx.span_lint(lint, e.span, msg);
1189 }
1190
1191 impl<'self> Visitor<()> for Context<'self> {
1192     fn visit_item(&mut self, it: @ast::item, _: ()) {
1193         do self.with_lint_attrs(it.attrs) |cx| {
1194             check_item_ctypes(cx, it);
1195             check_item_non_camel_case_types(cx, it);
1196             check_item_non_uppercase_statics(cx, it);
1197             check_heap_item(cx, it);
1198             check_missing_doc_item(cx, it);
1199             check_attrs_usage(cx, it.attrs);
1200
1201             do cx.visit_ids |v| {
1202                 v.visit_item(it, ());
1203             }
1204
1205             visit::walk_item(cx, it, ());
1206         }
1207     }
1208
1209     fn visit_foreign_item(&mut self, it: @ast::foreign_item, _: ()) {
1210         do self.with_lint_attrs(it.attrs) |cx| {
1211             check_attrs_usage(cx, it.attrs);
1212             visit::walk_foreign_item(cx, it, ());
1213         }
1214     }
1215
1216     fn visit_view_item(&mut self, i: &ast::view_item, _: ()) {
1217         do self.with_lint_attrs(i.attrs) |cx| {
1218             check_attrs_usage(cx, i.attrs);
1219             visit::walk_view_item(cx, i, ());
1220         }
1221     }
1222
1223     fn visit_pat(&mut self, p: &ast::Pat, _: ()) {
1224         check_pat_non_uppercase_statics(self, p);
1225         check_unused_mut_pat(self, p);
1226
1227         visit::walk_pat(self, p, ());
1228     }
1229
1230     fn visit_expr(&mut self, e: @ast::Expr, _: ()) {
1231         match e.node {
1232             ast::ExprUnary(_, ast::UnNeg, expr) => {
1233                 // propagate negation, if the negation itself isn't negated
1234                 if self.negated_expr_id != e.id {
1235                     self.negated_expr_id = expr.id;
1236                 }
1237             },
1238             ast::ExprParen(expr) => if self.negated_expr_id == e.id {
1239                 self.negated_expr_id = expr.id
1240             },
1241             _ => ()
1242         };
1243
1244         check_while_true_expr(self, e);
1245         check_stability(self, e);
1246         check_unused_unsafe(self, e);
1247         check_unsafe_block(self, e);
1248         check_unnecessary_allocation(self, e);
1249         check_heap_expr(self, e);
1250
1251         check_type_limits(self, e);
1252
1253         visit::walk_expr(self, e, ());
1254     }
1255
1256     fn visit_stmt(&mut self, s: @ast::Stmt, _: ()) {
1257         check_path_statement(self, s);
1258
1259         visit::walk_stmt(self, s, ());
1260     }
1261
1262     fn visit_fn(&mut self, fk: &visit::fn_kind, decl: &ast::fn_decl,
1263                 body: &ast::Block, span: Span, id: ast::NodeId, _: ()) {
1264         let recurse = |this: &mut Context| {
1265             visit::walk_fn(this, fk, decl, body, span, id, ());
1266         };
1267
1268         match *fk {
1269             visit::fk_method(_, _, m) => {
1270                 do self.with_lint_attrs(m.attrs) |cx| {
1271                     check_missing_doc_method(cx, m);
1272                     check_attrs_usage(cx, m.attrs);
1273
1274                     do cx.visit_ids |v| {
1275                         v.visit_fn(fk, decl, body, span, id, ());
1276                     }
1277                     recurse(cx);
1278                 }
1279             }
1280             _ => recurse(self),
1281         }
1282     }
1283
1284
1285     fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) {
1286         do self.with_lint_attrs(t.attrs) |cx| {
1287             check_missing_doc_ty_method(cx, t);
1288             check_attrs_usage(cx, t.attrs);
1289
1290             visit::walk_ty_method(cx, t, ());
1291         }
1292     }
1293
1294     fn visit_struct_def(&mut self,
1295                         s: @ast::struct_def,
1296                         i: ast::Ident,
1297                         g: &ast::Generics,
1298                         id: ast::NodeId,
1299                         _: ()) {
1300         let old_id = self.cur_struct_def_id;
1301         self.cur_struct_def_id = id;
1302         visit::walk_struct_def(self, s, i, g, id, ());
1303         self.cur_struct_def_id = old_id;
1304     }
1305
1306     fn visit_struct_field(&mut self, s: @ast::struct_field, _: ()) {
1307         do self.with_lint_attrs(s.node.attrs) |cx| {
1308             check_missing_doc_struct_field(cx, s);
1309             check_attrs_usage(cx, s.node.attrs);
1310
1311             visit::walk_struct_field(cx, s, ());
1312         }
1313     }
1314
1315     fn visit_variant(&mut self, v: &ast::variant, g: &ast::Generics, _: ()) {
1316         do self.with_lint_attrs(v.node.attrs) |cx| {
1317             check_missing_doc_variant(cx, v);
1318             check_attrs_usage(cx, v.node.attrs);
1319
1320             visit::walk_variant(cx, v, g, ());
1321         }
1322     }
1323 }
1324
1325 impl<'self> ast_util::IdVisitingOperation for Context<'self> {
1326     fn visit_id(&self, id: ast::NodeId) {
1327         match self.tcx.sess.lints.pop(&id) {
1328             None => {}
1329             Some(l) => {
1330                 for (lint, span, msg) in l.move_iter() {
1331                     self.span_lint(lint, span, msg)
1332                 }
1333             }
1334         }
1335     }
1336 }
1337
1338 pub fn check_crate(tcx: ty::ctxt,
1339                    exported_items: &privacy::ExportedItems,
1340                    crate: &ast::Crate) {
1341     let mut cx = Context {
1342         dict: @get_lint_dict(),
1343         cur: SmallIntMap::new(),
1344         tcx: tcx,
1345         exported_items: exported_items,
1346         cur_struct_def_id: -1,
1347         is_doc_hidden: false,
1348         lint_stack: ~[],
1349         negated_expr_id: -1
1350     };
1351
1352     // Install default lint levels, followed by the command line levels, and
1353     // then actually visit the whole crate.
1354     for (_, spec) in cx.dict.iter() {
1355         cx.set_level(spec.lint, spec.default, Default);
1356     }
1357     for &(lint, level) in tcx.sess.opts.lint_opts.iter() {
1358         cx.set_level(lint, level, CommandLine);
1359     }
1360     do cx.with_lint_attrs(crate.attrs) |cx| {
1361         do cx.visit_ids |v| {
1362             v.visited_outermost = true;
1363             visit::walk_crate(v, crate, ());
1364         }
1365
1366         check_crate_attrs_usage(cx, crate.attrs);
1367
1368         visit::walk_crate(cx, crate, ());
1369     }
1370
1371     // If we missed any lints added to the session, then there's a bug somewhere
1372     // in the iteration code.
1373     for (id, v) in tcx.sess.lints.iter() {
1374         for &(lint, span, ref msg) in v.iter() {
1375             tcx.sess.span_bug(span, format!("unprocessed lint {:?} at {}: {}",
1376                                             lint,
1377                                             ast_map::node_id_to_str(tcx.items,
1378                                                 *id,
1379                                                 token::get_ident_interner()),
1380                                             *msg))
1381         }
1382     }
1383
1384     tcx.sess.abort_if_errors();
1385 }