Rollup merge of #130646 - workingjubilee:literally-factorize-int-lint, r=compiler-errors
compiler: factor out `OVERFLOWING_LITERALS` impl This puts it into `rustc_lint/src/types/literal.rs`. It then uses the fact that it's easier to navigate the logic to identify something that can easily be factored out, as an instance of "why".
This commit is contained in:
commit
5770ba8686
@ -833,6 +833,28 @@ pub enum Integer {
|
||||
}
|
||||
|
||||
impl Integer {
|
||||
pub fn int_ty_str(self) -> &'static str {
|
||||
use Integer::*;
|
||||
match self {
|
||||
I8 => "i8",
|
||||
I16 => "i16",
|
||||
I32 => "i32",
|
||||
I64 => "i64",
|
||||
I128 => "i128",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uint_ty_str(self) -> &'static str {
|
||||
use Integer::*;
|
||||
match self {
|
||||
I8 => "u8",
|
||||
I16 => "u16",
|
||||
I32 => "u32",
|
||||
I64 => "u64",
|
||||
I128 => "u128",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(self) -> Size {
|
||||
use Integer::*;
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::DiagMessage;
|
||||
use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
|
||||
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
|
||||
use rustc_middle::ty::{
|
||||
self, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
|
||||
};
|
||||
@ -13,22 +13,23 @@
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{source_map, Span, Symbol};
|
||||
use rustc_target::abi::{Abi, Integer, Size, TagEncoding, Variants, WrappingRange};
|
||||
use rustc_target::abi::{Abi, TagEncoding, Variants, WrappingRange};
|
||||
use rustc_target::spec::abi::Abi as SpecAbi;
|
||||
use tracing::debug;
|
||||
use {rustc_ast as ast, rustc_attr as attr, rustc_hir as hir};
|
||||
use {rustc_ast as ast, rustc_hir as hir};
|
||||
|
||||
use crate::lints::{
|
||||
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
|
||||
AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
|
||||
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
|
||||
InvalidNanComparisonsSuggestion, OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign,
|
||||
OverflowingBinHexSignBitSub, OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp,
|
||||
OverflowingLiteral, OverflowingUInt, RangeEndpointOutOfRange, UnusedComparisons,
|
||||
UseInclusiveRange, VariantSizeDifferencesDiag,
|
||||
InvalidNanComparisonsSuggestion, UnusedComparisons, VariantSizeDifferencesDiag,
|
||||
};
|
||||
use crate::{fluent_generated as fluent, LateContext, LateLintPass, LintContext};
|
||||
|
||||
mod literal;
|
||||
|
||||
use literal::{int_ty_range, lint_literal, uint_ty_range};
|
||||
|
||||
declare_lint! {
|
||||
/// The `unused_comparisons` lint detects comparisons made useless by
|
||||
/// limits of the types involved.
|
||||
@ -185,403 +186,6 @@ pub(crate) fn new() -> TypeLimits {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint (`expr..MAX+1`).
|
||||
/// Returns `true` iff the lint was emitted.
|
||||
fn lint_overflowing_range_endpoint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
lit_val: u128,
|
||||
max: u128,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
ty: &str,
|
||||
) -> bool {
|
||||
// Look past casts to support cases like `0..256 as u8`
|
||||
let (expr, lit_span) = if let Node::Expr(par_expr) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let ExprKind::Cast(_, _) = par_expr.kind
|
||||
{
|
||||
(par_expr, expr.span)
|
||||
} else {
|
||||
(expr, expr.span)
|
||||
};
|
||||
|
||||
// We only want to handle exclusive (`..`) ranges,
|
||||
// which are represented as `ExprKind::Struct`.
|
||||
let Node::ExprField(field) = cx.tcx.parent_hir_node(expr.hir_id) else { return false };
|
||||
let Node::Expr(struct_expr) = cx.tcx.parent_hir_node(field.hir_id) else { return false };
|
||||
if !is_range_literal(struct_expr) {
|
||||
return false;
|
||||
};
|
||||
let ExprKind::Struct(_, [start, end], _) = &struct_expr.kind else { return false };
|
||||
|
||||
// We can suggest using an inclusive range
|
||||
// (`..=`) instead only if it is the `end` that is
|
||||
// overflowing and only by 1.
|
||||
if !(end.expr.hir_id == expr.hir_id && lit_val - 1 == max) {
|
||||
return false;
|
||||
};
|
||||
|
||||
use rustc_ast::{LitIntType, LitKind};
|
||||
let suffix = match lit.node {
|
||||
LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(),
|
||||
LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(),
|
||||
LitKind::Int(_, LitIntType::Unsuffixed) => "",
|
||||
_ => bug!(),
|
||||
};
|
||||
|
||||
let sub_sugg = if expr.span.lo() == lit_span.lo() {
|
||||
let Ok(start) = cx.sess().source_map().span_to_snippet(start.span) else { return false };
|
||||
UseInclusiveRange::WithoutParen {
|
||||
sugg: struct_expr.span.shrink_to_lo().to(lit_span.shrink_to_hi()),
|
||||
start,
|
||||
literal: lit_val - 1,
|
||||
suffix,
|
||||
}
|
||||
} else {
|
||||
UseInclusiveRange::WithParen {
|
||||
eq_sugg: expr.span.shrink_to_lo(),
|
||||
lit_sugg: lit_span,
|
||||
literal: lit_val - 1,
|
||||
suffix,
|
||||
}
|
||||
};
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
struct_expr.span,
|
||||
RangeEndpointOutOfRange { ty, sub: sub_sugg },
|
||||
);
|
||||
|
||||
// We've just emitted a lint, special cased for `(...)..MAX+1` ranges,
|
||||
// return `true` so the callers don't also emit a lint
|
||||
true
|
||||
}
|
||||
|
||||
// For `isize` & `usize`, be conservative with the warnings, so that the
|
||||
// warnings are consistent between 32- and 64-bit platforms.
|
||||
fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) {
|
||||
match int_ty {
|
||||
ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()),
|
||||
ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
|
||||
ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
|
||||
ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
|
||||
ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
|
||||
ty::IntTy::I128 => (i128::MIN, i128::MAX),
|
||||
}
|
||||
}
|
||||
|
||||
fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) {
|
||||
let max = match uint_ty {
|
||||
ty::UintTy::Usize => u64::MAX.into(),
|
||||
ty::UintTy::U8 => u8::MAX.into(),
|
||||
ty::UintTy::U16 => u16::MAX.into(),
|
||||
ty::UintTy::U32 => u32::MAX.into(),
|
||||
ty::UintTy::U64 => u64::MAX.into(),
|
||||
ty::UintTy::U128 => u128::MAX,
|
||||
};
|
||||
(0, max)
|
||||
}
|
||||
|
||||
fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
|
||||
let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
|
||||
let firstch = src.chars().next()?;
|
||||
|
||||
if firstch == '0' {
|
||||
match src.chars().nth(1) {
|
||||
Some('x' | 'b') => return Some(src),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn report_bin_hex_error(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
ty: attr::IntType,
|
||||
size: Size,
|
||||
repr_str: String,
|
||||
val: u128,
|
||||
negative: bool,
|
||||
) {
|
||||
let (t, actually) = match ty {
|
||||
attr::IntType::SignedInt(t) => {
|
||||
let actually = if negative { -(size.sign_extend(val)) } else { size.sign_extend(val) };
|
||||
(t.name_str(), actually.to_string())
|
||||
}
|
||||
attr::IntType::UnsignedInt(t) => {
|
||||
let actually = size.truncate(val);
|
||||
(t.name_str(), actually.to_string())
|
||||
}
|
||||
};
|
||||
let sign =
|
||||
if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive };
|
||||
let sub = get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative).map(
|
||||
|suggestion_ty| {
|
||||
if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
|
||||
let (sans_suffix, _) = repr_str.split_at(pos);
|
||||
OverflowingBinHexSub::Suggestion { span: expr.span, suggestion_ty, sans_suffix }
|
||||
} else {
|
||||
OverflowingBinHexSub::Help { suggestion_ty }
|
||||
}
|
||||
},
|
||||
);
|
||||
let sign_bit_sub = (!negative)
|
||||
.then(|| {
|
||||
let ty::Int(int_ty) = cx.typeck_results().node_type(expr.hir_id).kind() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(bit_width) = int_ty.bit_width() else {
|
||||
return None; // isize case
|
||||
};
|
||||
|
||||
// Skip if sign bit is not set
|
||||
if (val & (1 << (bit_width - 1))) == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lit_no_suffix =
|
||||
if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
|
||||
repr_str.split_at(pos).0
|
||||
} else {
|
||||
&repr_str
|
||||
};
|
||||
|
||||
Some(OverflowingBinHexSignBitSub {
|
||||
span: expr.span,
|
||||
lit_no_suffix,
|
||||
negative_val: actually.clone(),
|
||||
int_ty: int_ty.name_str(),
|
||||
uint_ty: int_ty.to_unsigned().name_str(),
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
expr.span,
|
||||
OverflowingBinHex {
|
||||
ty: t,
|
||||
lit: repr_str.clone(),
|
||||
dec: val,
|
||||
actually,
|
||||
sign,
|
||||
sub,
|
||||
sign_bit_sub,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// This function finds the next fitting type and generates a suggestion string.
|
||||
// It searches for fitting types in the following way (`X < Y`):
|
||||
// - `iX`: if literal fits in `uX` => `uX`, else => `iY`
|
||||
// - `-iX` => `iY`
|
||||
// - `uX` => `uY`
|
||||
//
|
||||
// No suggestion for: `isize`, `usize`.
|
||||
fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
|
||||
use ty::IntTy::*;
|
||||
use ty::UintTy::*;
|
||||
macro_rules! find_fit {
|
||||
($ty:expr, $val:expr, $negative:expr,
|
||||
$($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => {
|
||||
{
|
||||
let _neg = if negative { 1 } else { 0 };
|
||||
match $ty {
|
||||
$($type => {
|
||||
$(if !negative && val <= uint_ty_range($utypes).1 {
|
||||
return Some($utypes.name_str())
|
||||
})*
|
||||
$(if val <= int_ty_range($itypes).1 as u128 + _neg {
|
||||
return Some($itypes.name_str())
|
||||
})*
|
||||
None
|
||||
},)+
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match t.kind() {
|
||||
ty::Int(i) => find_fit!(i, val, negative,
|
||||
I8 => [U8] => [I16, I32, I64, I128],
|
||||
I16 => [U16] => [I32, I64, I128],
|
||||
I32 => [U32] => [I64, I128],
|
||||
I64 => [U64] => [I128],
|
||||
I128 => [U128] => []),
|
||||
ty::Uint(u) => find_fit!(u, val, negative,
|
||||
U8 => [U8, U16, U32, U64, U128] => [],
|
||||
U16 => [U16, U32, U64, U128] => [],
|
||||
U32 => [U32, U64, U128] => [],
|
||||
U64 => [U64, U128] => [],
|
||||
U128 => [U128] => []),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_int_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
type_limits: &TypeLimits,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
t: ty::IntTy,
|
||||
v: u128,
|
||||
) {
|
||||
let int_type = t.normalize(cx.sess().target.pointer_width);
|
||||
let (min, max) = int_ty_range(int_type);
|
||||
let max = max as u128;
|
||||
let negative = type_limits.negated_expr_id == Some(e.hir_id);
|
||||
|
||||
// Detect literal value out of range [min, max] inclusive
|
||||
// avoiding use of -min to prevent overflow/panic
|
||||
if (negative && v > max + 1) || (!negative && v > max) {
|
||||
if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
|
||||
report_bin_hex_error(
|
||||
cx,
|
||||
e,
|
||||
attr::IntType::SignedInt(ty::ast_int_ty(t)),
|
||||
Integer::from_int_ty(cx, t).size(),
|
||||
repr_str,
|
||||
v,
|
||||
negative,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if lint_overflowing_range_endpoint(cx, lit, v, max, e, t.name_str()) {
|
||||
// The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
|
||||
return;
|
||||
}
|
||||
|
||||
let span = if negative { type_limits.negated_expr_span.unwrap() } else { e.span };
|
||||
let lit = cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(span)
|
||||
.unwrap_or_else(|_| if negative { format!("-{v}") } else { v.to_string() });
|
||||
let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
|
||||
.map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
span,
|
||||
OverflowingInt { ty: t.name_str(), lit, min, max, help },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_uint_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
t: ty::UintTy,
|
||||
) {
|
||||
let uint_type = t.normalize(cx.sess().target.pointer_width);
|
||||
let (min, max) = uint_ty_range(uint_type);
|
||||
let lit_val: u128 = match lit.node {
|
||||
// _v is u8, within range by definition
|
||||
ast::LitKind::Byte(_v) => return,
|
||||
ast::LitKind::Int(v, _) => v.get(),
|
||||
_ => bug!(),
|
||||
};
|
||||
|
||||
if lit_val < min || lit_val > max {
|
||||
if let Node::Expr(par_e) = cx.tcx.parent_hir_node(e.hir_id) {
|
||||
match par_e.kind {
|
||||
hir::ExprKind::Cast(..) => {
|
||||
if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
par_e.span,
|
||||
OnlyCastu8ToChar { span: par_e.span, literal: lit_val },
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, t.name_str()) {
|
||||
// The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
|
||||
return;
|
||||
}
|
||||
if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
|
||||
report_bin_hex_error(
|
||||
cx,
|
||||
e,
|
||||
attr::IntType::UnsignedInt(ty::ast_uint_ty(t)),
|
||||
Integer::from_uint_ty(cx, t).size(),
|
||||
repr_str,
|
||||
lit_val,
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
e.span,
|
||||
OverflowingUInt {
|
||||
ty: t.name_str(),
|
||||
lit: cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(lit.span)
|
||||
.unwrap_or_else(|_| lit_val.to_string()),
|
||||
min,
|
||||
max,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
type_limits: &TypeLimits,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
) {
|
||||
match *cx.typeck_results().node_type(e.hir_id).kind() {
|
||||
ty::Int(t) => {
|
||||
match lit.node {
|
||||
ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
|
||||
lint_int_literal(cx, type_limits, e, lit, t, v.get())
|
||||
}
|
||||
_ => bug!(),
|
||||
};
|
||||
}
|
||||
ty::Uint(t) => lint_uint_literal(cx, e, lit, t),
|
||||
ty::Float(t) => {
|
||||
let (is_infinite, sym) = match lit.node {
|
||||
ast::LitKind::Float(v, _) => match t {
|
||||
// FIXME(f16_f128): add this check once `is_infinite` is reliable (ABI
|
||||
// issues resolved).
|
||||
ty::FloatTy::F16 => (Ok(false), v),
|
||||
ty::FloatTy::F32 => (v.as_str().parse().map(f32::is_infinite), v),
|
||||
ty::FloatTy::F64 => (v.as_str().parse().map(f64::is_infinite), v),
|
||||
ty::FloatTy::F128 => (Ok(false), v),
|
||||
},
|
||||
_ => bug!(),
|
||||
};
|
||||
if is_infinite == Ok(true) {
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
e.span,
|
||||
OverflowingLiteral {
|
||||
ty: t.name_str(),
|
||||
lit: cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(lit.span)
|
||||
.unwrap_or_else(|_| sym.to_string()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_nan<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
|
386
compiler/rustc_lint/src/types/literal.rs
Normal file
386
compiler/rustc_lint/src/types/literal.rs
Normal file
@ -0,0 +1,386 @@
|
||||
use hir::{is_range_literal, ExprKind, Node};
|
||||
use rustc_middle::ty::layout::IntegerExt;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_middle::{bug, ty};
|
||||
use rustc_target::abi::{Integer, Size};
|
||||
use {rustc_ast as ast, rustc_attr as attr, rustc_hir as hir};
|
||||
|
||||
use crate::context::LintContext;
|
||||
use crate::lints::{
|
||||
OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, OverflowingBinHexSignBitSub,
|
||||
OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp, OverflowingLiteral, OverflowingUInt,
|
||||
RangeEndpointOutOfRange, UseInclusiveRange,
|
||||
};
|
||||
use crate::types::{TypeLimits, OVERFLOWING_LITERALS};
|
||||
use crate::LateContext;
|
||||
|
||||
/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint (`expr..MAX+1`).
|
||||
/// Returns `true` iff the lint was emitted.
|
||||
fn lint_overflowing_range_endpoint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
lit_val: u128,
|
||||
max: u128,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
ty: &str,
|
||||
) -> bool {
|
||||
// Look past casts to support cases like `0..256 as u8`
|
||||
let (expr, lit_span) = if let Node::Expr(par_expr) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let ExprKind::Cast(_, _) = par_expr.kind
|
||||
{
|
||||
(par_expr, expr.span)
|
||||
} else {
|
||||
(expr, expr.span)
|
||||
};
|
||||
|
||||
// We only want to handle exclusive (`..`) ranges,
|
||||
// which are represented as `ExprKind::Struct`.
|
||||
let Node::ExprField(field) = cx.tcx.parent_hir_node(expr.hir_id) else { return false };
|
||||
let Node::Expr(struct_expr) = cx.tcx.parent_hir_node(field.hir_id) else { return false };
|
||||
if !is_range_literal(struct_expr) {
|
||||
return false;
|
||||
};
|
||||
let ExprKind::Struct(_, [start, end], _) = &struct_expr.kind else { return false };
|
||||
|
||||
// We can suggest using an inclusive range
|
||||
// (`..=`) instead only if it is the `end` that is
|
||||
// overflowing and only by 1.
|
||||
if !(end.expr.hir_id == expr.hir_id && lit_val - 1 == max) {
|
||||
return false;
|
||||
};
|
||||
|
||||
use rustc_ast::{LitIntType, LitKind};
|
||||
let suffix = match lit.node {
|
||||
LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(),
|
||||
LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(),
|
||||
LitKind::Int(_, LitIntType::Unsuffixed) => "",
|
||||
_ => bug!(),
|
||||
};
|
||||
|
||||
let sub_sugg = if expr.span.lo() == lit_span.lo() {
|
||||
let Ok(start) = cx.sess().source_map().span_to_snippet(start.span) else { return false };
|
||||
UseInclusiveRange::WithoutParen {
|
||||
sugg: struct_expr.span.shrink_to_lo().to(lit_span.shrink_to_hi()),
|
||||
start,
|
||||
literal: lit_val - 1,
|
||||
suffix,
|
||||
}
|
||||
} else {
|
||||
UseInclusiveRange::WithParen {
|
||||
eq_sugg: expr.span.shrink_to_lo(),
|
||||
lit_sugg: lit_span,
|
||||
literal: lit_val - 1,
|
||||
suffix,
|
||||
}
|
||||
};
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
struct_expr.span,
|
||||
RangeEndpointOutOfRange { ty, sub: sub_sugg },
|
||||
);
|
||||
|
||||
// We've just emitted a lint, special cased for `(...)..MAX+1` ranges,
|
||||
// return `true` so the callers don't also emit a lint
|
||||
true
|
||||
}
|
||||
|
||||
// For `isize` & `usize`, be conservative with the warnings, so that the
|
||||
// warnings are consistent between 32- and 64-bit platforms.
|
||||
pub(crate) fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) {
|
||||
match int_ty {
|
||||
ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()),
|
||||
ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
|
||||
ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
|
||||
ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
|
||||
ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
|
||||
ty::IntTy::I128 => (i128::MIN, i128::MAX),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) {
|
||||
let max = match uint_ty {
|
||||
ty::UintTy::Usize => u64::MAX.into(),
|
||||
ty::UintTy::U8 => u8::MAX.into(),
|
||||
ty::UintTy::U16 => u16::MAX.into(),
|
||||
ty::UintTy::U32 => u32::MAX.into(),
|
||||
ty::UintTy::U64 => u64::MAX.into(),
|
||||
ty::UintTy::U128 => u128::MAX,
|
||||
};
|
||||
(0, max)
|
||||
}
|
||||
|
||||
fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
|
||||
let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
|
||||
let firstch = src.chars().next()?;
|
||||
|
||||
if firstch == '0' {
|
||||
match src.chars().nth(1) {
|
||||
Some('x' | 'b') => return Some(src),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn report_bin_hex_error(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
ty: attr::IntType,
|
||||
size: Size,
|
||||
repr_str: String,
|
||||
val: u128,
|
||||
negative: bool,
|
||||
) {
|
||||
let (t, actually) = match ty {
|
||||
attr::IntType::SignedInt(t) => {
|
||||
let actually = if negative { -(size.sign_extend(val)) } else { size.sign_extend(val) };
|
||||
(t.name_str(), actually.to_string())
|
||||
}
|
||||
attr::IntType::UnsignedInt(t) => {
|
||||
let actually = size.truncate(val);
|
||||
(t.name_str(), actually.to_string())
|
||||
}
|
||||
};
|
||||
let sign =
|
||||
if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive };
|
||||
let sub = get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative).map(
|
||||
|suggestion_ty| {
|
||||
if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
|
||||
let (sans_suffix, _) = repr_str.split_at(pos);
|
||||
OverflowingBinHexSub::Suggestion { span: expr.span, suggestion_ty, sans_suffix }
|
||||
} else {
|
||||
OverflowingBinHexSub::Help { suggestion_ty }
|
||||
}
|
||||
},
|
||||
);
|
||||
let sign_bit_sub = (!negative)
|
||||
.then(|| {
|
||||
let ty::Int(int_ty) = cx.typeck_results().node_type(expr.hir_id).kind() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(bit_width) = int_ty.bit_width() else {
|
||||
return None; // isize case
|
||||
};
|
||||
|
||||
// Skip if sign bit is not set
|
||||
if (val & (1 << (bit_width - 1))) == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lit_no_suffix =
|
||||
if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
|
||||
repr_str.split_at(pos).0
|
||||
} else {
|
||||
&repr_str
|
||||
};
|
||||
|
||||
Some(OverflowingBinHexSignBitSub {
|
||||
span: expr.span,
|
||||
lit_no_suffix,
|
||||
negative_val: actually.clone(),
|
||||
int_ty: int_ty.name_str(),
|
||||
uint_ty: int_ty.to_unsigned().name_str(),
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
expr.span,
|
||||
OverflowingBinHex {
|
||||
ty: t,
|
||||
lit: repr_str.clone(),
|
||||
dec: val,
|
||||
actually,
|
||||
sign,
|
||||
sub,
|
||||
sign_bit_sub,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Find the "next" fitting integer and return a suggestion string
|
||||
//
|
||||
// No suggestion is offered for `{i,u}size`. Otherwise, we try to suggest an equal-sized type.
|
||||
fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
|
||||
match t.kind() {
|
||||
ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize) => None,
|
||||
ty::Uint(_) => Some(Integer::fit_unsigned(val).uint_ty_str()),
|
||||
ty::Int(_) if negative => Some(Integer::fit_signed(-(val as i128)).int_ty_str()),
|
||||
ty::Int(int) => {
|
||||
let signed = Integer::fit_signed(val as i128);
|
||||
let unsigned = Integer::fit_unsigned(val);
|
||||
Some(if Some(unsigned.size().bits()) == int.bit_width() {
|
||||
unsigned.uint_ty_str()
|
||||
} else {
|
||||
signed.int_ty_str()
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_int_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
type_limits: &TypeLimits,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
t: ty::IntTy,
|
||||
v: u128,
|
||||
) {
|
||||
let int_type = t.normalize(cx.sess().target.pointer_width);
|
||||
let (min, max) = int_ty_range(int_type);
|
||||
let max = max as u128;
|
||||
let negative = type_limits.negated_expr_id == Some(e.hir_id);
|
||||
|
||||
// Detect literal value out of range [min, max] inclusive
|
||||
// avoiding use of -min to prevent overflow/panic
|
||||
if (negative && v > max + 1) || (!negative && v > max) {
|
||||
if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
|
||||
report_bin_hex_error(
|
||||
cx,
|
||||
e,
|
||||
attr::IntType::SignedInt(ty::ast_int_ty(t)),
|
||||
Integer::from_int_ty(cx, t).size(),
|
||||
repr_str,
|
||||
v,
|
||||
negative,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if lint_overflowing_range_endpoint(cx, lit, v, max, e, t.name_str()) {
|
||||
// The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
|
||||
return;
|
||||
}
|
||||
|
||||
let span = if negative { type_limits.negated_expr_span.unwrap() } else { e.span };
|
||||
let lit = cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(span)
|
||||
.unwrap_or_else(|_| if negative { format!("-{v}") } else { v.to_string() });
|
||||
let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
|
||||
.map(|suggestion_ty| OverflowingIntHelp { suggestion_ty });
|
||||
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
span,
|
||||
OverflowingInt { ty: t.name_str(), lit, min, max, help },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_uint_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
t: ty::UintTy,
|
||||
) {
|
||||
let uint_type = t.normalize(cx.sess().target.pointer_width);
|
||||
let (min, max) = uint_ty_range(uint_type);
|
||||
let lit_val: u128 = match lit.node {
|
||||
// _v is u8, within range by definition
|
||||
ast::LitKind::Byte(_v) => return,
|
||||
ast::LitKind::Int(v, _) => v.get(),
|
||||
_ => bug!(),
|
||||
};
|
||||
|
||||
if lit_val < min || lit_val > max {
|
||||
if let Node::Expr(par_e) = cx.tcx.parent_hir_node(e.hir_id) {
|
||||
match par_e.kind {
|
||||
hir::ExprKind::Cast(..) => {
|
||||
if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
par_e.span,
|
||||
OnlyCastu8ToChar { span: par_e.span, literal: lit_val },
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, t.name_str()) {
|
||||
// The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`.
|
||||
return;
|
||||
}
|
||||
if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
|
||||
report_bin_hex_error(
|
||||
cx,
|
||||
e,
|
||||
attr::IntType::UnsignedInt(ty::ast_uint_ty(t)),
|
||||
Integer::from_uint_ty(cx, t).size(),
|
||||
repr_str,
|
||||
lit_val,
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
e.span,
|
||||
OverflowingUInt {
|
||||
ty: t.name_str(),
|
||||
lit: cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(lit.span)
|
||||
.unwrap_or_else(|_| lit_val.to_string()),
|
||||
min,
|
||||
max,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lint_literal<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
type_limits: &TypeLimits,
|
||||
e: &'tcx hir::Expr<'tcx>,
|
||||
lit: &hir::Lit,
|
||||
) {
|
||||
match *cx.typeck_results().node_type(e.hir_id).kind() {
|
||||
ty::Int(t) => {
|
||||
match lit.node {
|
||||
ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
|
||||
lint_int_literal(cx, type_limits, e, lit, t, v.get())
|
||||
}
|
||||
_ => bug!(),
|
||||
};
|
||||
}
|
||||
ty::Uint(t) => lint_uint_literal(cx, e, lit, t),
|
||||
ty::Float(t) => {
|
||||
let (is_infinite, sym) = match lit.node {
|
||||
ast::LitKind::Float(v, _) => match t {
|
||||
// FIXME(f16_f128): add this check once `is_infinite` is reliable (ABI
|
||||
// issues resolved).
|
||||
ty::FloatTy::F16 => (Ok(false), v),
|
||||
ty::FloatTy::F32 => (v.as_str().parse().map(f32::is_infinite), v),
|
||||
ty::FloatTy::F64 => (v.as_str().parse().map(f64::is_infinite), v),
|
||||
ty::FloatTy::F128 => (Ok(false), v),
|
||||
},
|
||||
_ => bug!(),
|
||||
};
|
||||
if is_infinite == Ok(true) {
|
||||
cx.emit_span_lint(
|
||||
OVERFLOWING_LITERALS,
|
||||
e.span,
|
||||
OverflowingLiteral {
|
||||
ty: t.name_str(),
|
||||
lit: cx
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(lit.span)
|
||||
.unwrap_or_else(|_| sym.to_string()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user