Skip to content

Commit 90bb7a3

Browse files
committed
New lint cast_enum_truncation
1 parent 8a46645 commit 90bb7a3

10 files changed

+183
-52
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
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

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint;
33
use clippy_utils::expr_or_init;
44
use clippy_utils::ty::is_isize_or_usize;
5+
use rustc_ast::ast;
6+
use rustc_attr::IntType;
57
use rustc_hir::def::{DefKind, Res};
68
use rustc_hir::{BinOpKind, Expr, ExprKind};
79
use rustc_lint::LateContext;
8-
use rustc_middle::ty::{self, FloatTy, Ty};
10+
use rustc_middle::ty::{self, FloatTy, Ty, VariantDiscr};
911

10-
use super::{utils, CAST_POSSIBLE_TRUNCATION};
12+
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
1113

1214
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
1315
if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
@@ -110,27 +112,54 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
110112
},
111113

112114
(ty::Adt(def, _), true) if def.is_enum() => {
113-
if let ExprKind::Path(p) = &cast_expr.kind
114-
&& let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id)
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)
115117
{
116-
return
117-
}
118-
119-
let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx);
118+
let i = def.variant_index_with_ctor_id(id);
119+
let variant = &def.variants[i];
120+
let nbits: u64 = match variant.discr {
121+
VariantDiscr::Explicit(id) => utils::read_explicit_enum_value(cx.tcx, id).unwrap().nbits(),
122+
VariantDiscr::Relative(x) => {
123+
match def.variants[(i.as_usize() - x as usize).into()].discr {
124+
VariantDiscr::Explicit(id) => {
125+
utils::read_explicit_enum_value(cx.tcx, id).unwrap().add(x).nbits()
126+
}
127+
VariantDiscr::Relative(_) => (32 - x.leading_zeros()).into(),
128+
}
129+
}
130+
};
131+
(nbits, Some(variant))
132+
} else {
133+
(utils::enum_ty_to_nbits(def, cx.tcx), None)
134+
};
120135
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
121136

122-
let suffix = if is_isize_or_usize(cast_to) {
123-
if from_nbits > 32 {
124-
" on targets with 32-bit wide pointers"
125-
} else {
126-
return;
127-
}
128-
} else if to_nbits < from_nbits {
129-
""
130-
} else {
131-
return;
137+
let cast_from_ptr_size = def.repr.int.map_or(true, |ty| {
138+
matches!(
139+
ty,
140+
IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
141+
)
142+
});
143+
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
144+
(false, false) if from_nbits > to_nbits => "",
145+
(true, false) if from_nbits > to_nbits => "",
146+
(false, true) if from_nbits > 64 => "",
147+
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
148+
_ => return,
132149
};
133150

151+
if let Some(variant) = variant {
152+
span_lint(
153+
cx,
154+
CAST_ENUM_TRUNCATION,
155+
expr.span,
156+
&format!(
157+
"casting `{}::{}` to `{}` will truncate the value{}",
158+
cast_from, variant.name, cast_to, suffix,
159+
),
160+
);
161+
return;
162+
}
134163
format!(
135164
"casting `{}` to `{}` may truncate the value{}",
136165
cast_from, cast_to, suffix,

clippy_lints/src/casts/mod.rs

Lines changed: 20 additions & 0 deletions
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 {

clippy_lints/src/casts/utils.rs

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use rustc_middle::mir::interpret::{ConstValue, Scalar};
22
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
3+
use rustc_span::def_id::DefId;
34
use rustc_target::abi::Size;
45

56
/// Returns the size in bits of an integral type.
@@ -26,48 +27,83 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
2627
}
2728
}
2829

30+
pub(super) enum EnumValue {
31+
Unsigned(u128),
32+
Signed(i128),
33+
}
34+
impl EnumValue {
35+
pub(super) fn add(self, n: u32) -> Self {
36+
match self {
37+
Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)),
38+
Self::Signed(x) => Self::Signed(x + i128::from(n)),
39+
}
40+
}
41+
42+
pub(super) fn nbits(self) -> u64 {
43+
match self {
44+
Self::Unsigned(x) => 128 - x.leading_zeros(),
45+
Self::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1,
46+
Self::Signed(x) => 128 - x.leading_zeros(),
47+
}
48+
.into()
49+
}
50+
}
51+
52+
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
53+
pub(super) fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
54+
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
55+
match tcx.type_of(id).kind() {
56+
ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() {
57+
1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
58+
2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
59+
4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
60+
8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
61+
16 => value.assert_bits(Size::from_bytes(16)) as i128,
62+
_ => return None,
63+
})),
64+
ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() {
65+
1 => value.assert_bits(Size::from_bytes(1)),
66+
2 => value.assert_bits(Size::from_bytes(2)),
67+
4 => value.assert_bits(Size::from_bytes(4)),
68+
8 => value.assert_bits(Size::from_bytes(8)),
69+
16 => value.assert_bits(Size::from_bytes(16)),
70+
_ => return None,
71+
})),
72+
_ => None,
73+
}
74+
} else {
75+
None
76+
}
77+
}
78+
2979
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
3080
let mut explicit = 0i128;
3181
let (start, end) = adt
3282
.variants
3383
.iter()
34-
.fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr {
84+
.fold((0, i128::MIN), |(start, end), variant| match variant.discr {
3585
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
3686
Some(x) => (start, end.max(x)),
3787
None => (i128::MIN, end),
3888
},
39-
VariantDiscr::Explicit(id) => {
40-
let ty = tcx.type_of(id);
41-
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
42-
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
43-
let value = match (value.size().bytes(), ty.kind()) {
44-
(1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
45-
(1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8),
46-
(2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
47-
(2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16),
48-
(4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
49-
(4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32),
50-
(8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
51-
(8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64),
52-
(16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128,
53-
(16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) {
54-
Ok(x) => x,
55-
// Requires 128 bits
56-
Err(_) => return (i128::MIN, end),
57-
},
58-
// Shouldn't happen if compilation was successful
59-
_ => return (start, end),
60-
};
61-
explicit = value;
62-
(start.min(value), end.max(value))
63-
} else {
64-
// Shouldn't happen if compilation was successful
65-
(start, end)
66-
}
89+
VariantDiscr::Explicit(id) => match read_explicit_enum_value(tcx, id) {
90+
Some(EnumValue::Signed(x)) => {
91+
explicit = x;
92+
(start.min(x), end.max(x))
93+
},
94+
Some(EnumValue::Unsigned(x)) => match i128::try_from(x) {
95+
Ok(x) => {
96+
explicit = x;
97+
(start, end.max(x))
98+
},
99+
Err(_) => (i128::MIN, end),
100+
},
101+
None => (start, end),
67102
},
68103
});
69104

70-
if start >= end {
105+
if start > end {
106+
// No variants.
71107
0
72108
} else {
73109
let neg_bits = if start < 0 {

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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;

tests/ui/cast.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ fn main() {
139139
impl E2 {
140140
fn test(self) {
141141
let _ = self as u8;
142+
let _ = Self::B as u8;
142143
let _ = self as i16; // Don't lint. `255..=256` fits in i16
144+
let _ = Self::A as u8; // Don't lint.
143145
}
144146
}
145147

@@ -174,7 +176,9 @@ fn main() {
174176
impl E5 {
175177
fn test(self) {
176178
let _ = self as i8;
179+
let _ = Self::A as i8;
177180
let _ = self as i16; // Don't lint. `-129..=127` fits in i16
181+
let _ = Self::B as u8; // Don't lint.
178182
}
179183
}
180184

@@ -189,6 +193,7 @@ fn main() {
189193
let _ = self as i16;
190194
let _ = Self::A as u16; // Don't lint. `2^16-1` fits in u16
191195
let _ = self as u32; // Don't lint. `2^16-1..=2^16` fits in u32
196+
let _ = Self::A as u16; // Don't lint.
192197
}
193198
}
194199

@@ -201,6 +206,7 @@ fn main() {
201206
impl E7 {
202207
fn test(self) {
203208
let _ = self as usize;
209+
let _ = Self::A as usize; // Don't lint.
204210
let _ = self as u64; // Don't lint. `2^32-1..=2^32` fits in u64
205211
}
206212
}
@@ -227,7 +233,22 @@ fn main() {
227233
}
228234
impl E9 {
229235
fn test(self) {
236+
let _ = Self::A as u8; // Don't lint.
230237
let _ = self as u128; // Don't lint. `0..=2^128-1` fits in u128
231238
}
232239
}
240+
241+
#[derive(Clone, Copy)]
242+
#[repr(usize)]
243+
enum E10 {
244+
A,
245+
B = u32::MAX as usize,
246+
}
247+
impl E10 {
248+
fn test(self) {
249+
let _ = self as u16;
250+
let _ = Self::B as u32; // Don't lint.
251+
let _ = self as u64; // Don't lint.
252+
}
253+
}
233254
}

tests/ui/cast.stderr

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,43 @@ error: casting `main::E2` to `u8` may truncate the value
156156
LL | let _ = self as u8;
157157
| ^^^^^^^^^^
158158

159+
error: casting `main::E2::B` to `u8` will truncate the value
160+
--> $DIR/cast.rs:142:21
161+
|
162+
LL | let _ = Self::B as u8;
163+
| ^^^^^^^^^^^^^
164+
|
165+
= note: `-D clippy::cast-enum-truncation` implied by `-D warnings`
166+
159167
error: casting `main::E5` to `i8` may truncate the value
160-
--> $DIR/cast.rs:176:21
168+
--> $DIR/cast.rs:178:21
161169
|
162170
LL | let _ = self as i8;
163171
| ^^^^^^^^^^
164172

173+
error: casting `main::E5::A` to `i8` will truncate the value
174+
--> $DIR/cast.rs:179:21
175+
|
176+
LL | let _ = Self::A as i8;
177+
| ^^^^^^^^^^^^^
178+
165179
error: casting `main::E6` to `i16` may truncate the value
166-
--> $DIR/cast.rs:189:21
180+
--> $DIR/cast.rs:193:21
167181
|
168182
LL | let _ = self as i16;
169183
| ^^^^^^^^^^^
170184

171185
error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers
172-
--> $DIR/cast.rs:203:21
186+
--> $DIR/cast.rs:208:21
173187
|
174188
LL | let _ = self as usize;
175189
| ^^^^^^^^^^^^^
176190

177-
error: aborting due to 28 previous errors
191+
error: casting `main::E10` to `u16` may truncate the value
192+
--> $DIR/cast.rs:249:21
193+
|
194+
LL | let _ = self as u16;
195+
| ^^^^^^^^^^^
196+
197+
error: aborting due to 31 previous errors
178198

0 commit comments

Comments
 (0)