use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use clippy_utils::comparisons::{normalize_comparison, Rel}; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::source::snippet; use clippy_utils::ty::is_isize_or_usize; use clippy_utils::{clip, int_bits, unsext}; declare_clippy_lint! { /// ### What it does /// Checks for comparisons where one side of the relation is /// either the minimum or maximum value for its type and warns if it involves a /// case that is always true or always false. Only integer and boolean types are /// checked. /// /// ### Why is this bad? /// An expression like `min <= x` may misleadingly imply /// that it is possible for `x` to be less than the minimum. Expressions like /// `max < x` are probably mistakes. /// /// ### Known problems /// For `usize` the size of the current compile target will /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such /// a comparison to detect target pointer width will trigger this lint. One can /// use `mem::sizeof` and compare its value or conditional compilation /// attributes /// like `#[cfg(target_pointer_width = "64")] ..` instead. /// /// ### Example /// ```rust /// let vec: Vec = Vec::new(); /// if vec.len() <= 0 {} /// if 100 > i32::MAX {} /// ``` #[clippy::version = "pre 1.29.0"] pub ABSURD_EXTREME_COMPARISONS, correctness, "a comparison with a maximum or minimum value that is always true or false" } declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { if !expr.span.from_expansion() { let msg = "this comparison involving the minimum or maximum element for this \ type contains a case that is always true or always false"; let conclusion = match result { AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(), AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(), AbsurdComparisonResult::InequalityImpossible => format!( "the case where the two sides are not equal never occurs, consider using `{} == {}` \ instead", snippet(cx, lhs.span, "lhs"), snippet(cx, rhs.span, "rhs") ), }; let help = format!( "because `{}` is the {} value for this type, {}", snippet(cx, culprit.expr.span, "x"), match culprit.which { ExtremeType::Minimum => "minimum", ExtremeType::Maximum => "maximum", }, conclusion ); span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); } } } } } enum ExtremeType { Minimum, Maximum, } struct ExtremeExpr<'a> { which: ExtremeType, expr: &'a Expr<'a>, } enum AbsurdComparisonResult { AlwaysFalse, AlwaysTrue, InequalityImpossible, } fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { if let ExprKind::Cast(cast_exp, _) = expr.kind { let precast_ty = cx.typeck_results().expr_ty(cast_exp); let cast_ty = cx.typeck_results().expr_ty(expr); return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty); } false } fn detect_absurd_comparison<'tcx>( cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, ) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> { use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; use ExtremeType::{Maximum, Minimum}; // absurd comparison only makes sense on primitive types // primitive types don't implement comparison operators with each other if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) { return None; } // comparisons between fix sized types and target sized types are considered unanalyzable if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) { return None; } let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?; let lx = detect_extreme_expr(cx, normalized_lhs); let rx = detect_extreme_expr(cx, normalized_rhs); Some(match rel { Rel::Lt => { match (lx, rx) { (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min _ => return None, } }, Rel::Le => { match (lx, rx) { (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max _ => return None, } }, Rel::Ne | Rel::Eq => return None, }) } fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option> { let ty = cx.typeck_results().expr_ty(expr); let cv = constant(cx, cx.typeck_results(), expr)?.0; let which = match (ty.kind(), cv) { (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum, (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => { ExtremeType::Minimum }, (&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum, (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => { ExtremeType::Maximum }, (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum, _ => return None, }; Some(ExtremeExpr { which, expr }) }