Skip to content

Commit 73367f8

Browse files
committed
Auto merge of rust-lang#8381 - Jarcho:cast_possible_truncation_542, r=Manishearth
Lint enum-to-int casts with `cast_possible_truncation` fixes: rust-lang#542 ~~This will not lint casting a specific variant to an integer. That really should be a new lint as it's definitely a truncation (other than `isize`/`usize` values).~~ changelog: Lint enum-to-int casts with `cast_possible_truncation` changelog: New lint `cast_enum_truncation`
2 parents 02f3c17 + 88ecdd0 commit 73367f8

File tree

11 files changed

+396
-41
lines changed

11 files changed

+396
-41
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3068,6 +3068,7 @@ Released 2018-09-13
30683068
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
30693069
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
30703070
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
3071+
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
30713072
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
30723073
[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
30733074
[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap

clippy_lints/src/casts/cast_possible_truncation.rs

+56-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint;
33
use clippy_utils::expr_or_init;
4-
use clippy_utils::ty::is_isize_or_usize;
4+
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
5+
use rustc_ast::ast;
6+
use rustc_attr::IntType;
7+
use rustc_hir::def::{DefKind, Res};
58
use rustc_hir::{BinOpKind, Expr, ExprKind};
69
use rustc_lint::LateContext;
710
use rustc_middle::ty::{self, FloatTy, Ty};
811

9-
use super::{utils, CAST_POSSIBLE_TRUNCATION};
12+
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
1013

1114
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
1215
if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
@@ -75,8 +78,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
7578
}
7679

7780
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
78-
let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
79-
(true, true) => {
81+
let msg = match (cast_from.kind(), cast_to.is_integral()) {
82+
(ty::Int(_) | ty::Uint(_), true) => {
8083
let from_nbits = apply_reductions(
8184
cx,
8285
utils::int_ty_to_nbits(cast_from, cx.tcx),
@@ -108,19 +111,60 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
108111
)
109112
},
110113

111-
(false, true) => {
112-
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
113-
},
114-
115-
(_, _) => {
116-
if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
117-
&& matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
114+
(ty::Adt(def, _), true) if def.is_enum() => {
115+
let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
116+
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
118117
{
119-
"casting `f64` to `f32` may truncate the value".to_string()
118+
let i = def.variant_index_with_ctor_id(id);
119+
let variant = &def.variants[i];
120+
let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, def, i));
121+
(nbits, Some(variant))
120122
} else {
123+
(utils::enum_ty_to_nbits(def, cx.tcx), None)
124+
};
125+
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
126+
127+
let cast_from_ptr_size = def.repr.int.map_or(true, |ty| {
128+
matches!(
129+
ty,
130+
IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
131+
)
132+
});
133+
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
134+
(false, false) if from_nbits > to_nbits => "",
135+
(true, false) if from_nbits > to_nbits => "",
136+
(false, true) if from_nbits > 64 => "",
137+
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
138+
_ => return,
139+
};
140+
141+
if let Some(variant) = variant {
142+
span_lint(
143+
cx,
144+
CAST_ENUM_TRUNCATION,
145+
expr.span,
146+
&format!(
147+
"casting `{}::{}` to `{}` will truncate the value{}",
148+
cast_from, variant.name, cast_to, suffix,
149+
),
150+
);
121151
return;
122152
}
153+
format!(
154+
"casting `{}` to `{}` may truncate the value{}",
155+
cast_from, cast_to, suffix,
156+
)
123157
},
158+
159+
(ty::Float(_), true) => {
160+
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
161+
},
162+
163+
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
164+
"casting `f64` to `f32` may truncate the value".to_string()
165+
},
166+
167+
_ => return,
124168
};
125169

126170
span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);

clippy_lints/src/casts/mod.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,25 @@ declare_clippy_lint! {
390390
"casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
391391
}
392392

393+
declare_clippy_lint! {
394+
/// ### What it does
395+
/// Checks for casts from an enum type to an integral type which will definitely truncate the
396+
/// value.
397+
///
398+
/// ### Why is this bad?
399+
/// The resulting integral value will not match the value of the variant it came from.
400+
///
401+
/// ### Example
402+
/// ```rust
403+
/// enum E { X = 256 };
404+
/// let _ = E::X as u8;
405+
/// ```
406+
#[clippy::version = "1.60.0"]
407+
pub CAST_ENUM_TRUNCATION,
408+
suspicious,
409+
"casts from an enum type to an integral type which will truncate the value"
410+
}
411+
393412
pub struct Casts {
394413
msrv: Option<RustcVersion>,
395414
}
@@ -415,6 +434,7 @@ impl_lint_pass!(Casts => [
415434
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
416435
CHAR_LIT_AS_U8,
417436
PTR_AS_PTR,
437+
CAST_ENUM_TRUNCATION,
418438
]);
419439

420440
impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -445,13 +465,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
445465
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
446466

447467
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
468+
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
448469
if cast_from.is_numeric() {
449-
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
450470
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
451471
cast_precision_loss::check(cx, expr, cast_from, cast_to);
452472
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
453473
}
454-
455474
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
456475
}
457476
}

clippy_lints/src/casts/utils.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
1+
use clippy_utils::ty::{read_explicit_enum_value, EnumValue};
2+
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
23

34
/// Returns the size in bits of an integral type.
45
/// Will return 0 if the type is not an int or uint variant
@@ -23,3 +24,52 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
2324
_ => 0,
2425
}
2526
}
27+
28+
pub(super) fn enum_value_nbits(value: EnumValue) -> u64 {
29+
match value {
30+
EnumValue::Unsigned(x) => 128 - x.leading_zeros(),
31+
EnumValue::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1,
32+
EnumValue::Signed(x) => 128 - x.leading_zeros(),
33+
}
34+
.into()
35+
}
36+
37+
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
38+
let mut explicit = 0i128;
39+
let (start, end) = adt
40+
.variants
41+
.iter()
42+
.fold((0, i128::MIN), |(start, end), variant| match variant.discr {
43+
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
44+
Some(x) => (start, end.max(x)),
45+
None => (i128::MIN, end),
46+
},
47+
VariantDiscr::Explicit(id) => match read_explicit_enum_value(tcx, id) {
48+
Some(EnumValue::Signed(x)) => {
49+
explicit = x;
50+
(start.min(x), end.max(x))
51+
},
52+
Some(EnumValue::Unsigned(x)) => match i128::try_from(x) {
53+
Ok(x) => {
54+
explicit = x;
55+
(start, end.max(x))
56+
},
57+
Err(_) => (i128::MIN, end),
58+
},
59+
None => (start, end),
60+
},
61+
});
62+
63+
if start > end {
64+
// No variants.
65+
0
66+
} else {
67+
let neg_bits = if start < 0 {
68+
128 - (-(start + 1)).leading_zeros() + 1
69+
} else {
70+
0
71+
};
72+
let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
73+
neg_bits.max(pos_bits).into()
74+
}
75+
}

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
2323
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
2424
LintId::of(booleans::LOGIC_BUG),
2525
LintId::of(booleans::NONMINIMAL_BOOL),
26+
LintId::of(casts::CAST_ENUM_TRUNCATION),
2627
LintId::of(casts::CAST_REF_TO_MUT),
2728
LintId::of(casts::CHAR_LIT_AS_U8),
2829
LintId::of(casts::FN_TO_NUMERIC_CAST),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ store.register_lints(&[
6767
cargo::REDUNDANT_FEATURE_NAMES,
6868
cargo::WILDCARD_DEPENDENCIES,
6969
case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
70+
casts::CAST_ENUM_TRUNCATION,
7071
casts::CAST_LOSSLESS,
7172
casts::CAST_POSSIBLE_TRUNCATION,
7273
casts::CAST_POSSIBLE_WRAP,

clippy_lints/src/lib.register_suspicious.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
77
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
88
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
99
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
10+
LintId::of(casts::CAST_ENUM_TRUNCATION),
1011
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
1112
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
1213
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),

clippy_lints/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
2626
extern crate rustc_ast;
2727
extern crate rustc_ast_pretty;
28+
extern crate rustc_attr;
2829
extern crate rustc_data_structures;
2930
extern crate rustc_driver;
3031
extern crate rustc_errors;

clippy_utils/src/ty.rs

+58-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use rustc_hir::def_id::DefId;
1010
use rustc_hir::{Expr, TyKind, Unsafety};
1111
use rustc_infer::infer::TyCtxtInferExt;
1212
use rustc_lint::LateContext;
13+
use rustc_middle::mir::interpret::{ConstValue, Scalar};
1314
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
1415
use rustc_middle::ty::{
15-
self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy,
16+
self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, VariantDiscr,
1617
};
1718
use rustc_span::symbol::Ident;
1819
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
20+
use rustc_target::abi::{Size, VariantIdx};
1921
use rustc_trait_selection::infer::InferCtxtExt;
2022
use rustc_trait_selection::traits::query::normalize::AtExt;
2123
use std::iter;
@@ -515,3 +517,58 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnS
515517
}
516518
}
517519
}
520+
521+
#[derive(Clone, Copy)]
522+
pub enum EnumValue {
523+
Unsigned(u128),
524+
Signed(i128),
525+
}
526+
impl core::ops::Add<u32> for EnumValue {
527+
type Output = Self;
528+
fn add(self, n: u32) -> Self::Output {
529+
match self {
530+
Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)),
531+
Self::Signed(x) => Self::Signed(x + i128::from(n)),
532+
}
533+
}
534+
}
535+
536+
/// Attempts to read the given constant as though it were an an enum value.
537+
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
538+
pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
539+
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
540+
match tcx.type_of(id).kind() {
541+
ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() {
542+
1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
543+
2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
544+
4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
545+
8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
546+
16 => value.assert_bits(Size::from_bytes(16)) as i128,
547+
_ => return None,
548+
})),
549+
ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() {
550+
1 => value.assert_bits(Size::from_bytes(1)),
551+
2 => value.assert_bits(Size::from_bytes(2)),
552+
4 => value.assert_bits(Size::from_bytes(4)),
553+
8 => value.assert_bits(Size::from_bytes(8)),
554+
16 => value.assert_bits(Size::from_bytes(16)),
555+
_ => return None,
556+
})),
557+
_ => None,
558+
}
559+
} else {
560+
None
561+
}
562+
}
563+
564+
/// Gets the value of the given variant.
565+
pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) -> EnumValue {
566+
let variant = &adt.variants[i];
567+
match variant.discr {
568+
VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(),
569+
VariantDiscr::Relative(x) => match adt.variants[(i.as_usize() - x as usize).into()].discr {
570+
VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x,
571+
VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()),
572+
},
573+
}
574+
}

0 commit comments

Comments
 (0)