Merge commit 'b52fb5234c' into clippyup
This commit is contained in:
parent
854f751b26
commit
98bf99e2f8
689 changed files with 15493 additions and 688 deletions
|
|
@ -1,119 +0,0 @@
|
|||
#![allow(
|
||||
// False positive
|
||||
clippy::match_same_arms
|
||||
)]
|
||||
|
||||
use super::ARITHMETIC;
|
||||
use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
const HARD_CODED_ALLOWED: &[&str] = &["std::num::Saturating", "std::string::String", "std::num::Wrapping"];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Arithmetic {
|
||||
allowed: FxHashSet<String>,
|
||||
// Used to check whether expressions are constants, such as in enum discriminants and consts
|
||||
const_span: Option<Span>,
|
||||
expr_span: Option<Span>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(Arithmetic => [ARITHMETIC]);
|
||||
|
||||
impl Arithmetic {
|
||||
#[must_use]
|
||||
pub fn new(mut allowed: FxHashSet<String>) -> Self {
|
||||
allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
|
||||
Self {
|
||||
allowed,
|
||||
const_span: None,
|
||||
expr_span: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given `expr` has any of the inner `allowed` elements.
|
||||
fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
self.allowed.contains(
|
||||
cx.typeck_results()
|
||||
.expr_ty(expr)
|
||||
.to_string()
|
||||
.split('<')
|
||||
.next()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
|
||||
span_lint(cx, ARITHMETIC, expr.span, "arithmetic detected");
|
||||
self.expr_span = Some(expr.span);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if self.expr_span.is_some() {
|
||||
return;
|
||||
}
|
||||
if let Some(span) = self.const_span && span.contains(expr.span) {
|
||||
return;
|
||||
}
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
let (
|
||||
hir::BinOpKind::Add
|
||||
| hir::BinOpKind::Sub
|
||||
| hir::BinOpKind::Mul
|
||||
| hir::BinOpKind::Div
|
||||
| hir::BinOpKind::Rem
|
||||
| hir::BinOpKind::Shl
|
||||
| hir::BinOpKind::Shr
|
||||
) = op.node else {
|
||||
return;
|
||||
};
|
||||
if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
|
||||
return;
|
||||
}
|
||||
self.issue_lint(cx, expr);
|
||||
},
|
||||
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
|
||||
// CTFE already takes care of things like `-1` that do not overflow.
|
||||
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
|
||||
self.issue_lint(cx, expr);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
|
||||
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
|
||||
match cx.tcx.hir().body_owner_kind(body_owner) {
|
||||
hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => {
|
||||
let body_span = cx.tcx.def_span(body_owner);
|
||||
if let Some(span) = self.const_span && span.contains(body_span) {
|
||||
return;
|
||||
}
|
||||
self.const_span = Some(body_span);
|
||||
},
|
||||
hir::BodyOwnerKind::Closure | hir::BodyOwnerKind::Fn => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
|
||||
let body_owner = cx.tcx.hir().body_owner(body.id());
|
||||
let body_span = cx.tcx.hir().span(body_owner);
|
||||
if let Some(span) = self.const_span && span.contains(body_span) {
|
||||
return;
|
||||
}
|
||||
self.const_span = None;
|
||||
}
|
||||
|
||||
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if Some(expr.span) == self.expr_span {
|
||||
self.expr_span = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
173
clippy_lints/src/operators/arithmetic_side_effects.rs
Normal file
173
clippy_lints/src/operators/arithmetic_side_effects.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#![allow(
|
||||
// False positive
|
||||
clippy::match_same_arms
|
||||
)]
|
||||
|
||||
use super::ARITHMETIC_SIDE_EFFECTS;
|
||||
use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
|
||||
use rustc_ast as ast;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::{Span, Spanned};
|
||||
|
||||
const HARD_CODED_ALLOWED: &[&str] = &[
|
||||
"f32",
|
||||
"f64",
|
||||
"std::num::Saturating",
|
||||
"std::string::String",
|
||||
"std::num::Wrapping",
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArithmeticSideEffects {
|
||||
allowed: FxHashSet<String>,
|
||||
// Used to check whether expressions are constants, such as in enum discriminants and consts
|
||||
const_span: Option<Span>,
|
||||
expr_span: Option<Span>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(ArithmeticSideEffects => [ARITHMETIC_SIDE_EFFECTS]);
|
||||
|
||||
impl ArithmeticSideEffects {
|
||||
#[must_use]
|
||||
pub fn new(mut allowed: FxHashSet<String>) -> Self {
|
||||
allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from));
|
||||
Self {
|
||||
allowed,
|
||||
const_span: None,
|
||||
expr_span: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks assign operators (+=, -=, *=, /=) of integers in a non-constant environment that
|
||||
/// won't overflow.
|
||||
fn has_valid_assign_op(op: &Spanned<hir::BinOpKind>, rhs: &hir::Expr<'_>, rhs_refs: Ty<'_>) -> bool {
|
||||
if !Self::is_literal_integer(rhs, rhs_refs) {
|
||||
return false;
|
||||
}
|
||||
if let hir::BinOpKind::Div | hir::BinOpKind::Mul = op.node
|
||||
&& let hir::ExprKind::Lit(ref lit) = rhs.kind
|
||||
&& let ast::LitKind::Int(1, _) = lit.node
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks "raw" binary operators (+, -, *, /) of integers in a non-constant environment
|
||||
/// already handled by the CTFE.
|
||||
fn has_valid_bin_op(lhs: &hir::Expr<'_>, lhs_refs: Ty<'_>, rhs: &hir::Expr<'_>, rhs_refs: Ty<'_>) -> bool {
|
||||
Self::is_literal_integer(lhs, lhs_refs) && Self::is_literal_integer(rhs, rhs_refs)
|
||||
}
|
||||
|
||||
/// Checks if the given `expr` has any of the inner `allowed` elements.
|
||||
fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
self.allowed.contains(
|
||||
cx.typeck_results()
|
||||
.expr_ty(expr)
|
||||
.to_string()
|
||||
.split('<')
|
||||
.next()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Explicit integers like `1` or `i32::MAX`. Does not take into consideration references.
|
||||
fn is_literal_integer(expr: &hir::Expr<'_>, expr_refs: Ty<'_>) -> bool {
|
||||
let is_integral = expr_refs.is_integral();
|
||||
let is_literal = matches!(expr.kind, hir::ExprKind::Lit(_));
|
||||
is_integral && is_literal
|
||||
}
|
||||
|
||||
fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
|
||||
span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, "arithmetic detected");
|
||||
self.expr_span = Some(expr.span);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn manage_bin_ops(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
op: &Spanned<hir::BinOpKind>,
|
||||
lhs: &hir::Expr<'_>,
|
||||
rhs: &hir::Expr<'_>,
|
||||
) {
|
||||
if constant_simple(cx, cx.typeck_results(), expr).is_some() {
|
||||
return;
|
||||
}
|
||||
if !matches!(
|
||||
op.node,
|
||||
hir::BinOpKind::Add
|
||||
| hir::BinOpKind::Sub
|
||||
| hir::BinOpKind::Mul
|
||||
| hir::BinOpKind::Div
|
||||
| hir::BinOpKind::Rem
|
||||
| hir::BinOpKind::Shl
|
||||
| hir::BinOpKind::Shr
|
||||
) {
|
||||
return;
|
||||
};
|
||||
if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
|
||||
return;
|
||||
}
|
||||
let lhs_refs = cx.typeck_results().expr_ty(lhs).peel_refs();
|
||||
let rhs_refs = cx.typeck_results().expr_ty(rhs).peel_refs();
|
||||
let has_valid_assign_op = Self::has_valid_assign_op(op, rhs, rhs_refs);
|
||||
if has_valid_assign_op || Self::has_valid_bin_op(lhs, lhs_refs, rhs, rhs_refs) {
|
||||
return;
|
||||
}
|
||||
self.issue_lint(cx, expr);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
|
||||
return;
|
||||
}
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
self.manage_bin_ops(cx, expr, op, lhs, rhs);
|
||||
},
|
||||
hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
|
||||
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
|
||||
self.issue_lint(cx, expr);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
|
||||
let body_owner = cx.tcx.hir().body_owner(body.id());
|
||||
let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner);
|
||||
let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
|
||||
if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
|
||||
let body_span = cx.tcx.hir().span_with_body(body_owner);
|
||||
if let Some(span) = self.const_span && span.contains(body_span) {
|
||||
return;
|
||||
}
|
||||
self.const_span = Some(body_span);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
|
||||
let body_owner = cx.tcx.hir().body_owner(body.id());
|
||||
let body_span = cx.tcx.hir().span(body_owner);
|
||||
if let Some(span) = self.const_span && span.contains(body_span) {
|
||||
return;
|
||||
}
|
||||
self.const_span = None;
|
||||
}
|
||||
|
||||
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if Some(expr.span) == self.expr_span {
|
||||
self.expr_span = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ mod ptr_eq;
|
|||
mod self_assignment;
|
||||
mod verbose_bit_mask;
|
||||
|
||||
pub(crate) mod arithmetic;
|
||||
pub(crate) mod arithmetic_side_effects;
|
||||
|
||||
use rustc_hir::{Body, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -61,25 +61,29 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for any kind of arithmetic operation of any type.
|
||||
/// Checks any kind of arithmetic operation of any type.
|
||||
///
|
||||
/// Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust
|
||||
/// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
|
||||
/// or can panic (`/`, `%`). Known safe built-in types like `Wrapping` or `Saturing` are filtered
|
||||
/// away.
|
||||
/// or can panic (`/`, `%`).
|
||||
///
|
||||
/// Known safe built-in types like `Wrapping` or `Saturing`, floats, operations in constant
|
||||
/// environments, allowed types and non-constant operations that won't overflow are ignored.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Integer overflow will trigger a panic in debug builds or will wrap in
|
||||
/// release mode. Division by zero will cause a panic in either mode. In some applications one
|
||||
/// wants explicitly checked, wrapping or saturating arithmetic.
|
||||
/// For integers, overflow will trigger a panic in debug builds or wrap the result in
|
||||
/// release mode; division by zero will cause a panic in either mode. As a result, it is
|
||||
/// desirable to explicitly call checked, wrapping or saturating arithmetic methods.
|
||||
///
|
||||
/// #### Example
|
||||
/// ```rust
|
||||
/// # let a = 0;
|
||||
/// a + 1;
|
||||
/// // `n` can be any number, including `i32::MAX`.
|
||||
/// fn foo(n: i32) -> i32 {
|
||||
/// n + 1
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Third-party types also tend to overflow.
|
||||
/// Third-party types can also overflow or present unwanted side-effects.
|
||||
///
|
||||
/// #### Example
|
||||
/// ```ignore,rust
|
||||
|
|
@ -88,11 +92,11 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
///
|
||||
/// ### Allowed types
|
||||
/// Custom allowed types can be specified through the "arithmetic-allowed" filter.
|
||||
/// Custom allowed types can be specified through the "arithmetic-side-effects-allowed" filter.
|
||||
#[clippy::version = "1.64.0"]
|
||||
pub ARITHMETIC,
|
||||
pub ARITHMETIC_SIDE_EFFECTS,
|
||||
restriction,
|
||||
"any arithmetic expression that could overflow or panic"
|
||||
"any arithmetic expression that can cause side effects like overflows or panics"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -785,7 +789,7 @@ pub struct Operators {
|
|||
}
|
||||
impl_lint_pass!(Operators => [
|
||||
ABSURD_EXTREME_COMPARISONS,
|
||||
ARITHMETIC,
|
||||
ARITHMETIC_SIDE_EFFECTS,
|
||||
INTEGER_ARITHMETIC,
|
||||
FLOAT_ARITHMETIC,
|
||||
ASSIGN_OP_PATTERN,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue