]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use rustc_hir::{BinOpKind, Expr, ExprKind}; |
2 | use rustc_lint::{LateContext, LateLintPass}; | |
3 | use rustc_middle::ty; | |
4 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
5 | ||
cdc7bbd5 | 6 | use clippy_utils::comparisons::{normalize_comparison, Rel}; |
17df50a5 | 7 | use clippy_utils::consts::{constant, Constant}; |
cdc7bbd5 XL |
8 | use clippy_utils::diagnostics::span_lint_and_help; |
9 | use clippy_utils::source::snippet; | |
10 | use clippy_utils::ty::is_isize_or_usize; | |
11 | use clippy_utils::{clip, int_bits, unsext}; | |
12 | ||
13 | declare_clippy_lint! { | |
94222f64 XL |
14 | /// ### What it does |
15 | /// Checks for comparisons where one side of the relation is | |
cdc7bbd5 XL |
16 | /// either the minimum or maximum value for its type and warns if it involves a |
17 | /// case that is always true or always false. Only integer and boolean types are | |
18 | /// checked. | |
19 | /// | |
94222f64 XL |
20 | /// ### Why is this bad? |
21 | /// An expression like `min <= x` may misleadingly imply | |
cdc7bbd5 XL |
22 | /// that it is possible for `x` to be less than the minimum. Expressions like |
23 | /// `max < x` are probably mistakes. | |
24 | /// | |
94222f64 XL |
25 | /// ### Known problems |
26 | /// For `usize` the size of the current compile target will | |
cdc7bbd5 XL |
27 | /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such |
28 | /// a comparison to detect target pointer width will trigger this lint. One can | |
29 | /// use `mem::sizeof` and compare its value or conditional compilation | |
30 | /// attributes | |
31 | /// like `#[cfg(target_pointer_width = "64")] ..` instead. | |
32 | /// | |
94222f64 | 33 | /// ### Example |
cdc7bbd5 XL |
34 | /// ```rust |
35 | /// let vec: Vec<isize> = Vec::new(); | |
36 | /// if vec.len() <= 0 {} | |
37 | /// if 100 > i32::MAX {} | |
38 | /// ``` | |
a2a8927a | 39 | #[clippy::version = "pre 1.29.0"] |
cdc7bbd5 XL |
40 | pub ABSURD_EXTREME_COMPARISONS, |
41 | correctness, | |
42 | "a comparison with a maximum or minimum value that is always true or false" | |
43 | } | |
44 | ||
45 | declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); | |
46 | ||
47 | impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons { | |
48 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
49 | if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { | |
50 | if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { | |
51 | if !expr.span.from_expansion() { | |
52 | let msg = "this comparison involving the minimum or maximum element for this \ | |
53 | type contains a case that is always true or always false"; | |
54 | ||
55 | let conclusion = match result { | |
56 | AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(), | |
57 | AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(), | |
58 | AbsurdComparisonResult::InequalityImpossible => format!( | |
59 | "the case where the two sides are not equal never occurs, consider using `{} == {}` \ | |
60 | instead", | |
61 | snippet(cx, lhs.span, "lhs"), | |
62 | snippet(cx, rhs.span, "rhs") | |
63 | ), | |
64 | }; | |
65 | ||
66 | let help = format!( | |
67 | "because `{}` is the {} value for this type, {}", | |
68 | snippet(cx, culprit.expr.span, "x"), | |
69 | match culprit.which { | |
70 | ExtremeType::Minimum => "minimum", | |
71 | ExtremeType::Maximum => "maximum", | |
72 | }, | |
73 | conclusion | |
74 | ); | |
75 | ||
76 | span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); | |
77 | } | |
78 | } | |
79 | } | |
80 | } | |
81 | } | |
82 | ||
83 | enum ExtremeType { | |
84 | Minimum, | |
85 | Maximum, | |
86 | } | |
87 | ||
88 | struct ExtremeExpr<'a> { | |
89 | which: ExtremeType, | |
90 | expr: &'a Expr<'a>, | |
91 | } | |
92 | ||
93 | enum AbsurdComparisonResult { | |
94 | AlwaysFalse, | |
95 | AlwaysTrue, | |
96 | InequalityImpossible, | |
97 | } | |
98 | ||
99 | fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { | |
100 | if let ExprKind::Cast(cast_exp, _) = expr.kind { | |
101 | let precast_ty = cx.typeck_results().expr_ty(cast_exp); | |
102 | let cast_ty = cx.typeck_results().expr_ty(expr); | |
103 | ||
104 | return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty); | |
105 | } | |
106 | ||
107 | false | |
108 | } | |
109 | ||
110 | fn detect_absurd_comparison<'tcx>( | |
111 | cx: &LateContext<'tcx>, | |
112 | op: BinOpKind, | |
113 | lhs: &'tcx Expr<'_>, | |
114 | rhs: &'tcx Expr<'_>, | |
115 | ) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> { | |
116 | use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible}; | |
117 | use ExtremeType::{Maximum, Minimum}; | |
118 | // absurd comparison only makes sense on primitive types | |
119 | // primitive types don't implement comparison operators with each other | |
120 | if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) { | |
121 | return None; | |
122 | } | |
123 | ||
124 | // comparisons between fix sized types and target sized types are considered unanalyzable | |
125 | if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) { | |
126 | return None; | |
127 | } | |
128 | ||
129 | let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?; | |
130 | ||
131 | let lx = detect_extreme_expr(cx, normalized_lhs); | |
132 | let rx = detect_extreme_expr(cx, normalized_rhs); | |
133 | ||
134 | Some(match rel { | |
135 | Rel::Lt => { | |
136 | match (lx, rx) { | |
137 | (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x | |
138 | (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min | |
139 | _ => return None, | |
140 | } | |
141 | }, | |
142 | Rel::Le => { | |
143 | match (lx, rx) { | |
144 | (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x | |
145 | (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x | |
146 | (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min | |
147 | (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max | |
148 | _ => return None, | |
149 | } | |
150 | }, | |
151 | Rel::Ne | Rel::Eq => return None, | |
152 | }) | |
153 | } | |
154 | ||
155 | fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> { | |
156 | let ty = cx.typeck_results().expr_ty(expr); | |
157 | ||
158 | let cv = constant(cx, cx.typeck_results(), expr)?.0; | |
159 | ||
160 | let which = match (ty.kind(), cv) { | |
161 | (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum, | |
162 | (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => { | |
163 | ExtremeType::Minimum | |
164 | }, | |
165 | ||
166 | (&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum, | |
167 | (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => { | |
168 | ExtremeType::Maximum | |
169 | }, | |
170 | (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum, | |
171 | ||
172 | _ => return None, | |
173 | }; | |
174 | Some(ExtremeExpr { which, expr }) | |
175 | } |