Skip to content

Commit 538c1e8

Browse files
committed
use Visitor to walk through expressions
1 parent c1d2ed1 commit 538c1e8

File tree

3 files changed

+121
-110
lines changed

3 files changed

+121
-110
lines changed
+78-80
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::utils::{is_direct_expn_of, span_lint};
2-
use core::ops::Deref;
32
use if_chain::if_chain;
43
use matches::matches;
5-
use rustc::hir::{Expr, ExprKind, Guard, Mutability, StmtKind, UnOp};
4+
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5+
use rustc::hir::{Expr, ExprKind, Mutability, StmtKind, UnOp};
66
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
77
use rustc::{declare_lint_pass, declare_tool_lint, ty};
88
use syntax_pos::Span;
@@ -32,72 +32,28 @@ declare_clippy_lint! {
3232

3333
declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
3434

35-
fn check_for_mutable_call(cx: &LateContext<'_, '_>, span: Span, e: &Expr) -> Option<Span> {
36-
match &e.kind {
37-
ExprKind::AddrOf(Mutability::MutMutable, _) => Some(span),
38-
ExprKind::Path(_) => cx.tables.adjustments().get(e.hir_id).and_then(|adj| {
39-
if adj
40-
.iter()
41-
.any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::MutMutable)))
42-
{
43-
Some(span)
44-
} else {
45-
None
35+
const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];
36+
37+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall {
38+
fn check_expr(&mut self, cx: &LateContext<'a, '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(span) = extract_call(cx, e) {
42+
span_lint(
43+
cx,
44+
DEBUG_ASSERT_WITH_MUT_CALL,
45+
span,
46+
&format!("do not call a function with mutable arguments inside of `{}!`", dmn),
47+
);
48+
}
4649
}
47-
}),
48-
ExprKind::AddrOf(Mutability::MutImmutable, expr)
49-
| ExprKind::Assign(_, expr)
50-
| ExprKind::AssignOp(_, _, expr)
51-
| ExprKind::Box(expr)
52-
| ExprKind::Break(_, Some(expr))
53-
| ExprKind::Cast(expr, _)
54-
| ExprKind::DropTemps(expr)
55-
| ExprKind::Field(expr, _)
56-
| ExprKind::Repeat(expr, _)
57-
| ExprKind::Ret(Some(expr))
58-
| ExprKind::Type(expr, _)
59-
| ExprKind::Unary(_, expr)
60-
| ExprKind::Yield(expr, _) => check_for_mutable_call(cx, expr.span, expr),
61-
ExprKind::Binary(_, lhs, rhs) | ExprKind::Index(lhs, rhs) => {
62-
check_for_mutable_call(cx, lhs.span, lhs).or_else(|| check_for_mutable_call(cx, rhs.span, rhs))
63-
},
64-
ExprKind::Array(args) | ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) | ExprKind::Tup(args) => {
65-
(*args).iter().find_map(|a| check_for_mutable_call(cx, span, a))
66-
},
67-
ExprKind::Match(header, arm, _) => check_for_mutable_call(cx, header.span, header).or_else(|| {
68-
(*arm).iter().find_map(|a| {
69-
check_for_mutable_call(cx, a.body.span, &a.body).or_else(|| {
70-
a.guard.as_ref().and_then(|g| {
71-
let Guard::If(e) = g;
72-
check_for_mutable_call(cx, e.span, &e)
73-
})
74-
})
75-
})
76-
}),
77-
ExprKind::Block(block, _) | ExprKind::Loop(block, _, _) => block
78-
.stmts
79-
.iter()
80-
.filter_map(|s| match &s.kind {
81-
StmtKind::Local(l) => l.init.as_ref().map(|x| x.deref()),
82-
StmtKind::Expr(e) => Some(e),
83-
StmtKind::Semi(e) => Some(e),
84-
StmtKind::Item(_) => None,
85-
})
86-
.chain(block.expr.as_ref().map(|x| x.deref()))
87-
.find_map(|a| check_for_mutable_call(cx, a.span, a)),
88-
ExprKind::Err
89-
| ExprKind::Lit(_)
90-
| ExprKind::Continue(_)
91-
| ExprKind::InlineAsm(_, _, _)
92-
| ExprKind::Struct(_, _, _)
93-
| ExprKind::Closure(_, _, _, _, _)
94-
| ExprKind::Break(_, None)
95-
| ExprKind::Ret(None) => None,
50+
}
9651
}
9752
}
9853

54+
//HACK(hellow554): remove this when #4694 is implemented
9955
#[allow(clippy::cognitive_complexity)]
100-
fn extract_call(cx: &LateContext<'_, '_>, e: &Expr) -> Option<Span> {
56+
fn extract_call<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, e: &'tcx Expr) -> Option<Span> {
10157
if_chain! {
10258
if let ExprKind::Block(ref block, _) = e.kind;
10359
if block.stmts.len() == 1;
@@ -109,7 +65,7 @@ fn extract_call(cx: &LateContext<'_, '_>, e: &Expr) -> Option<Span> {
10965
if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
11066
then {
11167
// debug_assert
112-
return check_for_mutable_call(cx, condition.span, condition)
68+
return MutArgVisitor::go(cx, condition);
11369
} else {
11470
// debug_assert_{eq,ne}
11571
if_chain! {
@@ -120,12 +76,12 @@ fn extract_call(cx: &LateContext<'_, '_>, e: &Expr) -> Option<Span> {
12076
if conditions.len() == 2;
12177
then {
12278
if let ExprKind::AddrOf(_, ref lhs) = conditions[0].kind {
123-
if let Some(span) = check_for_mutable_call(cx, lhs.span, lhs) {
79+
if let Some(span) = MutArgVisitor::go(cx, lhs) {
12480
return Some(span);
12581
}
12682
}
12783
if let ExprKind::AddrOf(_, ref rhs) = conditions[1].kind {
128-
if let Some(span) = check_for_mutable_call(cx, rhs.span, rhs) {
84+
if let Some(span) = MutArgVisitor::go(cx, rhs) {
12985
return Some(span);
13086
}
13187
}
@@ -138,20 +94,62 @@ fn extract_call(cx: &LateContext<'_, '_>, e: &Expr) -> Option<Span> {
13894

13995
None
14096
}
141-
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall {
142-
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
143-
if ["debug_assert", "debug_assert_eq", "debug_assert_ne"]
144-
.iter()
145-
.any(|name| is_direct_expn_of(e.span, name).is_some())
146-
{
147-
if let Some(span) = extract_call(cx, e) {
148-
span_lint(
149-
cx,
150-
DEBUG_ASSERT_WITH_MUT_CALL,
151-
span,
152-
"do not call functions with mutable arguments inside of a `debug_assert!`",
153-
);
154-
}
97+
98+
struct MutArgVisitor<'a, 'tcx> {
99+
cx: &'a LateContext<'a, 'tcx>,
100+
expr_span: Option<Span>,
101+
found: bool,
102+
}
103+
104+
impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
105+
fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
106+
Self {
107+
cx,
108+
expr_span: None,
109+
found: false,
110+
}
111+
}
112+
113+
fn get_span(&self) -> Option<Span> {
114+
if self.found {
115+
self.expr_span
116+
} else {
117+
None
118+
}
119+
}
120+
121+
fn go(cx: &'a LateContext<'a, 'tcx>, start: &'tcx Expr) -> Option<Span> {
122+
let mut s = Self::new(cx);
123+
s.visit_expr(start);
124+
s.get_span()
125+
}
126+
}
127+
128+
impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
129+
fn visit_expr(&mut self, expr: &'tcx Expr) {
130+
match expr.kind {
131+
ExprKind::AddrOf(Mutability::MutMutable, _) => {
132+
self.found = true;
133+
return;
134+
},
135+
ExprKind::Path(_) => {
136+
if let Some(adj) = self.cx.tables.adjustments().get(expr.hir_id) {
137+
if adj
138+
.iter()
139+
.any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::MutMutable)))
140+
{
141+
self.found = true;
142+
return;
143+
}
144+
}
145+
},
146+
_ if !self.found => self.expr_span = Some(expr.span),
147+
_ => return,
155148
}
149+
walk_expr(self, expr)
150+
}
151+
152+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
153+
NestedVisitorMap::All(&self.cx.tcx.hir())
156154
}
157155
}

tests/ui/debug_assert_with_mut_call.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![feature(custom_inner_attributes)]
22
#![rustfmt::skip]
3-
#![allow(clippy::trivially_copy_pass_by_ref, clippy::cognitive_complexity)]
3+
#![allow(clippy::trivially_copy_pass_by_ref, clippy::cognitive_complexity, clippy::redundant_closure_call)]
44

55
struct S;
66

@@ -105,6 +105,13 @@ fn misc() {
105105
bool_mut(&mut x);
106106
x > 10
107107
});
108+
109+
// closures
110+
debug_assert!((|| {
111+
let mut x = 42;
112+
bool_mut(&mut x);
113+
x > 10
114+
})());
108115
}
109116

110117
fn main() {

0 commit comments

Comments
 (0)