-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Bad Comparison of Upcasted Ints Fix #42 #802
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
Changes from all commits
ae98abd
238800d
8df0e07
95e680d
516194d
8d73acb
8ee9d39
1d73411
bc7f0f0
cba9637
2579023
de03684
99ef27b
06b23d8
e386874
e54bfda
dce1327
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,15 @@ use reexport::*; | |
use rustc::lint::*; | ||
use rustc::middle::def; | ||
use rustc::ty; | ||
use rustc::middle::const_val::ConstVal::Integral; | ||
use rustc_front::hir::*; | ||
use rustc_front::intravisit::{FnKind, Visitor, walk_ty}; | ||
use rustc_front::util::{is_comparison_binop, binop_to_string}; | ||
use syntax::ast::{IntTy, UintTy, FloatTy}; | ||
use syntax::codemap::Span; | ||
use utils::*; | ||
|
||
|
||
/// Handles all the linting of funky types | ||
#[allow(missing_copy_implementations)] | ||
pub struct TypePass; | ||
|
@@ -640,23 +642,20 @@ enum AbsurdComparisonResult { | |
InequalityImpossible, | ||
} | ||
|
||
|
||
|
||
fn detect_absurd_comparison<'a>(cx: &LateContext, op: BinOp_, lhs: &'a Expr, rhs: &'a Expr) | ||
-> Option<(ExtremeExpr<'a>, AbsurdComparisonResult)> { | ||
use types::ExtremeType::*; | ||
use types::AbsurdComparisonResult::*; | ||
use utils::comparisons::*; | ||
type Extr<'a> = ExtremeExpr<'a>; | ||
|
||
// Put the expression in the form lhs < rhs or lhs <= rhs. | ||
enum Rel { | ||
Lt, | ||
Le, | ||
}; | ||
let (rel, normalized_lhs, normalized_rhs) = match op { | ||
BiLt => (Rel::Lt, lhs, rhs), | ||
BiLe => (Rel::Le, lhs, rhs), | ||
BiGt => (Rel::Lt, rhs, lhs), | ||
BiGe => (Rel::Le, rhs, lhs), | ||
_ => return None, | ||
let normalized = normalize_comparison(op, lhs, rhs); | ||
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized { | ||
val | ||
} else { | ||
return None; | ||
}; | ||
|
||
let lx = detect_extreme_expr(cx, normalized_lhs); | ||
|
@@ -679,6 +678,7 @@ fn detect_absurd_comparison<'a>(cx: &LateContext, op: BinOp_, lhs: &'a Expr, rhs | |
_ => return None, | ||
} | ||
} | ||
Rel::Ne | Rel::Eq => return None, | ||
}) | ||
} | ||
|
||
|
@@ -778,3 +778,180 @@ impl LateLintPass for AbsurdExtremeComparisons { | |
} | ||
} | ||
} | ||
|
||
/// **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. | ||
/// | ||
/// **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`. | ||
/// | ||
/// **Known problems:** None | ||
/// | ||
/// **Example:** `let x : u8 = ...; (x as u32) > 300` | ||
declare_lint! { | ||
pub INVALID_UPCAST_COMPARISONS, Warn, | ||
"a comparison involving an upcast which is always true or false" | ||
} | ||
|
||
pub struct InvalidUpcastComparisons; | ||
|
||
impl LintPass for InvalidUpcastComparisons { | ||
fn get_lints(&self) -> LintArray { | ||
lint_array!(INVALID_UPCAST_COMPARISONS) | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, Eq)] | ||
enum FullInt { | ||
S(i64), | ||
U(u64), | ||
} | ||
|
||
use std::cmp::Ordering; | ||
|
||
impl FullInt { | ||
#[allow(cast_sign_loss)] | ||
fn cmp_s_u(s: i64, u: u64) -> Ordering { | ||
if s < 0 { | ||
Ordering::Less | ||
} else if u > (i64::max_value() as u64) { | ||
Ordering::Greater | ||
} else { | ||
(s as u64).cmp(&u) | ||
} | ||
} | ||
} | ||
|
||
impl PartialEq for FullInt { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.partial_cmp(other).expect("partial_cmp only returns Some(_)") == Ordering::Equal | ||
} | ||
} | ||
|
||
impl PartialOrd for FullInt { | ||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||
Some(match (self, other) { | ||
(&FullInt::S(s), &FullInt::S(o)) => s.cmp(&o), | ||
(&FullInt::U(s), &FullInt::U(o)) => s.cmp(&o), | ||
(&FullInt::S(s), &FullInt::U(o)) => Self::cmp_s_u(s, o), | ||
(&FullInt::U(s), &FullInt::S(o)) => Self::cmp_s_u(o, s).reverse(), | ||
}) | ||
} | ||
} | ||
impl Ord for FullInt { | ||
fn cmp(&self, other: &Self) -> Ordering { | ||
self.partial_cmp(other).expect("partial_cmp for FullInt can never return None") | ||
} | ||
} | ||
|
||
|
||
fn numeric_cast_precast_bounds<'a>(cx: &LateContext, expr: &'a Expr) -> Option<(FullInt, FullInt)> { | ||
use rustc::ty::TypeVariants::{TyInt, TyUint}; | ||
use syntax::ast::UintTy; | ||
use syntax::ast::IntTy; | ||
use std::*; | ||
|
||
if let ExprCast(ref cast_exp,_) = expr.node { | ||
match cx.tcx.expr_ty(cast_exp).sty { | ||
TyInt(int_ty) => Some(match int_ty { | ||
IntTy::I8 => (FullInt::S(i8::min_value() as i64), FullInt::S(i8::max_value() as i64)), | ||
IntTy::I16 => (FullInt::S(i16::min_value() as i64), FullInt::S(i16::max_value() as i64)), | ||
IntTy::I32 => (FullInt::S(i32::min_value() as i64), FullInt::S(i32::max_value() as i64)), | ||
IntTy::I64 => (FullInt::S(i64::min_value() as i64), FullInt::S(i64::max_value() as i64)), | ||
IntTy::Is => (FullInt::S(isize::min_value() as i64), FullInt::S(isize::max_value() as i64)), | ||
}), | ||
TyUint(uint_ty) => Some(match uint_ty { | ||
UintTy::U8 => (FullInt::U(u8::min_value() as u64), FullInt::U(u8::max_value() as u64)), | ||
UintTy::U16 => (FullInt::U(u16::min_value() as u64), FullInt::U(u16::max_value() as u64)), | ||
UintTy::U32 => (FullInt::U(u32::min_value() as u64), FullInt::U(u32::max_value() as u64)), | ||
UintTy::U64 => (FullInt::U(u64::min_value() as u64), FullInt::U(u64::max_value() as u64)), | ||
UintTy::Us => (FullInt::U(usize::min_value() as u64), FullInt::U(usize::max_value() as u64)), | ||
}), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn node_as_const_fullint(cx: &LateContext, expr: &Expr) -> Option<FullInt> { | ||
use rustc_const_eval::EvalHint::ExprTypeChecked; | ||
use rustc_const_eval::*; | ||
use rustc_const_math::ConstInt::{InferSigned, Infer}; | ||
|
||
match eval_const_expr_partial(cx.tcx, expr, ExprTypeChecked, None) { | ||
Ok(val) => { | ||
if let Integral(const_int) = val { | ||
Some(match const_int.erase_type() { | ||
InferSigned(x) => FullInt::S(x as i64), | ||
Infer(x) => FullInt::U(x as u64), | ||
_ => unreachable!(), | ||
}) | ||
} else { | ||
None | ||
} | ||
}, | ||
Err(_) => None, | ||
} | ||
} | ||
|
||
fn err_upcast_comparison(cx: &LateContext, span: &Span, expr: &Expr, always: bool) { | ||
if let ExprCast(ref cast_val, _) = expr.node { | ||
span_lint( | ||
cx, | ||
INVALID_UPCAST_COMPARISONS, | ||
*span, | ||
&format!( | ||
"because of the numeric bounds on `{}` prior to casting, this expression is always {}", | ||
snippet(cx, cast_val.span, "the expression"), | ||
if always { "true" } else { "false" }, | ||
) | ||
); | ||
} | ||
} | ||
|
||
fn upcast_comparison_bounds_err( | ||
cx: &LateContext, span: &Span, rel: comparisons::Rel, | ||
lhs_bounds: Option<(FullInt, FullInt)>, lhs: &Expr, rhs: &Expr, invert: bool) { | ||
use utils::comparisons::*; | ||
|
||
if let Some(nlb) = lhs_bounds { | ||
if let Some(norm_rhs_val) = node_as_const_fullint(cx, rhs) { | ||
if rel == Rel::Eq || rel == Rel::Ne { | ||
if norm_rhs_val < nlb.0 || norm_rhs_val > nlb.0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was also a small mistake here, it should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I appreciate your help. |
||
err_upcast_comparison(cx, &span, lhs, rel == Rel::Ne); | ||
} | ||
} else if match rel { | ||
Rel::Lt => if invert { norm_rhs_val < nlb.0 } else { nlb.1 < norm_rhs_val }, | ||
Rel::Le => if invert { norm_rhs_val <= nlb.0 } else { nlb.1 <= norm_rhs_val }, | ||
Rel::Eq | Rel::Ne => unreachable!(), | ||
} { | ||
err_upcast_comparison(cx, &span, lhs, true) | ||
} else if match rel { | ||
Rel::Lt => if invert { norm_rhs_val >= nlb.1 } else { nlb.0 >= norm_rhs_val }, | ||
Rel::Le => if invert { norm_rhs_val > nlb.1 } else { nlb.0 > norm_rhs_val }, | ||
Rel::Eq | Rel::Ne => unreachable!(), | ||
} { | ||
err_upcast_comparison(cx, &span, lhs, false) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl LateLintPass for InvalidUpcastComparisons { | ||
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) { | ||
if let ExprBinary(ref cmp, ref lhs, ref rhs) = expr.node { | ||
|
||
let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs); | ||
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized { | ||
val | ||
} else { | ||
return; | ||
}; | ||
|
||
let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs); | ||
let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs); | ||
|
||
upcast_comparison_bounds_err(cx, &expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false); | ||
upcast_comparison_bounds_err(cx, &expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use rustc_front::hir::{BinOp_, Expr}; | ||
|
||
#[derive(PartialEq, Eq, Debug, Copy, Clone)] | ||
pub enum Rel { | ||
Lt, | ||
Le, | ||
Eq, | ||
Ne, | ||
} | ||
|
||
/// Put the expression in the form `lhs < rhs` or `lhs <= rhs`. | ||
pub fn normalize_comparison<'a>(op: BinOp_, lhs: &'a Expr, rhs: &'a Expr) | ||
-> Option<(Rel, &'a Expr, &'a Expr)> { | ||
match op { | ||
BinOp_::BiLt => Some((Rel::Lt, lhs, rhs)), | ||
BinOp_::BiLe => Some((Rel::Le, lhs, rhs)), | ||
BinOp_::BiGt => Some((Rel::Lt, rhs, lhs)), | ||
BinOp_::BiGe => Some((Rel::Le, rhs, lhs)), | ||
BinOp_::BiEq => Some((Rel::Eq, rhs, lhs)), | ||
BinOp_::BiNe => Some((Rel::Ne, rhs, lhs)), | ||
_ => None, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#![feature(plugin)] | ||
#![plugin(clippy)] | ||
|
||
#![deny(invalid_upcast_comparisons)] | ||
#![allow(unused, eq_op, no_effect)] | ||
fn main() { | ||
let zero: u32 = 0; | ||
let u8_max: u8 = 255; | ||
|
||
(u8_max as u32) > 300; //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always false | ||
(u8_max as u32) > 20; | ||
|
||
(zero as i32) < -5; //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false | ||
(zero as i32) < 10; | ||
|
||
-5 < (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always true | ||
0 <= (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always true | ||
0 < (zero as i32); | ||
|
||
-5 > (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false | ||
-5 >= (u8_max as i32); //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always false | ||
|
||
-5 == (zero as i32); //~ERROR because of the numeric bounds on `zero` prior to casting, this expression is always false | ||
-5 != (u8_max as i32); //~ERROR because of the numeric bounds on `u8_max` prior to casting, this expression is always true | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have tests with (u8_max as u32) == 1337; // always false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use the
erase_type
method to only have two kinds to handle here.