Skip to main content

rustc_lint/types/
literal.rs

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