]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/casts/cast_possible_truncation.rs
Auto merge of #8649 - ebobrow:imperative_find, r=flip1995
[rust.git] / clippy_lints / src / casts / cast_possible_truncation.rs
1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::span_lint;
3 use clippy_utils::expr_or_init;
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};
8 use rustc_hir::{BinOpKind, Expr, ExprKind};
9 use rustc_lint::LateContext;
10 use rustc_middle::ty::{self, FloatTy, Ty};
11
12 use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
13
14 fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
15     if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
16         Some(c)
17     } else {
18         None
19     }
20 }
21
22 fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u64> {
23     constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros()))
24 }
25
26 fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: bool) -> u64 {
27     match expr_or_init(cx, expr).kind {
28         ExprKind::Cast(inner, _) => apply_reductions(cx, nbits, inner, signed),
29         ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
30         ExprKind::Binary(op, left, right) => match op.node {
31             BinOpKind::Div => {
32                 apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
33                     // let's be conservative here
34                     0
35                 } else {
36                     // by dividing by 1, we remove 0 bits, etc.
37                     get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
38                 })
39             },
40             BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
41                 .unwrap_or(u64::max_value())
42                 .min(apply_reductions(cx, nbits, left, signed)),
43             BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
44                 .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
45             _ => nbits,
46         },
47         ExprKind::MethodCall(method, [left, right], _) => {
48             if signed {
49                 return nbits;
50             }
51             let max_bits = if method.ident.as_str() == "min" {
52                 get_constant_bits(cx, right)
53             } else {
54                 None
55             };
56             apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
57         },
58         ExprKind::MethodCall(method, [_, lo, hi], _) => {
59             if method.ident.as_str() == "clamp" {
60                 //FIXME: make this a diagnostic item
61                 if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
62                     return lo_bits.max(hi_bits);
63                 }
64             }
65             nbits
66         },
67         ExprKind::MethodCall(method, [_value], _) => {
68             if method.ident.name.as_str() == "signum" {
69                 0 // do not lint if cast comes from a `signum` function
70             } else {
71                 nbits
72             }
73         },
74         _ => nbits,
75     }
76 }
77
78 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
79     let msg = match (cast_from.kind(), cast_to.is_integral()) {
80         (ty::Int(_) | ty::Uint(_), true) => {
81             let from_nbits = apply_reductions(
82                 cx,
83                 utils::int_ty_to_nbits(cast_from, cx.tcx),
84                 cast_expr,
85                 cast_from.is_signed(),
86             );
87             let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
88
89             let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
90                 (true, true) | (false, false) => (to_nbits < from_nbits, ""),
91                 (true, false) => (
92                     to_nbits <= 32,
93                     if to_nbits == 32 {
94                         " on targets with 64-bit wide pointers"
95                     } else {
96                         ""
97                     },
98                 ),
99                 (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"),
100             };
101
102             if !should_lint {
103                 return;
104             }
105
106             format!(
107                 "casting `{}` to `{}` may truncate the value{}",
108                 cast_from, cast_to, suffix,
109             )
110         },
111
112         (ty::Adt(def, _), true) if def.is_enum() => {
113             let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
114                 && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
115             {
116                 let i = def.variant_index_with_ctor_id(id);
117                 let variant = def.variant(i);
118                 let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
119                 (nbits, Some(variant))
120             } else {
121                 (utils::enum_ty_to_nbits(*def, cx.tcx), None)
122             };
123             let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
124
125             let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
126                 matches!(
127                     ty,
128                     IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
129                 )
130             });
131             let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
132                 (false, false) if from_nbits > to_nbits => "",
133                 (true, false) if from_nbits > to_nbits => "",
134                 (false, true) if from_nbits > 64 => "",
135                 (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
136                 _ => return,
137             };
138
139             if let Some(variant) = variant {
140                 span_lint(
141                     cx,
142                     CAST_ENUM_TRUNCATION,
143                     expr.span,
144                     &format!(
145                         "casting `{}::{}` to `{}` will truncate the value{}",
146                         cast_from, variant.name, cast_to, suffix,
147                     ),
148                 );
149                 return;
150             }
151             format!(
152                 "casting `{}` to `{}` may truncate the value{}",
153                 cast_from, cast_to, suffix,
154             )
155         },
156
157         (ty::Float(_), true) => {
158             format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
159         },
160
161         (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
162             "casting `f64` to `f32` may truncate the value".to_string()
163         },
164
165         _ => return,
166     };
167
168     span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
169 }