]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
New upstream version 1.61.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / casts / cast_possible_truncation.rs
CommitLineData
3c0e092e 1use clippy_utils::consts::{constant, Constant};
cdc7bbd5 2use clippy_utils::diagnostics::span_lint;
3c0e092e 3use clippy_utils::expr_or_init;
ee023bcb
FG
4use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
5use rustc_ast::ast;
6use rustc_attr::IntType;
7use rustc_hir::def::{DefKind, Res};
3c0e092e 8use rustc_hir::{BinOpKind, Expr, ExprKind};
f20569fa
XL
9use rustc_lint::LateContext;
10use rustc_middle::ty::{self, FloatTy, Ty};
11
ee023bcb 12use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
f20569fa 13
3c0e092e
XL
14fn 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
22fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u64> {
23 constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros()))
24}
25
26fn 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)
33 - (if signed {
34 0 // let's be conservative here
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 => {
44 apply_reductions(cx, nbits, left, signed)
45 - constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))
46 },
47 _ => nbits,
48 },
5099ac24 49 ExprKind::MethodCall(method, [left, right], _) => {
3c0e092e
XL
50 if signed {
51 return nbits;
52 }
53 let max_bits = if method.ident.as_str() == "min" {
54 get_constant_bits(cx, right)
55 } else {
56 None
57 };
58 apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
59 },
5099ac24 60 ExprKind::MethodCall(method, [_, lo, hi], _) => {
3c0e092e
XL
61 if method.ident.as_str() == "clamp" {
62 //FIXME: make this a diagnostic item
63 if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
64 return lo_bits.max(hi_bits);
65 }
66 }
67 nbits
68 },
5099ac24 69 ExprKind::MethodCall(method, [_value], _) => {
3c0e092e
XL
70 if method.ident.name.as_str() == "signum" {
71 0 // do not lint if cast comes from a `signum` function
72 } else {
73 nbits
74 }
75 },
76 _ => nbits,
77 }
78}
79
80pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
ee023bcb
FG
81 let msg = match (cast_from.kind(), cast_to.is_integral()) {
82 (ty::Int(_) | ty::Uint(_), true) => {
3c0e092e
XL
83 let from_nbits = apply_reductions(
84 cx,
85 utils::int_ty_to_nbits(cast_from, cx.tcx),
86 cast_expr,
87 cast_from.is_signed(),
88 );
f20569fa
XL
89 let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
90
91 let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
92 (true, true) | (false, false) => (to_nbits < from_nbits, ""),
93 (true, false) => (
94 to_nbits <= 32,
95 if to_nbits == 32 {
96 " on targets with 64-bit wide pointers"
97 } else {
98 ""
99 },
100 ),
101 (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"),
102 };
103
104 if !should_lint {
105 return;
106 }
107
108 format!(
109 "casting `{}` to `{}` may truncate the value{}",
110 cast_from, cast_to, suffix,
111 )
112 },
113
ee023bcb
FG
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)
f20569fa 117 {
ee023bcb
FG
118 let i = def.variant_index_with_ctor_id(id);
119 let variant = def.variant(i);
120 let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
121 (nbits, Some(variant))
f20569fa 122 } else {
ee023bcb
FG
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 );
f20569fa
XL
151 return;
152 }
ee023bcb
FG
153 format!(
154 "casting `{}` to `{}` may truncate the value{}",
155 cast_from, cast_to, suffix,
156 )
f20569fa 157 },
ee023bcb
FG
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,
f20569fa
XL
168 };
169
170 span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
171}