]>
Commit | Line | Data |
---|---|---|
3c0e092e | 1 | use clippy_utils::consts::{constant, Constant}; |
cdc7bbd5 | 2 | use clippy_utils::diagnostics::span_lint; |
3c0e092e | 3 | use clippy_utils::expr_or_init; |
ee023bcb FG |
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}; | |
3c0e092e | 8 | use rustc_hir::{BinOpKind, Expr, ExprKind}; |
f20569fa XL |
9 | use rustc_lint::LateContext; |
10 | use rustc_middle::ty::{self, FloatTy, Ty}; | |
11 | ||
ee023bcb | 12 | use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; |
f20569fa | 13 | |
3c0e092e XL |
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) | |
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 | ||
80 | pub(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 | } |