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