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_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
22/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint (`expr..MAX+1`).
23/// Returns `true` iff the lint was emitted.
24fn 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    // Look past casts to support cases like `0..256 as u8`
34    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    // We only want to handle exclusive (`..`) ranges,
43    // which are represented as `ExprKind::Struct`.
44    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    // We can suggest using an inclusive range
58    // (`..=`) instead only if it is the `end` that is
59    // overflowing and only by 1.
60    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    // We've just emitted a lint, special cased for `(...)..MAX+1` ranges,
98    // return `true` so the callers don't also emit a lint
99    true
100}
101
102// For `isize` & `usize`, be conservative with the warnings, so that the
103// warnings are consistent between 32- and 64-bit platforms.
104pub(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; // isize case
194            };
195
196            // Skip if sign bit is not set
197            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            // `cast_signed` only supports equal-width integer casts.
210            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
237// Find the "next" fitting integer and return a suggestion string
238//
239// No suggestion is offered for `{i,u}size`. Otherwise, we try to suggest an equal-sized type.
240fn 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    // Detect literal value out of range [min, max] inclusive
288    // avoiding use of -min to prevent overflow/panic
289    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            // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
306            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        // _v is u8, within range by definition
337        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            // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
374            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
406/// `None` if `v` does not parse as the float type, otherwise indicates whether a literal rounds
407/// to infinity.
408fn 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}