Consider type conversion that won't overflow#15950
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.
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.
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.
| && let Some(1) = literal_integer(cx, actual_rhs) | |
| && literal_integer(cx, actual_rhs) == Some(1) |
|
|
||
| /// 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>> { |
| 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.
You can do one lookup instead of two by getting the diag item name and matching against it.
| 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.
Please use proper doc comments /// to document functions (instead of simple code comments), so that the documentation can pop up in IDEs.
| false | ||
| } | ||
|
|
||
| // For example, `i8` or `u128` |
There was a problem hiding this comment.
This should also mention that it matches &&i8 for example.
| /// 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.
manage is unclear, especially when it looks like it does not perform any side effect operation and only returns a boolean.
| 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.
| 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.
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); |
|
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.
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.
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.
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?
A separate commit (in the same PR) would have make this clearer. |
22ec98f to
d6c29ac
Compare
|
ping @samueltardieu |
When review comments have been adressed, you should re-request a review which will change the I'll add this to my todo list. |
arithmetic_side_effects] Fix #15943
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