rustc_lint/types/
literal.rs

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