-
Couldn't load subscription status.
- Fork 1.8k
[arithmetic_side_effects] Fix #15943
#15950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
| fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> { | ||
| let actual = peel_hir_expr_unary(expr).0; | ||
| if let hir::ExprKind::Lit(lit) = actual.kind | ||
| && let ast::LitKind::Int(n, _) = lit.node | ||
| { | ||
| return Some(n.get()); | ||
| } | ||
| if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { | ||
| return Some(n); | ||
| } | ||
| None | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why I placed these non-self mehods inside the impl block. It it better to put them outside IMO.
This comment has been minimized.
This comment has been minimized.
|
Lintcheck changes for b7f9216
This comment will be updated if you push new changes |
|
r? clippy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This currently removes too many rightful instances of the lint. This is not safe to broadly assume that any function taking an u8 and returning a u64 will limit the return values to the range of u8. For example, the following code overflows and this PR suppresses the warning from Clippy:
use std::time::Duration;
fn shift(x: u8) -> u64 {
1 << u64::from(x)
}
fn main() {
_ = Duration::from_secs(86400 * shift(1));
}| && Self::is_non_zero_u(cx, receiver_ty) | ||
| && let Some(1) = Self::literal_integer(cx, actual_rhs) | ||
| && is_non_zero_u(cx, receiver_ty) | ||
| && let Some(1) = literal_integer(cx, actual_rhs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| && let Some(1) = literal_integer(cx, actual_rhs) | |
| && literal_integer(cx, actual_rhs) == Some(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not my code -> a745e2c
|
|
||
| /// For example, if the variable `foo` of type `u32` comes from another variable `bar` of type | ||
| /// `u8` like `let foo = u64::from(bar)`. | ||
| fn find_original_primitive_ty<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<Ty<'tcx>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too broad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
| rhs_ty: Ty<'tcx>, | ||
| ) -> bool { | ||
| let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); | ||
| let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do one lookup instead of two by getting the diag item name and matching against it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); | ||
| let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); | ||
|
|
||
| // If the RHS is `NonZero<u*>`, then division or module by zero will never occur. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use proper doc comments /// to document functions (instead of simple code comments), so that the documentation can pop up in IDEs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not my code -> a51fc2a
| false | ||
| } | ||
|
|
||
| // For example, `i8` or `u128` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also mention that it matches &&i8 for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| /// smaller type. `0` and `1` suffixes indicate different sides. | ||
| /// | ||
| /// For example, `1000u64 + u64::from(some_runtime_variable_of_type_u8)`. | ||
| fn manage_integer_types( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
manage is unclear, especially when it looks like it does not perform any side effect operation and only returns a boolean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
| if self.has_allowed_binary(lhs_ty, rhs_ty) { | ||
| return; | ||
| } | ||
| if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { | ||
| if has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { | ||
| return; | ||
| } | ||
|
|
||
| let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { | ||
| if manage_integer_types(cx, op, (actual_lhs, lhs_ty), actual_rhs) { | ||
| return; | ||
| } | ||
| if manage_integer_types(cx, op, (actual_rhs, rhs_ty), actual_lhs) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if self.has_allowed_binary(lhs_ty, rhs_ty) { | |
| return; | |
| } | |
| if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { | |
| if has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { | |
| return; | |
| } | |
| let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { | |
| if manage_integer_types(cx, op, (actual_lhs, lhs_ty), actual_rhs) { | |
| return; | |
| } | |
| if manage_integer_types(cx, op, (actual_rhs, rhs_ty), actual_lhs) { | |
| return; | |
| } | |
| if self.has_allowed_binary(lhs_ty, rhs_ty) | |
| || has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) | |
| || manage_integer_types(cx, op, (actual_lhs, lhs_ty), actual_rhs) | |
| || manage_integer_types(cx, op, (actual_rhs, rhs_ty), actual_lhs) | |
| { | |
| return; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| let has_valid_op = if is_integer(lhs_ty) && is_integer(rhs_ty) { | ||
| if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { | ||
| // At least for integers, shifts are already handled by the CTFE | ||
| return; | ||
| } | ||
| match ( | ||
| Self::literal_integer(cx, actual_lhs), | ||
| Self::literal_integer(cx, actual_rhs), | ||
| ) { | ||
| match (literal_integer(cx, actual_lhs), literal_integer(cx, actual_rhs)) { | ||
| (None, None) => false, | ||
| (None, Some(n)) => match (&op, n) { | ||
| // 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, | ||
| // Adding or subtracting zeros is always a no-op | ||
| (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) | ||
| // Multiplication by 1 or 0 will never overflow | ||
| | (hir::BinOpKind::Mul, 0 | 1) | ||
| => true, | ||
| _ => false, | ||
| }, | ||
| (Some(n), None) => match (&op, n) { | ||
| // Adding or subtracting zeros is always a no-op | ||
| (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) | ||
| // Multiplication by 1 or 0 will never overflow | ||
| | (hir::BinOpKind::Mul, 0 | 1) | ||
| => true, | ||
| _ => false, | ||
| }, | ||
| (Some(_), Some(_)) => { | ||
| matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) | ||
| }, | ||
| } | ||
| } else { | ||
| false | ||
| }; | ||
| if !has_valid_op { | ||
| self.issue_lint(cx, expr); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since early returns are used throughout this lint, it makes sense to keep using them instead of going through a has_valid_op boolean variable.
| if is_integer(lhs_ty) && is_integer(rhs_ty) { | |
| if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { | |
| // At least for integers, shifts are already handled by the CTFE | |
| return; | |
| } | |
| match (literal_integer(cx, actual_lhs), literal_integer(cx, actual_rhs)) { | |
| (None, Some(n)) => match (&op, n) { | |
| // Division and module are always valid if applied to non-zero integers | |
| (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => return, | |
| // Adding or subtracting zeros is always a no-op | |
| (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) | |
| // Multiplication by 1 or 0 will never overflow | |
| | (hir::BinOpKind::Mul, 0 | 1) | |
| => return, | |
| _ => (), | |
| }, | |
| (Some(n), None) | |
| if matches!( | |
| (&op, n), | |
| // Adding or subtracting zeros is always a no-op | |
| (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) | |
| // Multiplication by 1 or 0 will never overflow | |
| | (hir::BinOpKind::Mul, 0 | 1) | |
| ) => | |
| { | |
| return; | |
| }, | |
| (Some(_), Some(_)) if matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) => return, | |
| _ => (), | |
| } | |
| } | |
| self.issue_lint(cx, expr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
You also need to remove the issue number from the commit message. And why did you ask for a reassignment of this PR? |
Ops... Fixed.
Done
It is not rare to wait weeks or months for a review and I personally experimented many occasions where the assigned reviewer passed the task to another member after a long hiatus. Most individuals are volunteers with limited free time, therefore, it is reasonable to seek help from reviewers that are more active at the current time unless they are too overloaded. And I also would like to tackle more features like signed integers in a following PR instead of delivering everything at once to make things a little easier. |
|
The move of non-self methods was meant to be a verbatim operation. Maybe I should have made that clear in the description or created a separate PR. Regardless, all concerns were addressed. Let me know if there is anything left. |
| /// example, `let foo = u64::from(bar)`. | ||
| fn find_original_primitive_ty<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<Ty<'tcx>> { | ||
| if let hir::ExprKind::Call(path, [arg]) = &expr.kind | ||
| && let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &path.kind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be easy to design a BitShift::from(u8) -> u64 which will again work as a counter example while passing through this check.
You should probably check whether this is a call to an implementation of From::from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let [sym::core, sym::convert, sym::From, sym::from] = cx.get_def_path(def_id).as_slice() works as intended but I don't know of a better solution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path.res(cx).opt_def_id().is_diag_item(&cx.tcx, sym::from_fn) should work
Can you add a test with another from (not from the From trait) to see that it works as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
A separate commit (in the same PR) would have make this clearer. |
22ec98f to
d6c29ac
Compare
| @@ -1,12 +1,13 @@ | |||
| use super::ARITHMETIC_SIDE_EFFECTS; | |||
| use crate::clippy_utils::res::MaybeQPath; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add as _ since you only need the trait for its methods.
Fix #15943
An initial foundation that can be used for future improvements. It is been a while since I touched Clippy so feel free to indicate better designs if applicable.
Looks like #15342 will help but it is unclear when it will be finished. This PR provides a partial solution for the current time.
changelog: [
arithmetic_side_effects]: Consider type conversion that won't overflow