]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/mutable_debug_assertion.rs
Update upstream source from tag 'upstream/1.52.1+dfsg1'
[rustc.git] / src / tools / clippy / clippy_lints / src / mutable_debug_assertion.rs
CommitLineData
f20569fa
XL
1use crate::utils::{higher, is_direct_expn_of, span_lint};
2use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
3use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
4use rustc_lint::{LateContext, LateLintPass};
5use rustc_middle::hir::map::Map;
6use rustc_middle::ty;
7use rustc_session::{declare_lint_pass, declare_tool_lint};
8use rustc_span::Span;
9
10declare_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
33declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
34
35const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];
36
37impl<'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
60struct MutArgVisitor<'a, 'tcx> {
61 cx: &'a LateContext<'tcx>,
62 expr_span: Option<Span>,
63 found: bool,
64}
65
66impl<'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
80impl<'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}