Skip to content

Add lint for debug_assert_with_mut_call #4680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,7 @@ Released 2018-09-13
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

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

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

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

Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ pub mod mul_add;
pub mod multiple_crate_versions;
pub mod mut_mut;
pub mod mut_reference;
pub mod mutable_debug_assertion;
pub mod mutex_atomic;
pub mod needless_bool;
pub mod needless_borrow;
Expand Down Expand Up @@ -610,6 +611,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
reg.register_late_lint_pass(box comparison_chain::ComparisonChain);
reg.register_late_lint_pass(box mul_add::MulAddCheck);
reg.register_late_lint_pass(box unused_self::UnusedSelf);
reg.register_late_lint_pass(box mutable_debug_assertion::DebugAssertWithMutCall);

reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
arithmetic::FLOAT_ARITHMETIC,
Expand Down Expand Up @@ -855,6 +857,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
misc_early::ZERO_PREFIXED_LITERAL,
mul_add::MANUAL_MUL_ADD,
mut_reference::UNNECESSARY_MUT_PASSED,
mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
mutex_atomic::MUTEX_ATOMIC,
needless_bool::BOOL_COMPARISON,
needless_bool::NEEDLESS_BOOL,
Expand Down Expand Up @@ -1160,6 +1163,7 @@ pub fn register_plugins(reg: &mut rustc_driver::plugin::Registry<'_>, conf: &Con
misc::CMP_NAN,
misc::FLOAT_CMP,
misc::MODULO_ONE,
mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
open_options::NONSENSICAL_OPEN_OPTIONS,
Expand Down
155 changes: 155 additions & 0 deletions clippy_lints/src/mutable_debug_assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use crate::utils::{is_direct_expn_of, span_lint};
use if_chain::if_chain;
use matches::matches;
use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc::hir::{Expr, ExprKind, Mutability, StmtKind, UnOp};
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint, ty};
use syntax_pos::Span;

declare_clippy_lint! {
/// **What it does:** Checks for function/method calls with a mutable
/// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
///
/// **Why is this bad?** In release builds `debug_assert!` macros are optimized out by the
/// compiler.
/// Therefore mutating something in a `debug_assert!` macro results in different behaviour
/// between a release and debug build.
///
/// **Known problems:** None
///
/// **Example:**
/// ```rust,ignore
/// debug_assert_eq!(vec![3].pop(), Some(3));
/// // or
/// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
/// debug_assert!(take_a_mut_parameter(&mut 5));
/// ```
pub DEBUG_ASSERT_WITH_MUT_CALL,
correctness,
"mutable arguments in `debug_assert{,_ne,_eq}!`"
}

declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);

const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DebugAssertWithMutCall {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
for dmn in &DEBUG_MACRO_NAMES {
if is_direct_expn_of(e.span, dmn).is_some() {
if let Some(span) = extract_call(cx, e) {
span_lint(
cx,
DEBUG_ASSERT_WITH_MUT_CALL,
span,
&format!("do not call a function with mutable arguments inside of `{}!`", dmn),
);
}
}
}
}
}

//HACK(hellow554): remove this when #4694 is implemented
#[allow(clippy::cognitive_complexity)]
fn extract_call<'a, 'tcx>(cx: &'a LateContext<'a, 'tcx>, e: &'tcx Expr) -> Option<Span> {
if_chain! {
if let ExprKind::Block(ref block, _) = e.kind;
if block.stmts.len() == 1;
if let StmtKind::Semi(ref matchexpr) = block.stmts[0].kind;
then {
if_chain! {
if let ExprKind::Match(ref ifclause, _, _) = matchexpr.kind;
if let ExprKind::DropTemps(ref droptmp) = ifclause.kind;
if let ExprKind::Unary(UnOp::UnNot, ref condition) = droptmp.kind;
then {
// debug_assert
let mut visitor = MutArgVisitor::new(cx);
visitor.visit_expr(condition);
return visitor.expr_span();
} else {
// debug_assert_{eq,ne}
if_chain! {
if let ExprKind::Block(ref matchblock, _) = matchexpr.kind;
if let Some(ref matchheader) = matchblock.expr;
if let ExprKind::Match(ref headerexpr, _, _) = matchheader.kind;
if let ExprKind::Tup(ref conditions) = headerexpr.kind;
if conditions.len() == 2;
then {
if let ExprKind::AddrOf(_, ref lhs) = conditions[0].kind {
let mut visitor = MutArgVisitor::new(cx);
visitor.visit_expr(lhs);
if let Some(span) = visitor.expr_span() {
return Some(span);
}
}
if let ExprKind::AddrOf(_, ref rhs) = conditions[1].kind {
let mut visitor = MutArgVisitor::new(cx);
visitor.visit_expr(rhs);
if let Some(span) = visitor.expr_span() {
return Some(span);
}
}
}
}
}
}
}
}

None
}

struct MutArgVisitor<'a, 'tcx> {
cx: &'a LateContext<'a, 'tcx>,
expr_span: Option<Span>,
found: bool,
}

impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'a, 'tcx>) -> Self {
Self {
cx,
expr_span: None,
found: false,
}
}

fn expr_span(&self) -> Option<Span> {
if self.found {
self.expr_span
} else {
None
}
}
}

impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr) {
match expr.kind {
ExprKind::AddrOf(Mutability::MutMutable, _) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.tables.adjustments().get(expr.hir_id) {
if adj
.iter()
.any(|a| matches!(a.target.kind, ty::Ref(_, _, Mutability::MutMutable)))
{
self.found = true;
return;
}
}
},
_ if !self.found => self.expr_span = Some(expr.span),
_ => return,
}
walk_expr(self, expr)
}

fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::OnlyBodies(&self.cx.tcx.hir())
}
}
9 changes: 8 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 331] = [
pub const ALL_LINTS: [Lint; 332] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -280,6 +280,13 @@ pub const ALL_LINTS: [Lint; 331] = [
deprecation: None,
module: "dbg_macro",
},
Lint {
name: "debug_assert_with_mut_call",
group: "correctness",
desc: "mutable arguments in `debug_assert{,_ne,_eq}!`",
deprecation: None,
module: "mutable_debug_assertion",
},
Lint {
name: "decimal_literal_representation",
group: "restriction",
Expand Down
124 changes: 124 additions & 0 deletions tests/ui/debug_assert_with_mut_call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#![feature(custom_inner_attributes)]
#![rustfmt::skip]
#![allow(clippy::trivially_copy_pass_by_ref, clippy::cognitive_complexity, clippy::redundant_closure_call)]

struct S;

impl S {
fn bool_self_ref(&self) -> bool { false }
fn bool_self_mut(&mut self) -> bool { false }
fn bool_self_ref_arg_ref(&self, _: &u32) -> bool { false }
fn bool_self_ref_arg_mut(&self, _: &mut u32) -> bool { false }
fn bool_self_mut_arg_ref(&mut self, _: &u32) -> bool { false }
fn bool_self_mut_arg_mut(&mut self, _: &mut u32) -> bool { false }

fn u32_self_ref(&self) -> u32 { 0 }
fn u32_self_mut(&mut self) -> u32 { 0 }
fn u32_self_ref_arg_ref(&self, _: &u32) -> u32 { 0 }
fn u32_self_ref_arg_mut(&self, _: &mut u32) -> u32 { 0 }
fn u32_self_mut_arg_ref(&mut self, _: &u32) -> u32 { 0 }
fn u32_self_mut_arg_mut(&mut self, _: &mut u32) -> u32 { 0 }
}

fn bool_ref(_: &u32) -> bool { false }
fn bool_mut(_: &mut u32) -> bool { false }
fn u32_ref(_: &u32) -> u32 { 0 }
fn u32_mut(_: &mut u32) -> u32 { 0 }

fn func_non_mutable() {
debug_assert!(bool_ref(&3));
debug_assert!(!bool_ref(&3));

debug_assert_eq!(0, u32_ref(&3));
debug_assert_eq!(u32_ref(&3), 0);

debug_assert_ne!(1, u32_ref(&3));
debug_assert_ne!(u32_ref(&3), 1);
}

fn func_mutable() {
debug_assert!(bool_mut(&mut 3));
debug_assert!(!bool_mut(&mut 3));

debug_assert_eq!(0, u32_mut(&mut 3));
debug_assert_eq!(u32_mut(&mut 3), 0);

debug_assert_ne!(1, u32_mut(&mut 3));
debug_assert_ne!(u32_mut(&mut 3), 1);
}

fn method_non_mutable() {
debug_assert!(S.bool_self_ref());
debug_assert!(S.bool_self_ref_arg_ref(&3));

debug_assert_eq!(S.u32_self_ref(), 0);
debug_assert_eq!(S.u32_self_ref_arg_ref(&3), 0);

debug_assert_ne!(S.u32_self_ref(), 1);
debug_assert_ne!(S.u32_self_ref_arg_ref(&3), 1);
}

fn method_mutable() {
debug_assert!(S.bool_self_mut());
debug_assert!(!S.bool_self_mut());
debug_assert!(S.bool_self_ref_arg_mut(&mut 3));
debug_assert!(S.bool_self_mut_arg_ref(&3));
debug_assert!(S.bool_self_mut_arg_mut(&mut 3));

debug_assert_eq!(S.u32_self_mut(), 0);
debug_assert_eq!(S.u32_self_mut_arg_ref(&3), 0);
debug_assert_eq!(S.u32_self_ref_arg_mut(&mut 3), 0);
debug_assert_eq!(S.u32_self_mut_arg_mut(&mut 3), 0);

debug_assert_ne!(S.u32_self_mut(), 1);
debug_assert_ne!(S.u32_self_mut_arg_ref(&3), 1);
debug_assert_ne!(S.u32_self_ref_arg_mut(&mut 3), 1);
debug_assert_ne!(S.u32_self_mut_arg_mut(&mut 3), 1);
}

fn misc() {
// with variable
let mut v: Vec<u32> = vec![1, 2, 3, 4];
debug_assert_eq!(v.get(0), Some(&1));
debug_assert_ne!(v[0], 2);
debug_assert_eq!(v.pop(), Some(1));
debug_assert_ne!(Some(3), v.pop());

let a = &mut 3;
debug_assert!(bool_mut(a));

// nested
debug_assert!(!(bool_ref(&u32_mut(&mut 3))));

// chained
debug_assert_eq!(v.pop().unwrap(), 3);

// format args
debug_assert!(bool_ref(&3), "w/o format");
debug_assert!(bool_mut(&mut 3), "w/o format");
debug_assert!(bool_ref(&3), "{} format", "w/");
debug_assert!(bool_mut(&mut 3), "{} format", "w/");

// sub block
let mut x = 42_u32;
debug_assert!({
bool_mut(&mut x);
x > 10
});

// closures
debug_assert!((|| {
let mut x = 42;
bool_mut(&mut x);
x > 10
})());
}

fn main() {
func_non_mutable();
func_mutable();
method_non_mutable();
method_mutable();

misc();
}
Loading