1use hir::{ExprKind, Node, is_range_literal};
2use rustc_abi::{Integer, Size};
3use rustc_hir::HirId;
4use rustc_middle::ty::Ty;
5use rustc_middle::ty::layout::IntegerExt;
6use rustc_middle::{bug, ty};
7use rustc_span::Span;
8use {rustc_ast as ast, rustc_attr_parsing as attr, rustc_hir as hir};
9
10use crate::LateContext;
11use crate::context::LintContext;
12use crate::lints::{
13 OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, OverflowingBinHexSignBitSub,
14 OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp, OverflowingLiteral, OverflowingUInt,
15 RangeEndpointOutOfRange, UseInclusiveRange,
16};
17use crate::types::{OVERFLOWING_LITERALS, TypeLimits};
18
19fn lint_overflowing_range_endpoint<'tcx>(
22 cx: &LateContext<'tcx>,
23 lit: &hir::Lit,
24 lit_val: u128,
25 max: u128,
26 hir_id: HirId,
27 lit_span: Span,
28 ty: &str,
29) -> bool {
30 let (hir_id, span) = if let Node::Expr(par_expr) = cx.tcx.parent_hir_node(hir_id)
32 && let ExprKind::Cast(_, _) = par_expr.kind
33 {
34 (par_expr.hir_id, par_expr.span)
35 } else {
36 (hir_id, lit_span)
37 };
38
39 let Node::ExprField(field) = cx.tcx.parent_hir_node(hir_id) else { return false };
42 let Node::Expr(struct_expr) = cx.tcx.parent_hir_node(field.hir_id) else { return false };
43 if !is_range_literal(struct_expr) {
44 return false;
45 };
46 let ExprKind::Struct(_, [start, end], _) = &struct_expr.kind else { return false };
47
48 if !(end.expr.hir_id == hir_id && lit_val - 1 == max) {
52 return false;
53 };
54
55 use rustc_ast::{LitIntType, LitKind};
56 let suffix = match lit.node {
57 LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(),
58 LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(),
59 LitKind::Int(_, LitIntType::Unsuffixed) => "",
60 _ => bug!(),
61 };
62
63 let sub_sugg = if span.lo() == lit_span.lo() {
64 let Ok(start) = cx.sess().source_map().span_to_snippet(start.span) else { return false };
65 UseInclusiveRange::WithoutParen {
66 sugg: struct_expr.span.shrink_to_lo().to(lit_span.shrink_to_hi()),
67 start,
68 literal: lit_val - 1,
69 suffix,
70 }
71 } else {
72 UseInclusiveRange::WithParen {
73 eq_sugg: span.shrink_to_lo(),
74 lit_sugg: lit_span,
75 literal: lit_val - 1,
76 suffix,
77 }
78 };
79
80 cx.emit_span_lint(
81 OVERFLOWING_LITERALS,
82 struct_expr.span,
83 RangeEndpointOutOfRange { ty, sub: sub_sugg },
84 );
85
86 true
89}
90
91pub(crate) fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) {
94 match int_ty {
95 ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()),
96 ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
97 ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
98 ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
99 ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
100 ty::IntTy::I128 => (i128::MIN, i128::MAX),
101 }
102}
103
104pub(crate) fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) {
105 let max = match uint_ty {
106 ty::UintTy::Usize => u64::MAX.into(),
107 ty::UintTy::U8 => u8::MAX.into(),
108 ty::UintTy::U16 => u16::MAX.into(),
109 ty::UintTy::U32 => u32::MAX.into(),
110 ty::UintTy::U64 => u64::MAX.into(),
111 ty::UintTy::U128 => u128::MAX,
112 };
113 (0, max)
114}
115
116fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
117 let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
118 let firstch = src.chars().next()?;
119
120 if firstch == '0' {
121 match src.chars().nth(1) {
122 Some('x' | 'b') => return Some(src),
123 _ => return None,
124 }
125 }
126
127 None
128}
129
130fn report_bin_hex_error(
131 cx: &LateContext<'_>,
132 hir_id: HirId,
133 span: Span,
134 ty: attr::IntType,
135 size: Size,
136 repr_str: String,
137 val: u128,
138 negative: bool,
139) {
140 let (t, actually) = match ty {
141 attr::IntType::SignedInt(t) => {
142 let actually = if negative { -(size.sign_extend(val)) } else { size.sign_extend(val) };
143 (t.name_str(), actually.to_string())
144 }
145 attr::IntType::UnsignedInt(t) => {
146 let actually = size.truncate(val);
147 (t.name_str(), actually.to_string())
148 }
149 };
150 let sign =
151 if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive };
152 let sub = get_type_suggestion(cx.typeck_results().node_type(hir_id), val, negative).map(
153 |suggestion_ty| {
154 if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
155 let (sans_suffix, _) = repr_str.split_at(pos);
156 OverflowingBinHexSub::Suggestion { span, suggestion_ty, sans_suffix }
157 } else {
158 OverflowingBinHexSub::Help { suggestion_ty }
159 }
160 },
161 );
162 let sign_bit_sub = (!negative)
163 .then(|| {
164 let ty::Int(int_ty) = cx.typeck_results().node_type(hir_id).kind() else {
165 return None;
166 };
167
168 let Some(bit_width) = int_ty.bit_width() else {
169 return None; };
171
172 if (val & (1 << (bit_width - 1))) == 0 {
174 return None;
175 }
176
177 let lit_no_suffix =
178 if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
179 repr_str.split_at(pos).0
180 } else {
181 &repr_str
182 };
183
184 Some(OverflowingBinHexSignBitSub {
185 span,
186 lit_no_suffix,
187 negative_val: actually.clone(),
188 int_ty: int_ty.name_str(),
189 uint_ty: int_ty.to_unsigned().name_str(),
190 })
191 })
192 .flatten();
193
194 cx.emit_span_lint(
195 OVERFLOWING_LITERALS,
196 span,
197 OverflowingBinHex {
198 ty: t,
199 lit: repr_str.clone(),
200 dec: val,
201 actually,
202 sign,
203 sub,
204 sign_bit_sub,
205 },
206 )
207}
208
209fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
213 match t.kind() {
214 ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize) => None,
215 ty::Uint(_) => Some(Integer::fit_unsigned(val).uint_ty_str()),
216 ty::Int(_) => {
217 let signed = literal_to_i128(val, negative).map(Integer::fit_signed);
218 if negative {
219 signed.map(Integer::int_ty_str)
220 } else {
221 let unsigned = Integer::fit_unsigned(val);
222 Some(if let Some(signed) = signed {
223 if unsigned.size() < signed.size() {
224 unsigned.uint_ty_str()
225 } else {
226 signed.int_ty_str()
227 }
228 } else {
229 unsigned.uint_ty_str()
230 })
231 }
232 }
233 _ => None,
234 }
235}
236
237fn literal_to_i128(val: u128, negative: bool) -> Option<i128> {
238 if negative {
239 (val <= i128::MAX as u128 + 1).then(|| val.wrapping_neg() as i128)
240 } else {
241 val.try_into().ok()
242 }
243}
244
245fn lint_int_literal<'tcx>(
246 cx: &LateContext<'tcx>,
247 type_limits: &TypeLimits,
248 hir_id: HirId,
249 span: Span,
250 lit: &hir::Lit,
251 t: ty::IntTy,
252 v: u128,
253) {
254 let int_type = t.normalize(cx.sess().target.pointer_width);
255 let (min, max) = int_ty_range(int_type);
256 let max = max as u128;
257 let negative = type_limits.negated_expr_id == Some(hir_id);
258
259 if (negative && v > max + 1) || (!negative && v > max) {
262 if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
263 report_bin_hex_error(
264 cx,
265 hir_id,
266 span,
267 attr::IntType::SignedInt(ty::ast_int_ty(t)),
268 Integer::from_int_ty(cx, t).size(),
269 repr_str,
270 v,
271 negative,
272 );
273 return;
274 }
275
276 if lint_overflowing_range_endpoint(cx, lit, v, max, hir_id, span, t.name_str()) {
277 return;
279 }
280
281 let span = if negative { type_limits.negated_expr_span.unwrap() } else { span };
282 let lit = cx
283 .sess()
284 .source_map()
285 .span_to_snippet(span)
286 .unwrap_or_else(|_| if negative { format!("-{v}") } else { v.to_string() });
287 let help = get_type_suggestion(cx.typeck_results().node_type(hir_id), v, negative)
288 .map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
289
290 cx.emit_span_lint(
291 OVERFLOWING_LITERALS,
292 span,
293 OverflowingInt { ty: t.name_str(), lit, min, max, help },
294 );
295 }
296}
297
298fn lint_uint_literal<'tcx>(
299 cx: &LateContext<'tcx>,
300 hir_id: HirId,
301 span: Span,
302 lit: &hir::Lit,
303 t: ty::UintTy,
304) {
305 let uint_type = t.normalize(cx.sess().target.pointer_width);
306 let (min, max) = uint_ty_range(uint_type);
307 let lit_val: u128 = match lit.node {
308 ast::LitKind::Byte(_v) => return,
310 ast::LitKind::Int(v, _) => v.get(),
311 _ => bug!(),
312 };
313
314 if lit_val < min || lit_val > max {
315 if let Node::Expr(par_e) = cx.tcx.parent_hir_node(hir_id) {
316 match par_e.kind {
317 hir::ExprKind::Cast(..) => {
318 if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
319 cx.emit_span_lint(
320 OVERFLOWING_LITERALS,
321 par_e.span,
322 OnlyCastu8ToChar { span: par_e.span, literal: lit_val },
323 );
324 return;
325 }
326 }
327 _ => {}
328 }
329 }
330 if lint_overflowing_range_endpoint(cx, lit, lit_val, max, hir_id, span, t.name_str()) {
331 return;
333 }
334 if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
335 report_bin_hex_error(
336 cx,
337 hir_id,
338 span,
339 attr::IntType::UnsignedInt(ty::ast_uint_ty(t)),
340 Integer::from_uint_ty(cx, t).size(),
341 repr_str,
342 lit_val,
343 false,
344 );
345 return;
346 }
347 cx.emit_span_lint(
348 OVERFLOWING_LITERALS,
349 span,
350 OverflowingUInt {
351 ty: t.name_str(),
352 lit: cx
353 .sess()
354 .source_map()
355 .span_to_snippet(lit.span)
356 .unwrap_or_else(|_| lit_val.to_string()),
357 min,
358 max,
359 },
360 );
361 }
362}
363
364pub(crate) fn lint_literal<'tcx>(
365 cx: &LateContext<'tcx>,
366 type_limits: &TypeLimits,
367 hir_id: HirId,
368 span: Span,
369 lit: &hir::Lit,
370 negated: bool,
371) {
372 match *cx.typeck_results().node_type(hir_id).kind() {
373 ty::Int(t) => {
374 match lit.node {
375 ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
376 lint_int_literal(cx, type_limits, hir_id, span, lit, t, v.get())
377 }
378 _ => bug!(),
379 };
380 }
381 ty::Uint(t) => {
382 assert!(!negated);
383 lint_uint_literal(cx, hir_id, span, lit, t)
384 }
385 ty::Float(t) => {
386 let (is_infinite, sym) = match lit.node {
387 ast::LitKind::Float(v, _) => match t {
388 ty::FloatTy::F16 => (Ok(false), v),
391 ty::FloatTy::F32 => (v.as_str().parse().map(f32::is_infinite), v),
392 ty::FloatTy::F64 => (v.as_str().parse().map(f64::is_infinite), v),
393 ty::FloatTy::F128 => (Ok(false), v),
394 },
395 _ => bug!(),
396 };
397 if is_infinite == Ok(true) {
398 cx.emit_span_lint(
399 OVERFLOWING_LITERALS,
400 span,
401 OverflowingLiteral {
402 ty: t.name_str(),
403 lit: cx
404 .sess()
405 .source_map()
406 .span_to_snippet(lit.span)
407 .unwrap_or_else(|_| sym.to_string()),
408 },
409 );
410 }
411 }
412 _ => {}
413 }
414}