]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | //! Implementation of lint checking. |
2 | //! | |
3 | //! The lint checking is mostly consolidated into one pass which runs | |
4 | //! after all other analyses. Throughout compilation, lint warnings | |
5 | //! can be added via the `add_lint` method on the Session structure. This | |
6 | //! requires a span and an ID of the node that the lint is being added to. The | |
7 | //! lint isn't actually emitted at that time because it is unknown what the | |
8 | //! actual lint level at that location is. | |
9 | //! | |
10 | //! To actually emit lint warnings/errors, a separate pass is used. | |
11 | //! A context keeps track of the current state of all lint levels. | |
12 | //! Upon entering a node of the ast which can modify the lint settings, the | |
13 | //! previous lint state is pushed onto a stack and the ast is then recursed | |
14 | //! upon. As the ast is traversed, this keeps track of the current lint level | |
15 | //! for all lint attributes. | |
16 | ||
17 | use crate::context::{EarlyContext, LintContext, LintStore}; | |
18 | use crate::passes::{EarlyLintPass, EarlyLintPassObject}; | |
74b04a01 XL |
19 | use rustc_ast::ast; |
20 | use rustc_ast::visit as ast_visit; | |
dfeec247 XL |
21 | use rustc_session::lint::{LintBuffer, LintPass}; |
22 | use rustc_session::Session; | |
23 | use rustc_span::Span; | |
dfeec247 XL |
24 | |
25 | use log::debug; | |
26 | use std::slice; | |
27 | ||
28 | macro_rules! run_early_pass { ($cx:expr, $f:ident, $($args:expr),*) => ({ | |
29 | $cx.pass.$f(&$cx.context, $($args),*); | |
30 | }) } | |
31 | ||
32 | struct EarlyContextAndPass<'a, T: EarlyLintPass> { | |
33 | context: EarlyContext<'a>, | |
34 | pass: T, | |
35 | } | |
36 | ||
37 | impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { | |
38 | fn check_id(&mut self, id: ast::NodeId) { | |
39 | for early_lint in self.context.buffered.take(id) { | |
74b04a01 XL |
40 | let rustc_session::lint::BufferedEarlyLint { |
41 | span, | |
42 | msg, | |
43 | node_id: _, | |
44 | lint_id, | |
45 | diagnostic, | |
46 | } = early_lint; | |
47 | self.context.lookup_with_diagnostics( | |
48 | lint_id.lint, | |
49 | Some(span), | |
50 | |lint| lint.build(&msg).emit(), | |
51 | diagnostic, | |
dfeec247 XL |
52 | ); |
53 | } | |
54 | } | |
55 | ||
56 | /// Merge the lints specified by any lint attributes into the | |
57 | /// current lint context, call the provided function, then reset the | |
58 | /// lints in effect to their previous state. | |
59 | fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F) | |
60 | where | |
61 | F: FnOnce(&mut Self), | |
62 | { | |
63 | let push = self.context.builder.push(attrs, &self.context.lint_store); | |
64 | self.check_id(id); | |
65 | self.enter_attrs(attrs); | |
66 | f(self); | |
67 | self.exit_attrs(attrs); | |
68 | self.context.builder.pop(push); | |
69 | } | |
70 | ||
71 | fn enter_attrs(&mut self, attrs: &'a [ast::Attribute]) { | |
72 | debug!("early context: enter_attrs({:?})", attrs); | |
73 | run_early_pass!(self, enter_lint_attrs, attrs); | |
74 | } | |
75 | ||
76 | fn exit_attrs(&mut self, attrs: &'a [ast::Attribute]) { | |
77 | debug!("early context: exit_attrs({:?})", attrs); | |
78 | run_early_pass!(self, exit_lint_attrs, attrs); | |
79 | } | |
80 | } | |
81 | ||
82 | impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> { | |
83 | fn visit_param(&mut self, param: &'a ast::Param) { | |
84 | self.with_lint_attrs(param.id, ¶m.attrs, |cx| { | |
85 | run_early_pass!(cx, check_param, param); | |
86 | ast_visit::walk_param(cx, param); | |
87 | }); | |
88 | } | |
89 | ||
90 | fn visit_item(&mut self, it: &'a ast::Item) { | |
91 | self.with_lint_attrs(it.id, &it.attrs, |cx| { | |
92 | run_early_pass!(cx, check_item, it); | |
93 | ast_visit::walk_item(cx, it); | |
94 | run_early_pass!(cx, check_item_post, it); | |
95 | }) | |
96 | } | |
97 | ||
98 | fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) { | |
99 | self.with_lint_attrs(it.id, &it.attrs, |cx| { | |
100 | run_early_pass!(cx, check_foreign_item, it); | |
101 | ast_visit::walk_foreign_item(cx, it); | |
102 | run_early_pass!(cx, check_foreign_item_post, it); | |
103 | }) | |
104 | } | |
105 | ||
106 | fn visit_pat(&mut self, p: &'a ast::Pat) { | |
107 | run_early_pass!(self, check_pat, p); | |
108 | self.check_id(p.id); | |
109 | ast_visit::walk_pat(self, p); | |
110 | run_early_pass!(self, check_pat_post, p); | |
111 | } | |
112 | ||
113 | fn visit_expr(&mut self, e: &'a ast::Expr) { | |
114 | self.with_lint_attrs(e.id, &e.attrs, |cx| { | |
115 | run_early_pass!(cx, check_expr, e); | |
116 | ast_visit::walk_expr(cx, e); | |
117 | }) | |
118 | } | |
119 | ||
120 | fn visit_stmt(&mut self, s: &'a ast::Stmt) { | |
121 | run_early_pass!(self, check_stmt, s); | |
122 | self.check_id(s.id); | |
123 | ast_visit::walk_stmt(self, s); | |
124 | } | |
125 | ||
74b04a01 XL |
126 | fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) { |
127 | run_early_pass!(self, check_fn, fk, span, id); | |
dfeec247 | 128 | self.check_id(id); |
74b04a01 XL |
129 | ast_visit::walk_fn(self, fk, span); |
130 | run_early_pass!(self, check_fn_post, fk, span, id); | |
dfeec247 XL |
131 | } |
132 | ||
133 | fn visit_variant_data(&mut self, s: &'a ast::VariantData) { | |
134 | run_early_pass!(self, check_struct_def, s); | |
135 | if let Some(ctor_hir_id) = s.ctor_id() { | |
136 | self.check_id(ctor_hir_id); | |
137 | } | |
138 | ast_visit::walk_struct_def(self, s); | |
139 | run_early_pass!(self, check_struct_def_post, s); | |
140 | } | |
141 | ||
142 | fn visit_struct_field(&mut self, s: &'a ast::StructField) { | |
143 | self.with_lint_attrs(s.id, &s.attrs, |cx| { | |
144 | run_early_pass!(cx, check_struct_field, s); | |
145 | ast_visit::walk_struct_field(cx, s); | |
146 | }) | |
147 | } | |
148 | ||
149 | fn visit_variant(&mut self, v: &'a ast::Variant) { | |
150 | self.with_lint_attrs(v.id, &v.attrs, |cx| { | |
151 | run_early_pass!(cx, check_variant, v); | |
152 | ast_visit::walk_variant(cx, v); | |
153 | run_early_pass!(cx, check_variant_post, v); | |
154 | }) | |
155 | } | |
156 | ||
157 | fn visit_ty(&mut self, t: &'a ast::Ty) { | |
158 | run_early_pass!(self, check_ty, t); | |
159 | self.check_id(t.id); | |
160 | ast_visit::walk_ty(self, t); | |
161 | } | |
162 | ||
163 | fn visit_ident(&mut self, ident: ast::Ident) { | |
164 | run_early_pass!(self, check_ident, ident); | |
165 | } | |
166 | ||
167 | fn visit_mod(&mut self, m: &'a ast::Mod, s: Span, _a: &[ast::Attribute], n: ast::NodeId) { | |
168 | run_early_pass!(self, check_mod, m, s, n); | |
169 | self.check_id(n); | |
170 | ast_visit::walk_mod(self, m); | |
171 | run_early_pass!(self, check_mod_post, m, s, n); | |
172 | } | |
173 | ||
174 | fn visit_local(&mut self, l: &'a ast::Local) { | |
175 | self.with_lint_attrs(l.id, &l.attrs, |cx| { | |
176 | run_early_pass!(cx, check_local, l); | |
177 | ast_visit::walk_local(cx, l); | |
178 | }) | |
179 | } | |
180 | ||
181 | fn visit_block(&mut self, b: &'a ast::Block) { | |
182 | run_early_pass!(self, check_block, b); | |
183 | self.check_id(b.id); | |
184 | ast_visit::walk_block(self, b); | |
185 | run_early_pass!(self, check_block_post, b); | |
186 | } | |
187 | ||
188 | fn visit_arm(&mut self, a: &'a ast::Arm) { | |
189 | run_early_pass!(self, check_arm, a); | |
190 | ast_visit::walk_arm(self, a); | |
191 | } | |
192 | ||
193 | fn visit_expr_post(&mut self, e: &'a ast::Expr) { | |
194 | run_early_pass!(self, check_expr_post, e); | |
195 | } | |
196 | ||
197 | fn visit_generic_param(&mut self, param: &'a ast::GenericParam) { | |
198 | run_early_pass!(self, check_generic_param, param); | |
199 | ast_visit::walk_generic_param(self, param); | |
200 | } | |
201 | ||
202 | fn visit_generics(&mut self, g: &'a ast::Generics) { | |
203 | run_early_pass!(self, check_generics, g); | |
204 | ast_visit::walk_generics(self, g); | |
205 | } | |
206 | ||
207 | fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) { | |
208 | run_early_pass!(self, check_where_predicate, p); | |
209 | ast_visit::walk_where_predicate(self, p); | |
210 | } | |
211 | ||
212 | fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) { | |
213 | run_early_pass!(self, check_poly_trait_ref, t, m); | |
214 | ast_visit::walk_poly_trait_ref(self, t, m); | |
215 | } | |
216 | ||
74b04a01 XL |
217 | fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) { |
218 | self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt { | |
219 | ast_visit::AssocCtxt::Trait => { | |
220 | run_early_pass!(cx, check_trait_item, item); | |
221 | ast_visit::walk_assoc_item(cx, item, ctxt); | |
222 | run_early_pass!(cx, check_trait_item_post, item); | |
223 | } | |
224 | ast_visit::AssocCtxt::Impl => { | |
225 | run_early_pass!(cx, check_impl_item, item); | |
226 | ast_visit::walk_assoc_item(cx, item, ctxt); | |
227 | run_early_pass!(cx, check_impl_item_post, item); | |
228 | } | |
dfeec247 XL |
229 | }); |
230 | } | |
231 | ||
232 | fn visit_lifetime(&mut self, lt: &'a ast::Lifetime) { | |
233 | run_early_pass!(self, check_lifetime, lt); | |
234 | self.check_id(lt.id); | |
235 | } | |
236 | ||
237 | fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) { | |
238 | run_early_pass!(self, check_path, p, id); | |
239 | self.check_id(id); | |
240 | ast_visit::walk_path(self, p); | |
241 | } | |
242 | ||
243 | fn visit_attribute(&mut self, attr: &'a ast::Attribute) { | |
244 | run_early_pass!(self, check_attribute, attr); | |
245 | } | |
246 | ||
247 | fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) { | |
248 | run_early_pass!(self, check_mac_def, mac, id); | |
249 | self.check_id(id); | |
250 | } | |
251 | ||
252 | fn visit_mac(&mut self, mac: &'a ast::Mac) { | |
253 | // FIXME(#54110): So, this setup isn't really right. I think | |
74b04a01 | 254 | // that (a) the librustc_ast visitor ought to be doing this as |
dfeec247 XL |
255 | // part of `walk_mac`, and (b) we should be calling |
256 | // `visit_path`, *but* that would require a `NodeId`, and I | |
257 | // want to get #53686 fixed quickly. -nmatsakis | |
258 | ast_visit::walk_path(self, &mac.path); | |
259 | ||
260 | run_early_pass!(self, check_mac, mac); | |
261 | } | |
262 | } | |
263 | ||
264 | struct EarlyLintPassObjects<'a> { | |
265 | lints: &'a mut [EarlyLintPassObject], | |
266 | } | |
267 | ||
268 | #[allow(rustc::lint_pass_impl_without_macro)] | |
269 | impl LintPass for EarlyLintPassObjects<'_> { | |
270 | fn name(&self) -> &'static str { | |
271 | panic!() | |
272 | } | |
273 | } | |
274 | ||
275 | macro_rules! expand_early_lint_pass_impl_methods { | |
276 | ([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( | |
277 | $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { | |
278 | for obj in self.lints.iter_mut() { | |
279 | obj.$name(context, $($param),*); | |
280 | } | |
281 | })* | |
282 | ) | |
283 | } | |
284 | ||
285 | macro_rules! early_lint_pass_impl { | |
286 | ([], [$($methods:tt)*]) => ( | |
287 | impl EarlyLintPass for EarlyLintPassObjects<'_> { | |
288 | expand_early_lint_pass_impl_methods!([$($methods)*]); | |
289 | } | |
290 | ) | |
291 | } | |
292 | ||
293 | crate::early_lint_methods!(early_lint_pass_impl, []); | |
294 | ||
295 | fn early_lint_crate<T: EarlyLintPass>( | |
296 | sess: &Session, | |
297 | lint_store: &LintStore, | |
298 | krate: &ast::Crate, | |
299 | pass: T, | |
300 | buffered: LintBuffer, | |
301 | warn_about_weird_lints: bool, | |
302 | ) -> LintBuffer { | |
303 | let mut cx = EarlyContextAndPass { | |
304 | context: EarlyContext::new(sess, lint_store, krate, buffered, warn_about_weird_lints), | |
305 | pass, | |
306 | }; | |
307 | ||
308 | // Visit the whole crate. | |
309 | cx.with_lint_attrs(ast::CRATE_NODE_ID, &krate.attrs, |cx| { | |
310 | // since the root module isn't visited as an item (because it isn't an | |
311 | // item), warn for it here. | |
312 | run_early_pass!(cx, check_crate, krate); | |
313 | ||
314 | ast_visit::walk_crate(cx, krate); | |
315 | ||
316 | run_early_pass!(cx, check_crate_post, krate); | |
317 | }); | |
318 | cx.context.buffered | |
319 | } | |
320 | ||
321 | pub fn check_ast_crate<T: EarlyLintPass>( | |
322 | sess: &Session, | |
323 | lint_store: &LintStore, | |
324 | krate: &ast::Crate, | |
325 | pre_expansion: bool, | |
326 | lint_buffer: Option<LintBuffer>, | |
327 | builtin_lints: T, | |
328 | ) { | |
329 | let mut passes: Vec<_> = if pre_expansion { | |
330 | lint_store.pre_expansion_passes.iter().map(|p| (p)()).collect() | |
331 | } else { | |
332 | lint_store.early_passes.iter().map(|p| (p)()).collect() | |
333 | }; | |
334 | let mut buffered = lint_buffer.unwrap_or_default(); | |
335 | ||
336 | if !sess.opts.debugging_opts.no_interleave_lints { | |
337 | buffered = | |
338 | early_lint_crate(sess, lint_store, krate, builtin_lints, buffered, pre_expansion); | |
339 | ||
340 | if !passes.is_empty() { | |
341 | buffered = early_lint_crate( | |
342 | sess, | |
343 | lint_store, | |
344 | krate, | |
345 | EarlyLintPassObjects { lints: &mut passes[..] }, | |
346 | buffered, | |
347 | pre_expansion, | |
348 | ); | |
349 | } | |
350 | } else { | |
351 | for pass in &mut passes { | |
74b04a01 XL |
352 | buffered = |
353 | sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| { | |
dfeec247 XL |
354 | early_lint_crate( |
355 | sess, | |
356 | lint_store, | |
357 | krate, | |
358 | EarlyLintPassObjects { lints: slice::from_mut(pass) }, | |
359 | buffered, | |
360 | pre_expansion, | |
361 | ) | |
362 | }); | |
363 | } | |
364 | } | |
365 | ||
366 | // All of the buffered lints should have been emitted at this point. | |
367 | // If not, that means that we somehow buffered a lint for a node id | |
368 | // that was not lint-checked (perhaps it doesn't exist?). This is a bug. | |
369 | // | |
370 | // Rustdoc runs everybody-loops before the early lints and removes | |
371 | // function bodies, so it's totally possible for linted | |
372 | // node ids to not exist (e.g., macros defined within functions for the | |
373 | // unused_macro lint) anymore. So we only run this check | |
374 | // when we're not in rustdoc mode. (see issue #47639) | |
375 | if !sess.opts.actually_rustdoc { | |
376 | for (_id, lints) in buffered.map { | |
377 | for early_lint in lints { | |
378 | sess.delay_span_bug(early_lint.span, "failed to process buffered lint here"); | |
379 | } | |
380 | } | |
381 | } | |
382 | } |