]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::{higher, is_direct_expn_of, span_lint}; |
2 | use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; | |
3 | use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability}; | |
4 | use rustc_lint::{LateContext, LateLintPass}; | |
5 | use rustc_middle::hir::map::Map; | |
6 | use rustc_middle::ty; | |
7 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
8 | use rustc_span::Span; | |
9 | ||
10 | declare_clippy_lint! { | |
11 | /// **What it does:** Checks for function/method calls with a mutable | |
12 | /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros. | |
13 | /// | |
14 | /// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the | |
15 | /// compiler. | |
16 | /// Therefore mutating something in a `debug_assert!` macro results in different behaviour | |
17 | /// between a release and debug build. | |
18 | /// | |
19 | /// **Known problems:** None | |
20 | /// | |
21 | /// **Example:** | |
22 | /// ```rust,ignore | |
23 | /// debug_assert_eq!(vec![3].pop(), Some(3)); | |
24 | /// // or | |
25 | /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() } | |
26 | /// debug_assert!(take_a_mut_parameter(&mut 5)); | |
27 | /// ``` | |
28 | pub DEBUG_ASSERT_WITH_MUT_CALL, | |
29 | nursery, | |
30 | "mutable arguments in `debug_assert{,_ne,_eq}!`" | |
31 | } | |
32 | ||
33 | declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]); | |
34 | ||
35 | const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"]; | |
36 | ||
37 | impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall { | |
38 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { | |
39 | for dmn in &DEBUG_MACRO_NAMES { | |
40 | if is_direct_expn_of(e.span, dmn).is_some() { | |
41 | if let Some(macro_args) = higher::extract_assert_macro_args(e) { | |
42 | for arg in macro_args { | |
43 | let mut visitor = MutArgVisitor::new(cx); | |
44 | visitor.visit_expr(arg); | |
45 | if let Some(span) = visitor.expr_span() { | |
46 | span_lint( | |
47 | cx, | |
48 | DEBUG_ASSERT_WITH_MUT_CALL, | |
49 | span, | |
50 | &format!("do not call a function with mutable arguments inside of `{}!`", dmn), | |
51 | ); | |
52 | } | |
53 | } | |
54 | } | |
55 | } | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | struct MutArgVisitor<'a, 'tcx> { | |
61 | cx: &'a LateContext<'tcx>, | |
62 | expr_span: Option<Span>, | |
63 | found: bool, | |
64 | } | |
65 | ||
66 | impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> { | |
67 | fn new(cx: &'a LateContext<'tcx>) -> Self { | |
68 | Self { | |
69 | cx, | |
70 | expr_span: None, | |
71 | found: false, | |
72 | } | |
73 | } | |
74 | ||
75 | fn expr_span(&self) -> Option<Span> { | |
76 | if self.found { self.expr_span } else { None } | |
77 | } | |
78 | } | |
79 | ||
80 | impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { | |
81 | type Map = Map<'tcx>; | |
82 | ||
83 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
84 | match expr.kind { | |
85 | ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { | |
86 | self.found = true; | |
87 | return; | |
88 | }, | |
89 | ExprKind::If(..) => { | |
90 | self.found = true; | |
91 | return; | |
92 | }, | |
93 | ExprKind::Path(_) => { | |
94 | if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { | |
95 | if adj | |
96 | .iter() | |
97 | .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut))) | |
98 | { | |
99 | self.found = true; | |
100 | return; | |
101 | } | |
102 | } | |
103 | }, | |
104 | // Don't check await desugars | |
105 | ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return, | |
106 | _ if !self.found => self.expr_span = Some(expr.span), | |
107 | _ => return, | |
108 | } | |
109 | walk_expr(self, expr) | |
110 | } | |
111 | ||
112 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
113 | NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) | |
114 | } | |
115 | } |