From e822e62ee80a9108bfdb7d0952c85fab2146f569 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 22 Feb 2018 15:53:22 +0100 Subject: [PATCH] Suggest type for overflowing bin/hex-literals --- src/librustc_lint/types.rs | 194 ++++++++++++++++++++++---- src/test/ui/lint/type-overflow.rs | 35 +++++ src/test/ui/lint/type-overflow.stderr | 70 ++++++++++ 3 files changed, 274 insertions(+), 25 deletions(-) create mode 100644 src/test/ui/lint/type-overflow.rs create mode 100644 src/test/ui/lint/type-overflow.stderr diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs index ef9b3d38c63..4fabb5bafbf 100644 --- a/src/librustc_lint/types.rs +++ b/src/librustc_lint/types.rs @@ -150,11 +150,52 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { // 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) { - cx.span_lint(OVERFLOWING_LITERALS, - e.span, - &format!("literal out of range for {:?}", t)); + if (negative && v > max + 1) || (!negative && v > max) { + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + let bits = int_ty_bits(t, cx.sess().target.isize_ty); + let mut actually = v as i128; + if bits < 128 { + // v & 0b0..01..1, |1| = bits + let trimmed = v & ((1 << bits) - 1); + actually = if v & (1 << (bits - 1)) == 0 { + // positive + trimmed as i128 + } else { + // negative -> two's complement + (((-1 as i128 as u128) << bits) | trimmed) as i128 + }; + } + let mut err = cx.struct_span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); + err.note(&format!( + "the literal `{}` (decimal `{}`) does not fit into \ + an `{:?}` and will become `{}{:?}`.", + repr_str, v, t, actually, t + )); + let sugg_ty = get_fitting_type( + &cx.tables.node_id_to_type(e.hir_id).sty, + v, + negative, + ).map_or(String::new(), |ty| match ty { + ty::TyUint(t) => format!("Consider using `{:?}`", t), + ty::TyInt(t) => format!("Consider using `{:?}`", t), + _ => String::new(), + }); + if !sugg_ty.is_empty() { + err.help(&sugg_ty); + } + + err.emit(); + return; + } + cx.span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); return; } } @@ -180,37 +221,77 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { if let hir::ExprCast(..) = parent_expr.node { if let ty::TyChar = cx.tables.expr_ty(parent_expr).sty { let mut err = cx.struct_span_lint( - OVERFLOWING_LITERALS, - parent_expr.span, - "only u8 can be casted into char"); - err.span_suggestion(parent_expr.span, - &"use a char literal instead", - format!("'\\u{{{:X}}}'", lit_val)); + OVERFLOWING_LITERALS, + parent_expr.span, + "only u8 can be casted into char", + ); + err.span_suggestion( + parent_expr.span, + &"use a char literal instead", + format!("'\\u{{{:X}}}'", lit_val), + ); err.emit(); - return + return; } } } - cx.span_lint(OVERFLOWING_LITERALS, - e.span, - &format!("literal out of range for {:?}", t)); + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + let bits = uint_ty_bits(t, cx.sess().target.usize_ty); + // u128 cannot be greater than max -> compiler error + let actually = lit_val & ((1 << bits) - 1); + let mut err = cx.struct_span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); + err.note(&format!( + "the literal `{}` (decimal `{}`) does not fit into \ + an `{:?}` and will become `{}{:?}`.", + repr_str, lit_val, t, actually, t + )); + let sugg_ty = get_fitting_type( + &cx.tables.node_id_to_type(e.hir_id).sty, + lit_val, + false, + ).map_or( + String::new(), + |ty| { + if let ty::TyUint(t) = ty { + format!("Consider using `{:?}`", t) + } else { + String::new() + } + }, + ); + if !sugg_ty.is_empty() { + err.help(&sugg_ty); + } + + err.emit(); + return; + } + cx.span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); } } ty::TyFloat(t) => { let is_infinite = match lit.node { - ast::LitKind::Float(v, _) | - ast::LitKind::FloatUnsuffixed(v) => { - match t { - ast::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), - ast::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), - } - } + ast::LitKind::Float(v, _) | ast::LitKind::FloatUnsuffixed(v) => match t + { + ast::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), + ast::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), + }, _ => bug!(), }; if is_infinite == Ok(true) { - cx.span_lint(OVERFLOWING_LITERALS, - e.span, - &format!("literal out of range for {:?}", t)); + cx.span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); } } _ => (), @@ -338,6 +419,69 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { _ => false, } } + + fn get_bin_hex_repr(cx: &LateContext, lit: &ast::Lit) -> Option { + if let Some(src) = cx.sess().codemap().span_to_snippet(lit.span).ok() { + if let Some(firstch) = src.chars().next() { + if let Some(0) = char::to_digit(firstch, 10) { + if let Some(base) = src.chars().nth(1) { + if base == 'x' || base == 'b' { + return Some(src); + } + } + } + } + } + + None + } + + fn get_fitting_type<'a>( + t: &ty::TypeVariants, + val: u128, + negative: bool, + ) -> Option> { + use syntax::ast::IntTy::*; + use syntax::ast::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(ty::TyUint($utypes)) + })* + $(if val <= int_ty_range($itypes).1 as u128 + _neg { + return Some(ty::TyInt($itypes)) + })* + None + },)* + _ => None + } + } + } + } + if let &ty::TyInt(i) = t { + return 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] => []); + } + if let &ty::TyUint(u) = t { + return 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 + } } } diff --git a/src/test/ui/lint/type-overflow.rs b/src/test/ui/lint/type-overflow.rs new file mode 100644 index 00000000000..e414f43b3ff --- /dev/null +++ b/src/test/ui/lint/type-overflow.rs @@ -0,0 +1,35 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// must-compile-successfully + +#![feature(i128_type)] + +fn main() { + let error = 255i8; //~WARNING literal out of range for i8 + + let ok = 0b1000_0001; // should be ok -> i32 + let ok = 0b0111_1111i8; // should be ok -> 127i8 + + let fail = 0b1000_0001i8; //~WARNING literal out of range for i8 + + let fail = 0x8000_0000_0000_0000i64; //~WARNING literal out of range for i64 + + let fail = 0x1_FFFF_FFFFu32; //~WARNING literal out of range for u32 + + let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + //~^ WARNING literal out of range for i128 + + let fail = 0x8FFF_FFFF_FFFF_FFFE; //~WARNING literal out of range for i32 + + let fail: isize = 0x8000_0000_0000_0000; //~WARNING literal out of range for isize + + let fail = -0b1111_1111i8; //~WARNING literal out of range for i8 +} diff --git a/src/test/ui/lint/type-overflow.stderr b/src/test/ui/lint/type-overflow.stderr new file mode 100644 index 00000000000..425f76da5cb --- /dev/null +++ b/src/test/ui/lint/type-overflow.stderr @@ -0,0 +1,70 @@ +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:16:17 + | +16 | let error = 255i8; //~WARNING literal out of range for i8 + | ^^^^^ + | + = note: #[warn(overflowing_literals)] on by default + +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:21:16 + | +21 | let fail = 0b1000_0001i8; //~WARNING literal out of range for i8 + | ^^^^^^^^^^^^^ + | + = note: the literal `0b1000_0001i8` (decimal `129`) does not fit into an `i8` and will become `-127i8`. + = help: Consider using `u8` + +warning: literal out of range for i64 + --> $DIR/type-overflow.rs:23:16 + | +23 | let fail = 0x8000_0000_0000_0000i64; //~WARNING literal out of range for i64 + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8000_0000_0000_0000i64` (decimal `9223372036854775808`) does not fit into an `i64` and will become `-9223372036854775808i64`. + = help: Consider using `u64` + +warning: literal out of range for u32 + --> $DIR/type-overflow.rs:25:16 + | +25 | let fail = 0x1_FFFF_FFFFu32; //~WARNING literal out of range for u32 + | ^^^^^^^^^^^^^^^^ + | + = note: the literal `0x1_FFFF_FFFFu32` (decimal `8589934591`) does not fit into an `u32` and will become `4294967295u32`. + = help: Consider using `u64` + +warning: literal out of range for i128 + --> $DIR/type-overflow.rs:27:22 + | +27 | let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8000_0000_0000_0000_0000_0000_0000_0000` (decimal `170141183460469231731687303715884105728`) does not fit into an `i128` and will become `-170141183460469231731687303715884105728i128`. + = help: Consider using `u128` + +warning: literal out of range for i32 + --> $DIR/type-overflow.rs:30:16 + | +30 | let fail = 0x8FFF_FFFF_FFFF_FFFE; //~WARNING literal out of range for i32 + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8FFF_FFFF_FFFF_FFFE` (decimal `10376293541461622782`) does not fit into an `i32` and will become `-2i32`. + = help: Consider using `i128` + +warning: literal out of range for isize + --> $DIR/type-overflow.rs:32:23 + | +32 | let fail: isize = 0x8000_0000_0000_0000; //~WARNING literal out of range for isize + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8000_0000_0000_0000` (decimal `9223372036854775808`) does not fit into an `isize` and will become `-9223372036854775808isize`. + +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:34:17 + | +34 | let fail = -0b1111_1111i8; //~WARNING literal out of range for i8 + | ^^^^^^^^^^^^^ + | + = note: the literal `0b1111_1111i8` (decimal `255`) does not fit into an `i8` and will become `-1i8`. + = help: Consider using `i16` +