Skip to content

Commit 58f32c5

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 b87f474 commit 58f32c5

File tree

7 files changed

+394
-2
lines changed

7 files changed

+394
-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,
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use crate::utils::{is_direct_expn_of, span_lint};
2+
use if_chain::if_chain;
3+
use matches::matches;
4+
use rustc::hir::{Expr, ExprKind, Mutability, StmtKind, UnOp};
5+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
6+
use rustc::{declare_lint_pass, declare_tool_lint, ty};
7+
use syntax_pos::Span;
8+
9+
declare_clippy_lint! {
10+
/// **What it does:** Checks for function/method calls with a mutable
11+
/// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
12+
///
13+
/// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the
14+
/// compiler.
15+
/// Therefore mutating something in a `debug_assert!` macro results in different behaviour
16+
/// between a release and debug build.
17+
///
18+
/// **Known problems:** None
19+
///
20+
/// **Example:**
21+
/// ```rust,ignore
22+
/// debug_assert_eq!(vec![3].pop(), Some(3));
23+
/// // or
24+
/// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
25+
/// debug_assert!(take_a_mut_parameter(&mut 5));
26+
/// ```
27+
pub DEBUG_ASSERT_WITH_MUT_CALL,
28+
correctness,
29+
"mutable arguments in `debug_assert{,_ne,_eq}!`"
30+
}
31+
32+
declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
33+
34+
fn check_for_mutable_call(cx: &LateContext<'_, '_>, e: &Expr) -> bool {
35+
match &e.kind {
36+
ExprKind::AddrOf(Mutability::MutMutable, _) => true,
37+
ExprKind::AddrOf(Mutability::MutImmutable, expr) | ExprKind::Unary(_, expr) => check_for_mutable_call(cx, expr),
38+
ExprKind::Binary(_, lhs, rhs) => check_for_mutable_call(cx, lhs) | check_for_mutable_call(cx, rhs),
39+
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => {
40+
(*args).iter().any(|a| check_for_mutable_call(cx, a))
41+
},
42+
ExprKind::Path(_) => cx.tables.adjustments().get(e.hir_id).map_or(false, |adj| {
43+
adj.iter()
44+
.any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::MutMutable)))
45+
}),
46+
_ => false,
47+
}
48+
}
49+
50+
#[allow(clippy::cognitive_complexity)]
51+
fn extract_call(cx: &LateContext<'_, '_>, e: &Expr) -> Option<Span> {
52+
if_chain! {
53+
if let ExprKind::Block(ref block, _) = e.kind;
54+
if block.stmts.len() == 1;
55+
if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind;
56+
then {
57+
if_chain! {
58+
if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
59+
if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
60+
if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
61+
then {
62+
// debug_assert
63+
if check_for_mutable_call(cx, condition) {
64+
return Some(condition.span);
65+
}
66+
} else {
67+
// debug_assert_{eq,ne}
68+
if_chain! {
69+
if let ExprKind::Block(ref matchblock, _) = matchexpr.kind;
70+
if let Some(ref matchheader) = matchblock.expr;
71+
if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind;
72+
if let ExprKind::Tup(ref conditions) = headerexpr.kind;
73+
if conditions.len() == 2;
74+
then {
75+
if let ExprKind::AddrOf(_, ref lhs) = conditions[0].kind {
76+
if check_for_mutable_call(cx, lhs) {
77+
return Some(lhs.span);
78+
}
79+
}
80+
if let ExprKind::AddrOf(_, ref rhs) = conditions[1].kind {
81+
if check_for_mutable_call(cx, rhs) {
82+
return Some(rhs.span);
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
None
93+
}
94+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall {
95+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
96+
if ["debug_assert", "debug_assert_eq", "debug_assert_ne"]
97+
.iter()
98+
.any(|name| is_direct_expn_of(e.span, name).is_some())
99+
{
100+
if let Some(span) = extract_call(cx, e) {
101+
span_lint(
102+
cx,
103+
DEBUG_ASSERT_WITH_MUT_CALL,
104+
span,
105+
"do not call functions with mutable arguments inside of a `debug_assert!`",
106+
);
107+
}
108+
}
109+
}
110+
}

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",
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#![feature(custom_inner_attributes)]
2+
#![rustfmt::skip]
3+
#![allow(clippy::trivially_copy_pass_by_ref, clippy::cognitive_complexity)]
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+
103+
fn main() {
104+
func_non_mutable();
105+
func_mutable();
106+
method_non_mutable();
107+
method_mutable();
108+
109+
misc();
110+
}

0 commit comments

Comments
 (0)