Skip to content

Detect usage of custom floating-point abs implementation #5246

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 12 commits into from
Mar 4, 2020
Merged
167 changes: 164 additions & 3 deletions clippy_lints/src/floating_point_arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ use crate::consts::{
constant, Constant,
Constant::{F32, F64},
};
use crate::utils::{span_lint_and_sugg, sugg};
use crate::utils::{higher, span_lint_and_sugg, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc::ty;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Lit, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;

use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
use sugg::{format_numeric_literal, Sugg};
use syntax::ast;
use syntax::ast::{self, FloatTy, LitFloatType, LitKind};

declare_clippy_lint! {
/// **What it does:** Looks for floating-point expressions that
Expand Down Expand Up @@ -72,6 +72,16 @@ declare_clippy_lint! {
/// let _ = a.log(E);
/// let _ = a.powf(2.0);
/// let _ = a * 2.0 + 4.0;
/// let _ = if a < 0.0 {
/// -a
/// } else {
/// a
/// }
/// let _ = if a < 0.0 {
/// a
/// } else {
/// -a
/// }
/// ```
///
/// is better expressed as
Expand All @@ -88,6 +98,8 @@ declare_clippy_lint! {
/// let _ = a.ln();
/// let _ = a.powi(2);
/// let _ = a.mul_add(2.0, 4.0);
/// let _ = a.abs();
/// let _ = -a.abs();
/// ```
pub SUBOPTIMAL_FLOPS,
nursery,
Expand Down Expand Up @@ -359,6 +371,154 @@ fn check_mul_add(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
}
}

/// Returns true iff expr is an expression which tests whether or not
/// test is positive or an expression which tests whether or not test
/// is nonnegative.
/// Used for check-custom-abs function below
fn is_testing_positive(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(right) && are_exprs_equal(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(left) && are_exprs_equal(cx, right, test),
_ => false,
}
} else {
false
}
}

fn is_testing_negative(cx: &LateContext<'_, '_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(left) && are_exprs_equal(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(right) && are_exprs_equal(cx, left, test),
_ => false,
}
} else {
false
}
}

fn are_exprs_equal(cx: &LateContext<'_, '_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool {
SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2)
}

/// Returns true iff expr is some zero literal
fn is_zero(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(Lit { node: lit, .. }) = &expr.kind {
match lit {
LitKind::Int(0, _) => true,
LitKind::Float(symb, LitFloatType::Unsuffixed)
| LitKind::Float(symb, LitFloatType::Suffixed(FloatTy::F64)) => {
symb.as_str().parse::<f64>().unwrap() == 0.0
},
LitKind::Float(symb, LitFloatType::Suffixed(FloatTy::F32)) => symb.as_str().parse::<f32>().unwrap() == 0.0,
_ => false,
}
} else {
false
}
}

fn check_custom_abs(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
if let Some((cond, body, Some(else_body))) = higher::if_block(&expr) {
if let ExprKind::Block(
Block {
stmts: [],
expr:
Some(Expr {
kind: ExprKind::Unary(UnOp::UnNeg, else_expr),
..
}),
..
},
_,
) = else_body.kind
{
if let ExprKind::Block(
Block {
stmts: [],
expr: Some(body),
..
},
_,
) = &body.kind
{
if are_exprs_equal(cx, else_expr, body) {
if is_testing_positive(cx, cond, body) {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"This looks like you've implemented your own absolute value function",
"try",
format!("{}.abs()", Sugg::hir(cx, body, "..")),
Applicability::MachineApplicable,
);
} else if is_testing_negative(cx, cond, body) {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"This looks like you've implemented your own negative absolute value function",
"try",
format!("-{}.abs()", Sugg::hir(cx, body, "..")),
Applicability::MachineApplicable,
);
}
}
}
}
if let ExprKind::Block(
Block {
stmts: [],
expr:
Some(Expr {
kind: ExprKind::Unary(UnOp::UnNeg, else_expr),
..
}),
..
},
_,
) = &body.kind
{
if let ExprKind::Block(
Block {
stmts: [],
expr: Some(body),
..
},
_,
) = &else_body.kind
{
if are_exprs_equal(cx, else_expr, body) {
if is_testing_negative(cx, cond, body) {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"This looks like you've implemented your own absolute value function",
"try",
format!("{}.abs()", Sugg::hir(cx, body, "..")),
Applicability::MachineApplicable,
);
} else if is_testing_positive(cx, cond, body) {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"This looks like you've implemented your own negative absolute value function",
"try",
format!("-{}.abs()", Sugg::hir(cx, body, "..")),
Applicability::MachineApplicable,
);
}
}
}
}
}
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::MethodCall(ref path, _, args) = &expr.kind {
Expand All @@ -375,6 +535,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
} else {
check_expm1(cx, expr);
check_mul_add(cx, expr);
check_custom_abs(cx, expr);
}
}
}
111 changes: 111 additions & 0 deletions tests/ui/floating_point_abs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#![warn(clippy::suboptimal_flops)]

struct A {
a: f64,
b: f64,
}

fn fake_abs1(num: f64) -> f64 {
if num >= 0.0 {
num
} else {
-num
}
}

fn fake_abs2(num: f64) -> f64 {
if 0.0 < num {
num
} else {
-num
}
}

fn fake_abs3(a: A) -> f64 {
if a.a > 0.0 {
a.a
} else {
-a.a
}
}

fn fake_abs4(num: f64) -> f64 {
if 0.0 >= num {
-num
} else {
num
}
}

fn fake_abs5(a: A) -> f64 {
if a.a < 0.0 {
-a.a
} else {
a.a
}
}

fn fake_nabs1(num: f64) -> f64 {
if num < 0.0 {
num
} else {
-num
}
}

fn fake_nabs2(num: f64) -> f64 {
if 0.0 >= num {
num
} else {
-num
}
}

fn fake_nabs3(a: A) -> A {
A {
a: if a.a >= 0.0 { -a.a } else { a.a },
b: a.b,
}
}

fn not_fake_abs1(num: f64) -> f64 {
if num > 0.0 {
num
} else {
-num - 1f64
}
}

fn not_fake_abs2(num: f64) -> f64 {
if num > 0.0 {
num + 1.0
} else {
-(num + 1.0)
}
}

fn not_fake_abs3(num1: f64, num2: f64) -> f64 {
if num1 > 0.0 {
num2
} else {
-num2
}
}

fn not_fake_abs4(a: A) -> f64 {
if a.a > 0.0 {
a.b
} else {
-a.b
}
}

fn not_fake_abs5(a: A) -> f64 {
if a.a > 0.0 {
a.a
} else {
-a.b
}
}

fn main() {}
Loading