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