Skip to content

Commit 35d9891

Browse files
committed
Merge pull request #802 (through #828)
Bad Comparison of Upcasted Ints
2 parents c150ae7 + eada860 commit 35d9891

File tree

6 files changed

+248
-12
lines changed

6 files changed

+248
-12
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Table of contents:
1414
* [License](#license)
1515

1616
##Lints
17-
There are 139 lints included in this crate:
17+
There are 140 lints included in this crate:
1818

1919
name | default | meaning
2020
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -68,6 +68,7 @@ name
6868
[ineffective_bit_mask](https://github.com/Manishearth/rust-clippy/wiki#ineffective_bit_mask) | warn | expressions where a bit mask will be rendered useless by a comparison, e.g. `(x | 1) > 2`
6969
[inline_always](https://github.com/Manishearth/rust-clippy/wiki#inline_always) | warn | `#[inline(always)]` is a bad idea in most cases
7070
[invalid_regex](https://github.com/Manishearth/rust-clippy/wiki#invalid_regex) | deny | finds invalid regular expressions in `Regex::new(_)` invocations
71+
[invalid_upcast_comparisons](https://github.com/Manishearth/rust-clippy/wiki#invalid_upcast_comparisons) | warn | a comparison involving an upcast which is always true or false
7172
[items_after_statements](https://github.com/Manishearth/rust-clippy/wiki#items_after_statements) | warn | finds blocks where an item comes after a statement
7273
[iter_next_loop](https://github.com/Manishearth/rust-clippy/wiki#iter_next_loop) | warn | for-looping over `_.next()` which is probably not intended
7374
[len_without_is_empty](https://github.com/Manishearth/rust-clippy/wiki#len_without_is_empty) | warn | traits and impls that have `.len()` but not `.is_empty()`

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
221221
});
222222
reg.register_late_lint_pass(box drop_ref::DropRefPass);
223223
reg.register_late_lint_pass(box types::AbsurdExtremeComparisons);
224+
reg.register_late_lint_pass(box types::InvalidUpcastComparisons);
224225
reg.register_late_lint_pass(box regex::RegexPass::default());
225226
reg.register_late_lint_pass(box copies::CopyAndPaste);
226227
reg.register_late_lint_pass(box format::FormatMacLint);
@@ -369,6 +370,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
369370
types::ABSURD_EXTREME_COMPARISONS,
370371
types::BOX_VEC,
371372
types::CHAR_LIT_AS_U8,
373+
types::INVALID_UPCAST_COMPARISONS,
372374
types::LET_UNIT_VALUE,
373375
types::LINKEDLIST,
374376
types::TYPE_COMPLEXITY,

src/types.rs

Lines changed: 185 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc::ty;
55
use rustc_front::hir::*;
66
use rustc_front::intravisit::{FnKind, Visitor, walk_ty};
77
use rustc_front::util::{is_comparison_binop, binop_to_string};
8+
use std::cmp::Ordering;
89
use syntax::ast::{IntTy, UintTy, FloatTy};
910
use syntax::codemap::Span;
1011
use utils::*;
@@ -640,23 +641,20 @@ enum AbsurdComparisonResult {
640641
InequalityImpossible,
641642
}
642643

644+
645+
643646
fn detect_absurd_comparison<'a>(cx: &LateContext, op: BinOp_, lhs: &'a Expr, rhs: &'a Expr)
644647
-> Option<(ExtremeExpr<'a>, AbsurdComparisonResult)> {
645648
use types::ExtremeType::*;
646649
use types::AbsurdComparisonResult::*;
650+
use utils::comparisons::*;
647651
type Extr<'a> = ExtremeExpr<'a>;
648652

649-
// Put the expression in the form lhs < rhs or lhs <= rhs.
650-
enum Rel {
651-
Lt,
652-
Le,
653-
};
654-
let (rel, normalized_lhs, normalized_rhs) = match op {
655-
BiLt => (Rel::Lt, lhs, rhs),
656-
BiLe => (Rel::Le, lhs, rhs),
657-
BiGt => (Rel::Lt, rhs, lhs),
658-
BiGe => (Rel::Le, rhs, lhs),
659-
_ => return None,
653+
let normalized = normalize_comparison(op, lhs, rhs);
654+
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized {
655+
val
656+
} else {
657+
return None;
660658
};
661659

662660
let lx = detect_extreme_expr(cx, normalized_lhs);
@@ -679,6 +677,7 @@ fn detect_absurd_comparison<'a>(cx: &LateContext, op: BinOp_, lhs: &'a Expr, rhs
679677
_ => return None,
680678
}
681679
}
680+
Rel::Ne | Rel::Eq => return None,
682681
})
683682
}
684683

@@ -778,3 +777,178 @@ impl LateLintPass for AbsurdExtremeComparisons {
778777
}
779778
}
780779
}
780+
781+
/// **What it does:** This lint checks for comparisons where the relation is always either true or false, but where one side has been upcast so that the comparison is necessary. Only integer types are checked.
782+
///
783+
/// **Why is this bad?** An expression like `let x : u8 = ...; (x as u32) > 300` will mistakenly imply that it is possible for `x` to be outside the range of `u8`.
784+
///
785+
/// **Known problems:** None
786+
///
787+
/// **Example:** `let x : u8 = ...; (x as u32) > 300`
788+
declare_lint! {
789+
pub INVALID_UPCAST_COMPARISONS, Warn,
790+
"a comparison involving an upcast which is always true or false"
791+
}
792+
793+
pub struct InvalidUpcastComparisons;
794+
795+
impl LintPass for InvalidUpcastComparisons {
796+
fn get_lints(&self) -> LintArray {
797+
lint_array!(INVALID_UPCAST_COMPARISONS)
798+
}
799+
}
800+
801+
#[derive(Copy, Clone, Debug, Eq)]
802+
enum FullInt {
803+
S(i64),
804+
U(u64),
805+
}
806+
807+
impl FullInt {
808+
#[allow(cast_sign_loss)]
809+
fn cmp_s_u(s: i64, u: u64) -> Ordering {
810+
if s < 0 {
811+
Ordering::Less
812+
} else if u > (i64::max_value() as u64) {
813+
Ordering::Greater
814+
} else {
815+
(s as u64).cmp(&u)
816+
}
817+
}
818+
}
819+
820+
impl PartialEq for FullInt {
821+
fn eq(&self, other: &Self) -> bool {
822+
self.partial_cmp(other).expect("partial_cmp only returns Some(_)") == Ordering::Equal
823+
}
824+
}
825+
826+
impl PartialOrd for FullInt {
827+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
828+
Some(match (self, other) {
829+
(&FullInt::S(s), &FullInt::S(o)) => s.cmp(&o),
830+
(&FullInt::U(s), &FullInt::U(o)) => s.cmp(&o),
831+
(&FullInt::S(s), &FullInt::U(o)) => Self::cmp_s_u(s, o),
832+
(&FullInt::U(s), &FullInt::S(o)) => Self::cmp_s_u(o, s).reverse(),
833+
})
834+
}
835+
}
836+
impl Ord for FullInt {
837+
fn cmp(&self, other: &Self) -> Ordering {
838+
self.partial_cmp(other).expect("partial_cmp for FullInt can never return None")
839+
}
840+
}
841+
842+
843+
fn numeric_cast_precast_bounds<'a>(cx: &LateContext, expr: &'a Expr) -> Option<(FullInt, FullInt)> {
844+
use rustc::ty::TypeVariants::{TyInt, TyUint};
845+
use syntax::ast::{IntTy, UintTy};
846+
use std::*;
847+
848+
if let ExprCast(ref cast_exp,_) = expr.node {
849+
match cx.tcx.expr_ty(cast_exp).sty {
850+
TyInt(int_ty) => Some(match int_ty {
851+
IntTy::I8 => (FullInt::S(i8::min_value() as i64), FullInt::S(i8::max_value() as i64)),
852+
IntTy::I16 => (FullInt::S(i16::min_value() as i64), FullInt::S(i16::max_value() as i64)),
853+
IntTy::I32 => (FullInt::S(i32::min_value() as i64), FullInt::S(i32::max_value() as i64)),
854+
IntTy::I64 => (FullInt::S(i64::min_value() as i64), FullInt::S(i64::max_value() as i64)),
855+
IntTy::Is => (FullInt::S(isize::min_value() as i64), FullInt::S(isize::max_value() as i64)),
856+
}),
857+
TyUint(uint_ty) => Some(match uint_ty {
858+
UintTy::U8 => (FullInt::U(u8::min_value() as u64), FullInt::U(u8::max_value() as u64)),
859+
UintTy::U16 => (FullInt::U(u16::min_value() as u64), FullInt::U(u16::max_value() as u64)),
860+
UintTy::U32 => (FullInt::U(u32::min_value() as u64), FullInt::U(u32::max_value() as u64)),
861+
UintTy::U64 => (FullInt::U(u64::min_value() as u64), FullInt::U(u64::max_value() as u64)),
862+
UintTy::Us => (FullInt::U(usize::min_value() as u64), FullInt::U(usize::max_value() as u64)),
863+
}),
864+
_ => None,
865+
}
866+
} else {
867+
None
868+
}
869+
}
870+
871+
fn node_as_const_fullint(cx: &LateContext, expr: &Expr) -> Option<FullInt> {
872+
use rustc::middle::const_val::ConstVal::*;
873+
use rustc_const_eval::EvalHint::ExprTypeChecked;
874+
use rustc_const_eval::eval_const_expr_partial;
875+
use rustc_const_math::ConstInt;
876+
877+
match eval_const_expr_partial(cx.tcx, expr, ExprTypeChecked, None) {
878+
Ok(val) => {
879+
if let Integral(const_int) = val {
880+
Some(match const_int.erase_type() {
881+
ConstInt::InferSigned(x) => FullInt::S(x as i64),
882+
ConstInt::Infer(x) => FullInt::U(x as u64),
883+
_ => unreachable!(),
884+
})
885+
} else {
886+
None
887+
}
888+
},
889+
Err(_) => None,
890+
}
891+
}
892+
893+
fn err_upcast_comparison(cx: &LateContext, span: &Span, expr: &Expr, always: bool) {
894+
if let ExprCast(ref cast_val, _) = expr.node {
895+
span_lint(
896+
cx,
897+
INVALID_UPCAST_COMPARISONS,
898+
*span,
899+
&format!(
900+
"because of the numeric bounds on `{}` prior to casting, this expression is always {}",
901+
snippet(cx, cast_val.span, "the expression"),
902+
if always { "true" } else { "false" },
903+
)
904+
);
905+
}
906+
}
907+
908+
fn upcast_comparison_bounds_err(
909+
cx: &LateContext, span: &Span, rel: comparisons::Rel,
910+
lhs_bounds: Option<(FullInt, FullInt)>, lhs: &Expr, rhs: &Expr, invert: bool) {
911+
use utils::comparisons::*;
912+
913+
if let Some((lb, ub)) = lhs_bounds {
914+
if let Some(norm_rhs_val) = node_as_const_fullint(cx, rhs) {
915+
if rel == Rel::Eq || rel == Rel::Ne {
916+
if norm_rhs_val < lb || norm_rhs_val > ub {
917+
err_upcast_comparison(cx, &span, lhs, rel == Rel::Ne);
918+
}
919+
} else if match rel {
920+
Rel::Lt => if invert { norm_rhs_val < lb } else { ub < norm_rhs_val },
921+
Rel::Le => if invert { norm_rhs_val <= lb } else { ub <= norm_rhs_val },
922+
Rel::Eq | Rel::Ne => unreachable!(),
923+
} {
924+
err_upcast_comparison(cx, &span, lhs, true)
925+
} else if match rel {
926+
Rel::Lt => if invert { norm_rhs_val >= ub } else { lb >= norm_rhs_val },
927+
Rel::Le => if invert { norm_rhs_val > ub } else { lb > norm_rhs_val },
928+
Rel::Eq | Rel::Ne => unreachable!(),
929+
} {
930+
err_upcast_comparison(cx, &span, lhs, false)
931+
}
932+
}
933+
}
934+
}
935+
936+
impl LateLintPass for InvalidUpcastComparisons {
937+
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
938+
if let ExprBinary(ref cmp, ref lhs, ref rhs) = expr.node {
939+
940+
let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs);
941+
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized {
942+
val
943+
} else {
944+
return;
945+
};
946+
947+
let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs);
948+
let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs);
949+
950+
upcast_comparison_bounds_err(cx, &expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false);
951+
upcast_comparison_bounds_err(cx, &expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true);
952+
}
953+
}
954+
}

src/utils/comparisons.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use rustc_front::hir::{BinOp_, Expr};
2+
3+
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
4+
pub enum Rel {
5+
Lt,
6+
Le,
7+
Eq,
8+
Ne,
9+
}
10+
11+
/// Put the expression in the form `lhs < rhs` or `lhs <= rhs`.
12+
pub fn normalize_comparison<'a>(op: BinOp_, lhs: &'a Expr, rhs: &'a Expr)
13+
-> Option<(Rel, &'a Expr, &'a Expr)> {
14+
match op {
15+
BinOp_::BiLt => Some((Rel::Lt, lhs, rhs)),
16+
BinOp_::BiLe => Some((Rel::Le, lhs, rhs)),
17+
BinOp_::BiGt => Some((Rel::Lt, rhs, lhs)),
18+
BinOp_::BiGe => Some((Rel::Le, rhs, lhs)),
19+
BinOp_::BiEq => Some((Rel::Eq, rhs, lhs)),
20+
BinOp_::BiNe => Some((Rel::Ne, rhs, lhs)),
21+
_ => None,
22+
}
23+
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use syntax::codemap::{ExpnInfo, Span, ExpnFormat};
1919
use syntax::errors::DiagnosticBuilder;
2020
use syntax::ptr::P;
2121

22+
pub mod comparisons;
2223
pub mod conf;
2324
mod hir;
2425
pub use self::hir::{SpanlessEq, SpanlessHash};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![deny(invalid_upcast_comparisons)]
5+
#![allow(unused, eq_op, no_effect)]
6+
fn main() {
7+
let zero: u32 = 0;
8+
let u8_max: u8 = 255;
9+
10+
(u8_max as u32) > 300; //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always false
11+
(u8_max as u32) > 20;
12+
13+
(zero as i32) < -5; //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false
14+
(zero as i32) < 10;
15+
16+
-5 < (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always true
17+
0 <= (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always true
18+
0 < (zero as i32);
19+
20+
-5 > (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false
21+
-5 >= (u8_max as i32); //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always false
22+
1337 == (u8_max as i32); //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always false
23+
24+
-5 == (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false
25+
-5 != (u8_max as i32); //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always true
26+
27+
// Those are Ok:
28+
42 == (u8_max as i32);
29+
42 != (u8_max as i32);
30+
42 > (u8_max as i32);
31+
(u8_max as i32) == 42;
32+
(u8_max as i32) != 42;
33+
(u8_max as i32) > 42;
34+
(u8_max as i32) < 42;
35+
}

0 commit comments

Comments
 (0)