]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/functions.rs
implemented `let_underscore` lint
[rust.git] / clippy_lints / src / functions.rs
1 use crate::utils::{
2     attrs::is_proc_macro, is_must_use_ty, iter_input_pats, match_def_path, must_use_attr, qpath_res, return_ty,
3     snippet, snippet_opt, span_help_and_lint, span_lint, span_lint_and_then, trait_ref_of_method,
4     type_is_unsafe_function,
5 };
6 use matches::matches;
7 use rustc::hir::{self, def::Res, def_id::DefId, intravisit};
8 use rustc::impl_lint_pass;
9 use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
10 use rustc::ty::{self, Ty};
11 use rustc_data_structures::fx::FxHashSet;
12 use rustc_errors::Applicability;
13 use rustc_session::declare_tool_lint;
14 use rustc_target::spec::abi::Abi;
15 use syntax::ast::Attribute;
16 use syntax::source_map::Span;
17
18 declare_clippy_lint! {
19     /// **What it does:** Checks for functions with too many parameters.
20     ///
21     /// **Why is this bad?** Functions with lots of parameters are considered bad
22     /// style and reduce readability (“what does the 5th parameter mean?”). Consider
23     /// grouping some parameters into a new type.
24     ///
25     /// **Known problems:** None.
26     ///
27     /// **Example:**
28     /// ```rust
29     /// # struct Color;
30     /// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
31     ///     // ..
32     /// }
33     /// ```
34     pub TOO_MANY_ARGUMENTS,
35     complexity,
36     "functions with too many arguments"
37 }
38
39 declare_clippy_lint! {
40     /// **What it does:** Checks for functions with a large amount of lines.
41     ///
42     /// **Why is this bad?** Functions with a lot of lines are harder to understand
43     /// due to having to look at a larger amount of code to understand what the
44     /// function is doing. Consider splitting the body of the function into
45     /// multiple functions.
46     ///
47     /// **Known problems:** None.
48     ///
49     /// **Example:**
50     /// ``` rust
51     /// fn im_too_long() {
52     /// println!("");
53     /// // ... 100 more LoC
54     /// println!("");
55     /// }
56     /// ```
57     pub TOO_MANY_LINES,
58     pedantic,
59     "functions with too many lines"
60 }
61
62 declare_clippy_lint! {
63     /// **What it does:** Checks for public functions that dereference raw pointer
64     /// arguments but are not marked unsafe.
65     ///
66     /// **Why is this bad?** The function should probably be marked `unsafe`, since
67     /// for an arbitrary raw pointer, there is no way of telling for sure if it is
68     /// valid.
69     ///
70     /// **Known problems:**
71     ///
72     /// * It does not check functions recursively so if the pointer is passed to a
73     /// private non-`unsafe` function which does the dereferencing, the lint won't
74     /// trigger.
75     /// * It only checks for arguments whose type are raw pointers, not raw pointers
76     /// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
77     /// `some_argument.get_raw_ptr()`).
78     ///
79     /// **Example:**
80     /// ```rust
81     /// pub fn foo(x: *const u8) {
82     ///     println!("{}", unsafe { *x });
83     /// }
84     /// ```
85     pub NOT_UNSAFE_PTR_ARG_DEREF,
86     correctness,
87     "public functions dereferencing raw pointer arguments but not marked `unsafe`"
88 }
89
90 declare_clippy_lint! {
91     /// **What it does:** Checks for a [`#[must_use]`] attribute on
92     /// unit-returning functions and methods.
93     ///
94     /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
95     ///
96     /// **Why is this bad?** Unit values are useless. The attribute is likely
97     /// a remnant of a refactoring that removed the return type.
98     ///
99     /// **Known problems:** None.
100     ///
101     /// **Examples:**
102     /// ```rust
103     /// #[must_use]
104     /// fn useless() { }
105     /// ```
106     pub MUST_USE_UNIT,
107     style,
108     "`#[must_use]` attribute on a unit-returning function / method"
109 }
110
111 declare_clippy_lint! {
112     /// **What it does:** Checks for a [`#[must_use]`] attribute without
113     /// further information on functions and methods that return a type already
114     /// marked as `#[must_use]`.
115     ///
116     /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
117     ///
118     /// **Why is this bad?** The attribute isn't needed. Not using the result
119     /// will already be reported. Alternatively, one can add some text to the
120     /// attribute to improve the lint message.
121     ///
122     /// **Known problems:** None.
123     ///
124     /// **Examples:**
125     /// ```rust
126     /// #[must_use]
127     /// fn double_must_use() -> Result<(), ()> {
128     ///     unimplemented!();
129     /// }
130     /// ```
131     pub DOUBLE_MUST_USE,
132     style,
133     "`#[must_use]` attribute on a `#[must_use]`-returning function / method"
134 }
135
136 declare_clippy_lint! {
137     /// **What it does:** Checks for public functions that have no
138     /// [`#[must_use]`] attribute, but return something not already marked
139     /// must-use, have no mutable arg and mutate no statics.
140     ///
141     /// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
142     ///
143     /// **Why is this bad?** Not bad at all, this lint just shows places where
144     /// you could add the attribute.
145     ///
146     /// **Known problems:** The lint only checks the arguments for mutable
147     /// types without looking if they are actually changed. On the other hand,
148     /// it also ignores a broad range of potentially interesting side effects,
149     /// because we cannot decide whether the programmer intends the function to
150     /// be called for the side effect or the result. Expect many false
151     /// positives. At least we don't lint if the result type is unit or already
152     /// `#[must_use]`.
153     ///
154     /// **Examples:**
155     /// ```rust
156     /// // this could be annotated with `#[must_use]`.
157     /// fn id<T>(t: T) -> T { t }
158     /// ```
159     pub MUST_USE_CANDIDATE,
160     pedantic,
161     "function or method that could take a `#[must_use]` attribute"
162 }
163
164 #[derive(Copy, Clone)]
165 pub struct Functions {
166     threshold: u64,
167     max_lines: u64,
168 }
169
170 impl Functions {
171     pub fn new(threshold: u64, max_lines: u64) -> Self {
172         Self { threshold, max_lines }
173     }
174 }
175
176 impl_lint_pass!(Functions => [
177     TOO_MANY_ARGUMENTS,
178     TOO_MANY_LINES,
179     NOT_UNSAFE_PTR_ARG_DEREF,
180     MUST_USE_UNIT,
181     DOUBLE_MUST_USE,
182     MUST_USE_CANDIDATE,
183 ]);
184
185 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Functions {
186     fn check_fn(
187         &mut self,
188         cx: &LateContext<'a, 'tcx>,
189         kind: intravisit::FnKind<'tcx>,
190         decl: &'tcx hir::FnDecl,
191         body: &'tcx hir::Body<'_>,
192         span: Span,
193         hir_id: hir::HirId,
194     ) {
195         let is_impl = if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
196             matches!(item.kind, hir::ItemKind::Impl(_, _, _, _, Some(_), _, _))
197         } else {
198             false
199         };
200
201         let unsafety = match kind {
202             hir::intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _, _) => unsafety,
203             hir::intravisit::FnKind::Method(_, sig, _, _) => sig.header.unsafety,
204             hir::intravisit::FnKind::Closure(_) => return,
205         };
206
207         // don't warn for implementations, it's not their fault
208         if !is_impl {
209             // don't lint extern functions decls, it's not their fault either
210             match kind {
211                 hir::intravisit::FnKind::Method(
212                     _,
213                     &hir::FnSig {
214                         header: hir::FnHeader { abi: Abi::Rust, .. },
215                         ..
216                     },
217                     _,
218                     _,
219                 )
220                 | hir::intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _, _) => {
221                     self.check_arg_number(cx, decl, span.with_hi(decl.output.span().hi()))
222                 },
223                 _ => {},
224             }
225         }
226
227         Self::check_raw_ptr(cx, unsafety, decl, body, hir_id);
228         self.check_line_number(cx, span, body);
229     }
230
231     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
232         let attr = must_use_attr(&item.attrs);
233         if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
234             if let Some(attr) = attr {
235                 let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
236                 check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
237                 return;
238             }
239             if cx.access_levels.is_exported(item.hir_id) && !is_proc_macro(&item.attrs) {
240                 check_must_use_candidate(
241                     cx,
242                     &sig.decl,
243                     cx.tcx.hir().body(*body_id),
244                     item.span,
245                     item.hir_id,
246                     item.span.with_hi(sig.decl.output.span().hi()),
247                     "this function could have a `#[must_use]` attribute",
248                 );
249             }
250         }
251     }
252
253     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) {
254         if let hir::ImplItemKind::Method(ref sig, ref body_id) = item.kind {
255             let attr = must_use_attr(&item.attrs);
256             if let Some(attr) = attr {
257                 let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
258                 check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
259             } else if cx.access_levels.is_exported(item.hir_id)
260                 && !is_proc_macro(&item.attrs)
261                 && trait_ref_of_method(cx, item.hir_id).is_none()
262             {
263                 check_must_use_candidate(
264                     cx,
265                     &sig.decl,
266                     cx.tcx.hir().body(*body_id),
267                     item.span,
268                     item.hir_id,
269                     item.span.with_hi(sig.decl.output.span().hi()),
270                     "this method could have a `#[must_use]` attribute",
271                 );
272             }
273         }
274     }
275
276     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) {
277         if let hir::TraitItemKind::Method(ref sig, ref eid) = item.kind {
278             // don't lint extern functions decls, it's not their fault
279             if sig.header.abi == Abi::Rust {
280                 self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
281             }
282
283             let attr = must_use_attr(&item.attrs);
284             if let Some(attr) = attr {
285                 let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
286                 check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
287             }
288             if let hir::TraitMethod::Provided(eid) = *eid {
289                 let body = cx.tcx.hir().body(eid);
290                 Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id);
291
292                 if attr.is_none() && cx.access_levels.is_exported(item.hir_id) && !is_proc_macro(&item.attrs) {
293                     check_must_use_candidate(
294                         cx,
295                         &sig.decl,
296                         body,
297                         item.span,
298                         item.hir_id,
299                         item.span.with_hi(sig.decl.output.span().hi()),
300                         "this method could have a `#[must_use]` attribute",
301                     );
302                 }
303             }
304         }
305     }
306 }
307
308 impl<'a, 'tcx> Functions {
309     fn check_arg_number(self, cx: &LateContext<'_, '_>, decl: &hir::FnDecl, fn_span: Span) {
310         let args = decl.inputs.len() as u64;
311         if args > self.threshold {
312             span_lint(
313                 cx,
314                 TOO_MANY_ARGUMENTS,
315                 fn_span,
316                 &format!("this function has too many arguments ({}/{})", args, self.threshold),
317             );
318         }
319     }
320
321     fn check_line_number(self, cx: &LateContext<'_, '_>, span: Span, body: &'tcx hir::Body<'_>) {
322         if in_external_macro(cx.sess(), span) {
323             return;
324         }
325
326         let code_snippet = snippet(cx, body.value.span, "..");
327         let mut line_count: u64 = 0;
328         let mut in_comment = false;
329         let mut code_in_line;
330
331         // Skip the surrounding function decl.
332         let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
333         let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
334         let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
335
336         for mut line in function_lines {
337             code_in_line = false;
338             loop {
339                 line = line.trim_start();
340                 if line.is_empty() {
341                     break;
342                 }
343                 if in_comment {
344                     match line.find("*/") {
345                         Some(i) => {
346                             line = &line[i + 2..];
347                             in_comment = false;
348                             continue;
349                         },
350                         None => break,
351                     }
352                 } else {
353                     let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
354                     let single_idx = line.find("//").unwrap_or_else(|| line.len());
355                     code_in_line |= multi_idx > 0 && single_idx > 0;
356                     // Implies multi_idx is below line.len()
357                     if multi_idx < single_idx {
358                         line = &line[multi_idx + 2..];
359                         in_comment = true;
360                         continue;
361                     }
362                     break;
363                 }
364             }
365             if code_in_line {
366                 line_count += 1;
367             }
368         }
369
370         if line_count > self.max_lines {
371             span_lint(cx, TOO_MANY_LINES, span, "This function has a large number of lines.")
372         }
373     }
374
375     fn check_raw_ptr(
376         cx: &LateContext<'a, 'tcx>,
377         unsafety: hir::Unsafety,
378         decl: &'tcx hir::FnDecl,
379         body: &'tcx hir::Body<'_>,
380         hir_id: hir::HirId,
381     ) {
382         let expr = &body.value;
383         if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
384             let raw_ptrs = iter_input_pats(decl, body)
385                 .zip(decl.inputs.iter())
386                 .filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
387                 .collect::<FxHashSet<_>>();
388
389             if !raw_ptrs.is_empty() {
390                 let tables = cx.tcx.body_tables(body.id());
391                 let mut v = DerefVisitor {
392                     cx,
393                     ptrs: raw_ptrs,
394                     tables,
395                 };
396
397                 hir::intravisit::walk_expr(&mut v, expr);
398             }
399         }
400     }
401 }
402
403 fn check_needless_must_use(
404     cx: &LateContext<'_, '_>,
405     decl: &hir::FnDecl,
406     item_id: hir::HirId,
407     item_span: Span,
408     fn_header_span: Span,
409     attr: &Attribute,
410 ) {
411     if in_external_macro(cx.sess(), item_span) {
412         return;
413     }
414     if returns_unit(decl) {
415         span_lint_and_then(
416             cx,
417             MUST_USE_UNIT,
418             fn_header_span,
419             "this unit-returning function has a `#[must_use]` attribute",
420             |db| {
421                 db.span_suggestion(
422                     attr.span,
423                     "remove the attribute",
424                     "".into(),
425                     Applicability::MachineApplicable,
426                 );
427             },
428         );
429     } else if !attr.is_value_str() && is_must_use_ty(cx, return_ty(cx, item_id)) {
430         span_help_and_lint(
431             cx,
432             DOUBLE_MUST_USE,
433             fn_header_span,
434             "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
435             "either add some descriptive text or remove the attribute",
436         );
437     }
438 }
439
440 fn check_must_use_candidate<'a, 'tcx>(
441     cx: &LateContext<'a, 'tcx>,
442     decl: &'tcx hir::FnDecl,
443     body: &'tcx hir::Body<'_>,
444     item_span: Span,
445     item_id: hir::HirId,
446     fn_span: Span,
447     msg: &str,
448 ) {
449     if has_mutable_arg(cx, body)
450         || mutates_static(cx, body)
451         || in_external_macro(cx.sess(), item_span)
452         || returns_unit(decl)
453         || !cx.access_levels.is_exported(item_id)
454         || is_must_use_ty(cx, return_ty(cx, item_id))
455     {
456         return;
457     }
458     span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |db| {
459         if let Some(snippet) = snippet_opt(cx, fn_span) {
460             db.span_suggestion(
461                 fn_span,
462                 "add the attribute",
463                 format!("#[must_use] {}", snippet),
464                 Applicability::MachineApplicable,
465             );
466         }
467     });
468 }
469
470 fn returns_unit(decl: &hir::FnDecl) -> bool {
471     match decl.output {
472         hir::FunctionRetTy::DefaultReturn(_) => true,
473         hir::FunctionRetTy::Return(ref ty) => match ty.kind {
474             hir::TyKind::Tup(ref tys) => tys.is_empty(),
475             hir::TyKind::Never => true,
476             _ => false,
477         },
478     }
479 }
480
481 fn has_mutable_arg(cx: &LateContext<'_, '_>, body: &hir::Body<'_>) -> bool {
482     let mut tys = FxHashSet::default();
483     body.params.iter().any(|param| is_mutable_pat(cx, &param.pat, &mut tys))
484 }
485
486 fn is_mutable_pat(cx: &LateContext<'_, '_>, pat: &hir::Pat, tys: &mut FxHashSet<DefId>) -> bool {
487     if let hir::PatKind::Wild = pat.kind {
488         return false; // ignore `_` patterns
489     }
490     let def_id = pat.hir_id.owner_def_id();
491     if cx.tcx.has_typeck_tables(def_id) {
492         is_mutable_ty(cx, &cx.tcx.typeck_tables_of(def_id).pat_ty(pat), pat.span, tys)
493     } else {
494         false
495     }
496 }
497
498 static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
499
500 fn is_mutable_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet<DefId>) -> bool {
501     use ty::TyKind::*;
502     match ty.kind {
503         // primitive types are never mutable
504         Bool | Char | Int(_) | Uint(_) | Float(_) | Str => false,
505         Adt(ref adt, ref substs) => {
506             tys.insert(adt.did) && !ty.is_freeze(cx.tcx, cx.param_env, span)
507                 || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
508                     && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
509         },
510         Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
511         Array(ty, _) | Slice(ty) => is_mutable_ty(cx, ty, span, tys),
512         RawPtr(ty::TypeAndMut { ty, mutbl }) | Ref(_, ty, mutbl) => {
513             mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
514         },
515         // calling something constitutes a side effect, so return true on all callables
516         // also never calls need not be used, so return true for them, too
517         _ => true,
518     }
519 }
520
521 fn raw_ptr_arg(arg: &hir::Param, ty: &hir::Ty) -> Option<hir::HirId> {
522     if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
523         Some(id)
524     } else {
525         None
526     }
527 }
528
529 struct DerefVisitor<'a, 'tcx> {
530     cx: &'a LateContext<'a, 'tcx>,
531     ptrs: FxHashSet<hir::HirId>,
532     tables: &'a ty::TypeckTables<'tcx>,
533 }
534
535 impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
536     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
537         match expr.kind {
538             hir::ExprKind::Call(ref f, ref args) => {
539                 let ty = self.tables.expr_ty(f);
540
541                 if type_is_unsafe_function(self.cx, ty) {
542                     for arg in args {
543                         self.check_arg(arg);
544                     }
545                 }
546             },
547             hir::ExprKind::MethodCall(_, _, ref args) => {
548                 let def_id = self.tables.type_dependent_def_id(expr.hir_id).unwrap();
549                 let base_type = self.cx.tcx.type_of(def_id);
550
551                 if type_is_unsafe_function(self.cx, base_type) {
552                     for arg in args {
553                         self.check_arg(arg);
554                     }
555                 }
556             },
557             hir::ExprKind::Unary(hir::UnDeref, ref ptr) => self.check_arg(ptr),
558             _ => (),
559         }
560
561         intravisit::walk_expr(self, expr);
562     }
563
564     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
565         intravisit::NestedVisitorMap::None
566     }
567 }
568
569 impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
570     fn check_arg(&self, ptr: &hir::Expr) {
571         if let hir::ExprKind::Path(ref qpath) = ptr.kind {
572             if let Res::Local(id) = qpath_res(self.cx, qpath, ptr.hir_id) {
573                 if self.ptrs.contains(&id) {
574                     span_lint(
575                         self.cx,
576                         NOT_UNSAFE_PTR_ARG_DEREF,
577                         ptr.span,
578                         "this public function dereferences a raw pointer but is not marked `unsafe`",
579                     );
580                 }
581             }
582         }
583     }
584 }
585
586 struct StaticMutVisitor<'a, 'tcx> {
587     cx: &'a LateContext<'a, 'tcx>,
588     mutates_static: bool,
589 }
590
591 impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
592     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
593         use hir::ExprKind::*;
594
595         if self.mutates_static {
596             return;
597         }
598         match expr.kind {
599             Call(_, ref args) | MethodCall(_, _, ref args) => {
600                 let mut tys = FxHashSet::default();
601                 for arg in args {
602                     let def_id = arg.hir_id.owner_def_id();
603                     if self.cx.tcx.has_typeck_tables(def_id)
604                         && is_mutable_ty(
605                             self.cx,
606                             self.cx.tcx.typeck_tables_of(def_id).expr_ty(arg),
607                             arg.span,
608                             &mut tys,
609                         )
610                         && is_mutated_static(self.cx, arg)
611                     {
612                         self.mutates_static = true;
613                         return;
614                     }
615                     tys.clear();
616                 }
617             },
618             Assign(ref target, _) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => {
619                 self.mutates_static |= is_mutated_static(self.cx, target)
620             },
621             _ => {},
622         }
623     }
624
625     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
626         intravisit::NestedVisitorMap::None
627     }
628 }
629
630 fn is_mutated_static(cx: &LateContext<'_, '_>, e: &hir::Expr) -> bool {
631     use hir::ExprKind::*;
632
633     match e.kind {
634         Path(ref qpath) => {
635             if let Res::Local(_) = qpath_res(cx, qpath, e.hir_id) {
636                 false
637             } else {
638                 true
639             }
640         },
641         Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(cx, inner),
642         _ => false,
643     }
644 }
645
646 fn mutates_static<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, body: &'tcx hir::Body<'_>) -> bool {
647     let mut v = StaticMutVisitor {
648         cx,
649         mutates_static: false,
650     };
651     intravisit::walk_expr(&mut v, &body.value);
652     v.mutates_static
653 }