[arithmetic_side_effects] Fix #12318
This commit is contained in:
parent
62fd1d5377
commit
2a4dae368c
@ -7,14 +7,13 @@
|
|||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::ty::{self, Ty};
|
use rustc_middle::ty::{self, Ty};
|
||||||
use rustc_session::impl_lint_pass;
|
use rustc_session::impl_lint_pass;
|
||||||
use rustc_span::source_map::Spanned;
|
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
use rustc_span::{Span, Symbol};
|
use rustc_span::{Span, Symbol};
|
||||||
use {rustc_ast as ast, rustc_hir as hir};
|
use {rustc_ast as ast, rustc_hir as hir};
|
||||||
|
|
||||||
const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]];
|
const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]];
|
||||||
const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
|
const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
|
||||||
const INTEGER_METHODS: &[Symbol] = &[
|
const DISALLOWED_INT_METHODS: &[Symbol] = &[
|
||||||
sym::saturating_div,
|
sym::saturating_div,
|
||||||
sym::wrapping_div,
|
sym::wrapping_div,
|
||||||
sym::wrapping_rem,
|
sym::wrapping_rem,
|
||||||
@ -27,8 +26,8 @@ pub struct ArithmeticSideEffects {
|
|||||||
allowed_unary: FxHashSet<String>,
|
allowed_unary: FxHashSet<String>,
|
||||||
// Used to check whether expressions are constants, such as in enum discriminants and consts
|
// Used to check whether expressions are constants, such as in enum discriminants and consts
|
||||||
const_span: Option<Span>,
|
const_span: Option<Span>,
|
||||||
|
disallowed_int_methods: FxHashSet<Symbol>,
|
||||||
expr_span: Option<Span>,
|
expr_span: Option<Span>,
|
||||||
integer_methods: FxHashSet<Symbol>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
|
impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
|
||||||
@ -53,8 +52,8 @@ impl ArithmeticSideEffects {
|
|||||||
allowed_binary,
|
allowed_binary,
|
||||||
allowed_unary,
|
allowed_unary,
|
||||||
const_span: None,
|
const_span: None,
|
||||||
|
disallowed_int_methods: DISALLOWED_INT_METHODS.iter().copied().collect(),
|
||||||
expr_span: None,
|
expr_span: None,
|
||||||
integer_methods: INTEGER_METHODS.iter().copied().collect(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,10 +90,10 @@ fn has_allowed_unary(&self, ty: Ty<'_>) -> bool {
|
|||||||
fn has_specific_allowed_type_and_operation<'tcx>(
|
fn has_specific_allowed_type_and_operation<'tcx>(
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
lhs_ty: Ty<'tcx>,
|
lhs_ty: Ty<'tcx>,
|
||||||
op: &Spanned<hir::BinOpKind>,
|
op: hir::BinOpKind,
|
||||||
rhs_ty: Ty<'tcx>,
|
rhs_ty: Ty<'tcx>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let is_div_or_rem = matches!(op.node, hir::BinOpKind::Div | hir::BinOpKind::Rem);
|
let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem);
|
||||||
let is_non_zero_u = |cx: &LateContext<'tcx>, ty: Ty<'tcx>| {
|
let is_non_zero_u = |cx: &LateContext<'tcx>, ty: Ty<'tcx>| {
|
||||||
let tcx = cx.tcx;
|
let tcx = cx.tcx;
|
||||||
|
|
||||||
@ -166,13 +165,35 @@ fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Methods like `add_assign` are send to their `BinOps` references.
|
||||||
|
fn manage_sugar_methods<'tcx>(
|
||||||
|
&mut self,
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
expr: &'tcx hir::Expr<'_>,
|
||||||
|
lhs: &'tcx hir::Expr<'_>,
|
||||||
|
ps: &hir::PathSegment<'_>,
|
||||||
|
rhs: &'tcx hir::Expr<'_>,
|
||||||
|
) {
|
||||||
|
if ps.ident.name == sym::add || ps.ident.name == sym::add_assign {
|
||||||
|
self.manage_bin_ops(cx, expr, hir::BinOpKind::Add, lhs, rhs);
|
||||||
|
} else if ps.ident.name == sym::div || ps.ident.name == sym::div_assign {
|
||||||
|
self.manage_bin_ops(cx, expr, hir::BinOpKind::Div, lhs, rhs);
|
||||||
|
} else if ps.ident.name == sym::mul || ps.ident.name == sym::mul_assign {
|
||||||
|
self.manage_bin_ops(cx, expr, hir::BinOpKind::Mul, lhs, rhs);
|
||||||
|
} else if ps.ident.name == sym::rem || ps.ident.name == sym::rem_assign {
|
||||||
|
self.manage_bin_ops(cx, expr, hir::BinOpKind::Rem, lhs, rhs);
|
||||||
|
} else if ps.ident.name == sym::sub || ps.ident.name == sym::sub_assign {
|
||||||
|
self.manage_bin_ops(cx, expr, hir::BinOpKind::Sub, lhs, rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Manages when the lint should be triggered. Operations in constant environments, hard coded
|
/// Manages when the lint should be triggered. Operations in constant environments, hard coded
|
||||||
/// types, custom allowed types and non-constant operations that won't overflow are ignored.
|
/// types, custom allowed types and non-constant operations that don't overflow are ignored.
|
||||||
fn manage_bin_ops<'tcx>(
|
fn manage_bin_ops<'tcx>(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
expr: &'tcx hir::Expr<'_>,
|
expr: &'tcx hir::Expr<'_>,
|
||||||
op: &Spanned<hir::BinOpKind>,
|
op: hir::BinOpKind,
|
||||||
lhs: &'tcx hir::Expr<'_>,
|
lhs: &'tcx hir::Expr<'_>,
|
||||||
rhs: &'tcx hir::Expr<'_>,
|
rhs: &'tcx hir::Expr<'_>,
|
||||||
) {
|
) {
|
||||||
@ -180,7 +201,7 @@ fn manage_bin_ops<'tcx>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !matches!(
|
if !matches!(
|
||||||
op.node,
|
op,
|
||||||
hir::BinOpKind::Add
|
hir::BinOpKind::Add
|
||||||
| hir::BinOpKind::Div
|
| hir::BinOpKind::Div
|
||||||
| hir::BinOpKind::Mul
|
| hir::BinOpKind::Mul
|
||||||
@ -204,7 +225,7 @@ fn manage_bin_ops<'tcx>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
|
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
|
||||||
if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node {
|
if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op {
|
||||||
// At least for integers, shifts are already handled by the CTFE
|
// At least for integers, shifts are already handled by the CTFE
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -213,7 +234,7 @@ fn manage_bin_ops<'tcx>(
|
|||||||
Self::literal_integer(cx, actual_rhs),
|
Self::literal_integer(cx, actual_rhs),
|
||||||
) {
|
) {
|
||||||
(None, None) => false,
|
(None, None) => false,
|
||||||
(None, Some(n)) => match (&op.node, n) {
|
(None, Some(n)) => match (&op, n) {
|
||||||
// Division and module are always valid if applied to non-zero integers
|
// Division and module are always valid if applied to non-zero integers
|
||||||
(hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
|
(hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
|
||||||
// Adding or subtracting zeros is always a no-op
|
// Adding or subtracting zeros is always a no-op
|
||||||
@ -223,7 +244,7 @@ fn manage_bin_ops<'tcx>(
|
|||||||
=> true,
|
=> true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
(Some(n), None) => match (&op.node, n) {
|
(Some(n), None) => match (&op, n) {
|
||||||
// Adding or subtracting zeros is always a no-op
|
// Adding or subtracting zeros is always a no-op
|
||||||
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
|
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
|
||||||
// Multiplication by 1 or 0 will never overflow
|
// Multiplication by 1 or 0 will never overflow
|
||||||
@ -249,6 +270,7 @@ fn manage_method_call<'tcx>(
|
|||||||
&mut self,
|
&mut self,
|
||||||
args: &'tcx [hir::Expr<'_>],
|
args: &'tcx [hir::Expr<'_>],
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
|
expr: &'tcx hir::Expr<'_>,
|
||||||
ps: &'tcx hir::PathSegment<'_>,
|
ps: &'tcx hir::PathSegment<'_>,
|
||||||
receiver: &'tcx hir::Expr<'_>,
|
receiver: &'tcx hir::Expr<'_>,
|
||||||
) {
|
) {
|
||||||
@ -262,7 +284,8 @@ fn manage_method_call<'tcx>(
|
|||||||
if !Self::is_integral(instance_ty) {
|
if !Self::is_integral(instance_ty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !self.integer_methods.contains(&ps.ident.name) {
|
self.manage_sugar_methods(cx, expr, receiver, ps, arg);
|
||||||
|
if !self.disallowed_int_methods.contains(&ps.ident.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let (actual_arg, _) = peel_hir_expr_refs(arg);
|
let (actual_arg, _) = peel_hir_expr_refs(arg);
|
||||||
@ -310,10 +333,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
|
|||||||
}
|
}
|
||||||
match &expr.kind {
|
match &expr.kind {
|
||||||
hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
|
hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
|
||||||
self.manage_bin_ops(cx, expr, op, lhs, rhs);
|
self.manage_bin_ops(cx, expr, op.node, lhs, rhs);
|
||||||
},
|
},
|
||||||
hir::ExprKind::MethodCall(ps, receiver, args, _) => {
|
hir::ExprKind::MethodCall(ps, receiver, args, _) => {
|
||||||
self.manage_method_call(args, cx, ps, receiver);
|
self.manage_method_call(args, cx, expr, ps, receiver);
|
||||||
},
|
},
|
||||||
hir::ExprKind::Unary(un_op, un_expr) => {
|
hir::ExprKind::Unary(un_op, un_expr) => {
|
||||||
self.manage_unary_ops(cx, expr, un_expr, *un_op);
|
self.manage_unary_ops(cx, expr, un_expr, *un_op);
|
||||||
|
@ -521,4 +521,14 @@ fn example_rem(x: Wrapping<i32>, maybe_zero: Wrapping<i32>) -> Wrapping<i32> {
|
|||||||
example_rem(x, maybe_zero);
|
example_rem(x, maybe_zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn issue_12318() {
|
||||||
|
use core::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
|
||||||
|
let mut one: i32 = 1;
|
||||||
|
one.add_assign(1);
|
||||||
|
one.div_assign(1);
|
||||||
|
one.mul_assign(1);
|
||||||
|
one.rem_assign(1);
|
||||||
|
one.sub_assign(1);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -715,5 +715,17 @@ error: arithmetic operation that can potentially result in unexpected side-effec
|
|||||||
LL | x % maybe_zero
|
LL | x % maybe_zero
|
||||||
| ^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to 119 previous errors
|
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||||
|
--> tests/ui/arithmetic_side_effects.rs:527:5
|
||||||
|
|
|
||||||
|
LL | one.add_assign(1);
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: arithmetic operation that can potentially result in unexpected side-effects
|
||||||
|
--> tests/ui/arithmetic_side_effects.rs:531:5
|
||||||
|
|
|
||||||
|
LL | one.sub_assign(1);
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 121 previous errors
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user