rustc_lint/types/
literal.rs

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