]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/mutable_debug_assertion.rs
Auto merge of #4879 - matthiaskrgr:rustup_23, r=flip1995
[rust.git] / clippy_lints / src / mutable_debug_assertion.rs
1 use crate::utils::{is_direct_expn_of, span_lint};
2 use if_chain::if_chain;
3 use matches::matches;
4 use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5 use rustc::hir::{BorrowKind, Expr, ExprKind, Mutability, StmtKind, UnOp};
6 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
7 use rustc::{declare_lint_pass, ty};
8 use rustc_session::declare_tool_lint;
9 use syntax_pos::Span;
10
11 declare_clippy_lint! {
12     /// **What it does:** Checks for function/method calls with a mutable
13     /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
14     ///
15     /// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the
16     /// compiler.
17     /// Therefore mutating something in a `debug_assert!` macro results in different behaviour
18     /// between a release and debug build.
19     ///
20     /// **Known problems:** None
21     ///
22     /// **Example:**
23     /// ```rust,ignore
24     /// debug_assert_eq!(vec![3].pop(), Some(3));
25     /// // or
26     /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
27     /// debug_assert!(take_a_mut_parameter(&mut 5));
28     /// ```
29     pub DEBUG_ASSERT_WITH_MUT_CALL,
30     correctness,
31     "mutable arguments in `debug_assert{,_ne,_eq}!`"
32 }
33
34 declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
35
36 const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];
37
38 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall {
39     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
40         for dmn in &DEBUG_MACRO_NAMES {
41             if is_direct_expn_of(e.span, dmn).is_some() {
42                 if let Some(span) = extract_call(cx, e) {
43                     span_lint(
44                         cx,
45                         DEBUG_ASSERT_WITH_MUT_CALL,
46                         span,
47                         &format!("do not call a function with mutable arguments inside of `{}!`", dmn),
48                     );
49                 }
50             }
51         }
52     }
53 }
54
55 //HACK(hellow554): remove this when #4694 is implemented
56 #[allow(clippy::cognitive_complexity)]
57 fn extract_call<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, e: &'tcx Expr) -> Option<Span> {
58     if_chain! {
59         if let ExprKind::Block(ref block, _) = e.kind;
60         if block.stmts.len() == 1;
61         if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind;
62         then {
63             if_chain! {
64                 if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
65                 if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
66                 if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
67                 then {
68                     // debug_assert
69                     let mut visitor = MutArgVisitor::new(cx);
70                     visitor.visit_expr(condition);
71                     return visitor.expr_span();
72                 } else {
73                     // debug_assert_{eq,ne}
74                     if_chain! {
75                         if let ExprKind::Block(ref matchblock, _) = matchexpr.kind;
76                         if let Some(ref matchheader) = matchblock.expr;
77                         if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind;
78                         if let ExprKind::Tup(ref conditions) = headerexpr.kind;
79                         if conditions.len() == 2;
80                         then {
81                             if let ExprKind::AddrOf(BorrowKind::Ref, _, ref lhs) = conditions[0].kind {
82                                 let mut visitor = MutArgVisitor::new(cx);
83                                 visitor.visit_expr(lhs);
84                                 if let Some(span) = visitor.expr_span() {
85                                     return Some(span);
86                                 }
87                             }
88                             if let ExprKind::AddrOf(BorrowKind::Ref, _, ref rhs) = conditions[1].kind {
89                                 let mut visitor = MutArgVisitor::new(cx);
90                                 visitor.visit_expr(rhs);
91                                 if let Some(span) = visitor.expr_span() {
92                                     return Some(span);
93                                 }
94                             }
95                         }
96                     }
97                 }
98             }
99         }
100     }
101
102     None
103 }
104
105 struct MutArgVisitor<'a, 'tcx> {
106     cx: &'a LateContext<'a, 'tcx>,
107     expr_span: Option<Span>,
108     found: bool,
109 }
110
111 impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
112     fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
113         Self {
114             cx,
115             expr_span: None,
116             found: false,
117         }
118     }
119
120     fn expr_span(&self) -> Option<Span> {
121         if self.found {
122             self.expr_span
123         } else {
124             None
125         }
126     }
127 }
128
129 impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
130     fn visit_expr(&mut self, expr: &'tcx Expr) {
131         match expr.kind {
132             ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mutable, _) => {
133                 self.found = true;
134                 return;
135             },
136             ExprKind::Path(_) => {
137                 if let Some(adj) = self.cx.tables.adjustments().get(expr.hir_id) {
138                     if adj
139                         .iter()
140                         .any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::Mutable)))
141                     {
142                         self.found = true;
143                         return;
144                     }
145                 }
146             },
147             _ if !self.found => self.expr_span = Some(expr.span),
148             _ => return,
149         }
150         walk_expr(self, expr)
151     }
152
153     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
154         NestedVisitorMap::OnlyBodies(&self.cx.tcx.hir())
155     }
156 }