Lint enum-to-int casts with cast_possible_truncation

This commit is contained in:
Jason Newcomb 2022-01-31 15:45:49 -05:00
parent 02f3c17593
commit 8a466454ab
5 changed files with 258 additions and 39 deletions

View file

@ -2,6 +2,7 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::expr_or_init;
use clippy_utils::ty::is_isize_or_usize;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
@ -75,8 +76,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
(true, true) => {
let msg = match (cast_from.kind(), cast_to.is_integral()) {
(ty::Int(_) | ty::Uint(_), true) => {
let from_nbits = apply_reductions(
cx,
utils::int_ty_to_nbits(cast_from, cx.tcx),
@ -108,19 +109,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
)
},
(false, true) => {
(ty::Adt(def, _), true) if def.is_enum() => {
if let ExprKind::Path(p) = &cast_expr.kind
&& let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id)
{
return
}
let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
let suffix = if is_isize_or_usize(cast_to) {
if from_nbits > 32 {
" on targets with 32-bit wide pointers"
} else {
return;
}
} else if to_nbits < from_nbits {
""
} else {
return;
};
format!(
"casting `{}` to `{}` may truncate the value{}",
cast_from, cast_to, suffix,
)
},
(ty::Float(_), true) => {
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
},
(_, _) => {
if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
&& matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
{
"casting `f64` to `f32` may truncate the value".to_string()
} else {
return;
}
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
"casting `f64` to `f32` may truncate the value".to_string()
},
_ => return,
};
span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);

View file

@ -445,13 +445,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
if cast_from.is_numeric() {
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
cast_precision_loss::check(cx, expr, cast_from, cast_to);
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
}
}

View file

@ -1,4 +1,6 @@
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
use rustc_middle::mir::interpret::{ConstValue, Scalar};
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
use rustc_target::abi::Size;
/// Returns the size in bits of an integral type.
/// Will return 0 if the type is not an int or uint variant
@ -23,3 +25,57 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
_ => 0,
}
}
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
let mut explicit = 0i128;
let (start, end) = adt
.variants
.iter()
.fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr {
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
Some(x) => (start, end.max(x)),
None => (i128::MIN, end),
},
VariantDiscr::Explicit(id) => {
let ty = tcx.type_of(id);
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let value = match (value.size().bytes(), ty.kind()) {
(1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
(1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8),
(2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
(2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16),
(4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
(4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32),
(8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
(8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64),
(16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128,
(16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) {
Ok(x) => x,
// Requires 128 bits
Err(_) => return (i128::MIN, end),
},
// Shouldn't happen if compilation was successful
_ => return (start, end),
};
explicit = value;
(start.min(value), end.max(value))
} else {
// Shouldn't happen if compilation was successful
(start, end)
}
},
});
if start >= end {
0
} else {
let neg_bits = if start < 0 {
128 - (-(start + 1)).leading_zeros() + 1
} else {
0
};
let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
neg_bits.max(pos_bits).into()
}
}