Skip to content

Commit 5572476

Browse files
committed
Add lint for debug_assert_with_mut_call
This lint will complain when you put a mutable function/method call inside a `debug_assert` macro, because it will not be executed in release mode, therefore it will change the execution flow, which is not wanted.
1 parent 1d0f625 commit 5572476

File tree

7 files changed

+465
-2
lines changed

7 files changed

+465
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,7 @@ Released 2018-09-13
966966
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
967967
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
968968
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
969+
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
969970
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
970971
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
971972
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 331 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 332 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ pub mod mul_add;
230230
pub mod multiple_crate_versions;
231231
pub mod mut_mut;
232232
pub mod mut_reference;
233+
pub mod mutable_debug_assertion;
233234
pub mod mutex_atomic;
234235
pub mod needless_bool;
235236
pub mod needless_borrow;
@@ -610,6 +611,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
610611
reg.register_late_lint_pass(box comparison_chain::ComparisonChain);
611612
reg.register_late_lint_pass(box mul_add::MulAddCheck);
612613
reg.register_late_lint_pass(box unused_self::UnusedSelf);
614+
reg.register_late_lint_pass(box mutable_debug_assertion::DebugAssertWithMutCall);
613615

614616
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
615617
arithmetic::FLOAT_ARITHMETIC,
@@ -855,6 +857,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
855857
misc_early::ZERO_PREFIXED_LITERAL,
856858
mul_add::MANUAL_MUL_ADD,
857859
mut_reference::UNNECESSARY_MUT_PASSED,
860+
mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
858861
mutex_atomic::MUTEX_ATOMIC,
859862
needless_bool::BOOL_COMPARISON,
860863
needless_bool::NEEDLESS_BOOL,
@@ -1160,6 +1163,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
11601163
misc::CMP_NAN,
11611164
misc::FLOAT_CMP,
11621165
misc::MODULO_ONE,
1166+
mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
11631167
non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
11641168
non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
11651169
open_options::NONSENSICAL_OPEN_OPTIONS,
+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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::{Expr, ExprKind, Mutability, StmtKind, UnOp};
6+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
7+
use rustc::{declare_lint_pass, declare_tool_lint, ty};
8+
use syntax_pos::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+
correctness,
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<'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+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
//HACK(hellow554): remove this when #4694 is implemented
55+
#[allow(clippy::cognitive_complexity)]
56+
fn extract_call<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, e: &'tcx Expr) -> Option<Span> {
57+
if_chain! {
58+
if let ExprKind::Block(ref block, _) = e.kind;
59+
if block.stmts.len() == 1;
60+
if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind;
61+
then {
62+
if_chain! {
63+
if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
64+
if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
65+
if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
66+
then {
67+
// debug_assert
68+
let mut visitor = MutArgVisitor::new(cx);
69+
visitor.visit_expr(condition);
70+
return visitor.expr_span();
71+
} else {
72+
// debug_assert_{eq,ne}
73+
if_chain! {
74+
if let ExprKind::Block(ref matchblock, _) = matchexpr.kind;
75+
if let Some(ref matchheader) = matchblock.expr;
76+
if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind;
77+
if let ExprKind::Tup(ref conditions) = headerexpr.kind;
78+
if conditions.len() == 2;
79+
then {
80+
if let ExprKind::AddrOf(_, ref lhs) = conditions[0].kind {
81+
let mut visitor = MutArgVisitor::new(cx);
82+
visitor.visit_expr(lhs);
83+
if let Some(span) = visitor.expr_span() {
84+
return Some(span);
85+
}
86+
}
87+
if let ExprKind::AddrOf(_, ref rhs) = conditions[1].kind {
88+
let mut visitor = MutArgVisitor::new(cx);
89+
visitor.visit_expr(rhs);
90+
if let Some(span) = visitor.expr_span() {
91+
return Some(span);
92+
}
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}
99+
}
100+
101+
None
102+
}
103+
104+
struct MutArgVisitor<'a, 'tcx> {
105+
cx: &'a LateContext<'a, 'tcx>,
106+
expr_span: Option<Span>,
107+
found: bool,
108+
}
109+
110+
impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
111+
fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
112+
Self {
113+
cx,
114+
expr_span: None,
115+
found: false,
116+
}
117+
}
118+
119+
fn expr_span(&self) -> Option<Span> {
120+
if self.found {
121+
self.expr_span
122+
} else {
123+
None
124+
}
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,
148+
}
149+
walk_expr(self, expr)
150+
}
151+
152+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
153+
NestedVisitorMap::OnlyBodies(&self.cx.tcx.hir())
154+
}
155+
}

src/lintlist/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 331] = [
9+
pub const ALL_LINTS: [Lint; 332] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -280,6 +280,13 @@ pub const ALL_LINTS: [Lint; 331] = [
280280
deprecation: None,
281281
module: "dbg_macro",
282282
},
283+
Lint {
284+
name: "debug_assert_with_mut_call",
285+
group: "correctness",
286+
desc: "mutable arguments in `debug_assert{,_ne,_eq}!`",
287+
deprecation: None,
288+
module: "mutable_debug_assertion",
289+
},
283290
Lint {
284291
name: "decimal_literal_representation",
285292
group: "restriction",
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
#![allow(clippy::trivially_copy_pass_by_ref, clippy::cognitive_complexity, clippy::redundant_closure_call)]
4+
5+
struct S;
6+
7+
impl S {
8+
fn bool_self_ref(&self) -> bool { false }
9+
fn bool_self_mut(&mut self) -> bool { false }
10+
fn bool_self_ref_arg_ref(&self, _: &u32) -> bool { false }
11+
fn bool_self_ref_arg_mut(&self, _: &mut u32) -> bool { false }
12+
fn bool_self_mut_arg_ref(&mut self, _: &u32) -> bool { false }
13+
fn bool_self_mut_arg_mut(&mut self, _: &mut u32) -> bool { false }
14+
15+
fn u32_self_ref(&self) -> u32 { 0 }
16+
fn u32_self_mut(&mut self) -> u32 { 0 }
17+
fn u32_self_ref_arg_ref(&self, _: &u32) -> u32 { 0 }
18+
fn u32_self_ref_arg_mut(&self, _: &mut u32) -> u32 { 0 }
19+
fn u32_self_mut_arg_ref(&mut self, _: &u32) -> u32 { 0 }
20+
fn u32_self_mut_arg_mut(&mut self, _: &mut u32) -> u32 { 0 }
21+
}
22+
23+
fn bool_ref(_: &u32) -> bool { false }
24+
fn bool_mut(_: &mut u32) -> bool { false }
25+
fn u32_ref(_: &u32) -> u32 { 0 }
26+
fn u32_mut(_: &mut u32) -> u32 { 0 }
27+
28+
fn func_non_mutable() {
29+
debug_assert!(bool_ref(&3));
30+
debug_assert!(!bool_ref(&3));
31+
32+
debug_assert_eq!(0, u32_ref(&3));
33+
debug_assert_eq!(u32_ref(&3), 0);
34+
35+
debug_assert_ne!(1, u32_ref(&3));
36+
debug_assert_ne!(u32_ref(&3), 1);
37+
}
38+
39+
fn func_mutable() {
40+
debug_assert!(bool_mut(&mut 3));
41+
debug_assert!(!bool_mut(&mut 3));
42+
43+
debug_assert_eq!(0, u32_mut(&mut 3));
44+
debug_assert_eq!(u32_mut(&mut 3), 0);
45+
46+
debug_assert_ne!(1, u32_mut(&mut 3));
47+
debug_assert_ne!(u32_mut(&mut 3), 1);
48+
}
49+
50+
fn method_non_mutable() {
51+
debug_assert!(S.bool_self_ref());
52+
debug_assert!(S.bool_self_ref_arg_ref(&3));
53+
54+
debug_assert_eq!(S.u32_self_ref(), 0);
55+
debug_assert_eq!(S.u32_self_ref_arg_ref(&3), 0);
56+
57+
debug_assert_ne!(S.u32_self_ref(), 1);
58+
debug_assert_ne!(S.u32_self_ref_arg_ref(&3), 1);
59+
}
60+
61+
fn method_mutable() {
62+
debug_assert!(S.bool_self_mut());
63+
debug_assert!(!S.bool_self_mut());
64+
debug_assert!(S.bool_self_ref_arg_mut(&mut 3));
65+
debug_assert!(S.bool_self_mut_arg_ref(&3));
66+
debug_assert!(S.bool_self_mut_arg_mut(&mut 3));
67+
68+
debug_assert_eq!(S.u32_self_mut(), 0);
69+
debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0);
70+
debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0);
71+
debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0);
72+
73+
debug_assert_ne!(S.u32_self_mut(), 1);
74+
debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1);
75+
debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1);
76+
debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1);
77+
}
78+
79+
fn misc() {
80+
// with variable
81+
let mut v: Vec<u32> = vec![1, 2, 3, 4];
82+
debug_assert_eq!(v.get(0), Some(&1));
83+
debug_assert_ne!(v[0], 2);
84+
debug_assert_eq!(v.pop(), Some(1));
85+
debug_assert_ne!(Some(3), v.pop());
86+
87+
let a = &mut 3;
88+
debug_assert!(bool_mut(a));
89+
90+
// nested
91+
debug_assert!(!(bool_ref(&u32_mut(&mut 3))));
92+
93+
// chained
94+
debug_assert_eq!(v.pop().unwrap(), 3);
95+
96+
// format args
97+
debug_assert!(bool_ref(&3), "w/o format");
98+
debug_assert!(bool_mut(&mut 3), "w/o format");
99+
debug_assert!(bool_ref(&3), "{} format", "w/");
100+
debug_assert!(bool_mut(&mut 3), "{} format", "w/");
101+
102+
// sub block
103+
let mut x = 42_u32;
104+
debug_assert!({
105+
bool_mut(&mut x);
106+
x > 10
107+
});
108+
109+
// closures
110+
debug_assert!((|| {
111+
let mut x = 42;
112+
bool_mut(&mut x);
113+
x > 10
114+
})());
115+
}
116+
117+
fn main() {
118+
func_non_mutable();
119+
func_mutable();
120+
method_non_mutable();
121+
method_mutable();
122+
123+
misc();
124+
}

0 commit comments

Comments
 (0)