blob: 907861755621a5406eb057b2365ff55caa9bdead [file] [log] [blame]
use std::convert::Infallible;
use std::ops::ControlFlow;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{method_chain_args, sext};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use super::CAST_SIGN_LOSS;
/// A list of methods that can never return a negative value.
/// Includes methods that panic rather than returning a negative value.
///
/// Methods that can overflow and return a negative value must not be included in this list,
/// because casting their return values can still result in sign loss.
const METHODS_RET_POSITIVE: &[&str] = &[
"checked_abs",
"saturating_abs",
"isqrt",
"checked_isqrt",
"rem_euclid",
"checked_rem_euclid",
"wrapping_rem_euclid",
];
/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details.
///
/// Methods that can overflow and return a negative value must not be included in this list,
/// because casting their return values can still result in sign loss.
const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"];
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
pub(super) fn check<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
cast_op: &Expr<'_>,
cast_from: Ty<'cx>,
cast_to: Ty<'_>,
) {
if should_lint(cx, cast_op, cast_from, cast_to) {
span_lint(
cx,
CAST_SIGN_LOSS,
expr.span,
&format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"),
);
}
}
fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool {
match (cast_from.is_integral(), cast_to.is_integral()) {
(true, true) => {
if !cast_from.is_signed() || cast_to.is_signed() {
return false;
}
// Don't lint if `cast_op` is known to be positive, ignoring overflow.
if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) {
return false;
}
if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) {
return false;
}
if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) {
return false;
}
true
},
(false, true) => !cast_to.is_signed(),
(_, _) => false,
}
}
fn get_const_signed_int_eval<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
ty: impl Into<Option<Ty<'cx>>>,
) -> Option<i128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
&& let ty::Int(ity) = *ty.kind()
{
return Some(sext(cx.tcx, n, ity));
}
None
}
fn get_const_unsigned_int_eval<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
ty: impl Into<Option<Ty<'cx>>>,
) -> Option<u128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
&& let ty::Uint(_ity) = *ty.kind()
{
return Some(n);
}
None
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Sign {
ZeroOrPositive,
Negative,
Uncertain,
}
fn expr_sign<'cx, 'tcx>(cx: &LateContext<'cx>, mut expr: &'tcx Expr<'tcx>, ty: impl Into<Option<Ty<'cx>>>) -> Sign {
// Try evaluate this expr first to see if it's positive
if let Some(val) = get_const_signed_int_eval(cx, expr, ty) {
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
}
if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) {
return Sign::ZeroOrPositive;
}
// Calling on methods that always return non-negative values.
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
let mut method_name = path.ident.name.as_str();
// Peel unwrap(), expect(), etc.
while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name)
&& let Some(arglist) = method_chain_args(expr, &[found_name])
&& let ExprKind::MethodCall(inner_path, recv, ..) = &arglist[0].0.kind
{
// The original type has changed, but we can't use `ty` here anyway, because it has been
// moved.
method_name = inner_path.ident.name.as_str();
expr = recv;
}
if METHODS_POW.iter().any(|&name| method_name == name)
&& let [arg] = args
{
return pow_call_result_sign(cx, caller, arg);
} else if METHODS_RET_POSITIVE.iter().any(|&name| method_name == name) {
return Sign::ZeroOrPositive;
}
}
Sign::Uncertain
}
/// Return the sign of the `pow` call's result, ignoring overflow.
///
/// If the base is positive, the result is always positive.
/// If the exponent is a even number, the result is always positive,
/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always
/// negative.
///
/// Otherwise, returns [`Sign::Uncertain`].
fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
let base_sign = expr_sign(cx, base, None);
// Rust's integer pow() functions take an unsigned exponent.
let exponent_val = get_const_unsigned_int_eval(cx, exponent, None);
let exponent_is_even = exponent_val.map(|val| val % 2 == 0);
match (base_sign, exponent_is_even) {
// Non-negative bases always return non-negative results, ignoring overflow.
(Sign::ZeroOrPositive, _) |
// Any base raised to an even exponent is non-negative.
// These both hold even if we don't know the value of the base.
(_, Some(true))
=> Sign::ZeroOrPositive,
// A negative base raised to an odd exponent is non-negative.
(Sign::Negative, Some(false)) => Sign::Negative,
// Negative/unknown base to an unknown exponent, or unknown base to an odd exponent.
// Could be negative or positive depending on the actual values.
(Sign::Negative | Sign::Uncertain, None) |
(Sign::Uncertain, Some(false)) => Sign::Uncertain,
}
}
/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`],
/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details.
///
/// Returns the sign of the list of peeled expressions.
fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
let mut negative_count = 0;
// Peel off possible binary expressions, for example:
// x * x / y => [x, x, y]
// a % b => [a]
let exprs = exprs_with_muldiv_binop_peeled(expr);
for expr in exprs {
match expr_sign(cx, expr, None) {
Sign::Negative => negative_count += 1,
// A mul/div is:
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => (),
};
}
// A mul/div is:
// - negative if there are an odd number of negative values,
// - positive or zero otherwise.
if negative_count % 2 == 1 {
Sign::Negative
} else {
Sign::ZeroOrPositive
}
}
/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive.
/// See [`exprs_with_add_binop_peeled()`] for details.
///
/// Returns the sign of the list of peeled expressions.
fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
let mut negative_count = 0;
let mut positive_count = 0;
// Peel off possible binary expressions, for example:
// a + b + c => [a, b, c]
let exprs = exprs_with_add_binop_peeled(expr);
for expr in exprs {
match expr_sign(cx, expr, None) {
Sign::Negative => negative_count += 1,
// A sum is:
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => positive_count += 1,
};
}
// A sum is:
// - positive or zero if there are only positive (or zero) values,
// - negative if there are only negative (or zero) values, or
// - uncertain if there are both.
// We could split Zero out into its own variant, but we don't yet.
if negative_count == 0 {
Sign::ZeroOrPositive
} else if positive_count == 0 {
Sign::Negative
} else {
Sign::Uncertain
}
}
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
/// where the result depends on:
/// - the number of negative values in the entire expression, or
/// - the number of negative values on the left hand side of the expression.
/// Ignores overflow.
///
///
/// Expressions using other operators are preserved, so we can try to evaluate them later.
fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
let mut res = vec![];
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
// We don't check for mul/div/rem methods here, but we could.
if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
// For binary operators where both sides contribute to the sign of the result,
// collect all their operands, recursively. This ignores overflow.
ControlFlow::Continue(Descend::Yes)
} else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) {
// For binary operators where the left hand side determines the sign of the result,
// only collect that side, recursively. Overflow panics, so this always holds.
//
// Large left shifts turn negatives into zeroes, so we can't use it here.
//
// > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend
// > ...
// > Arithmetic right shift on signed integer types
// https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
// We want to descend into the lhs, but skip the rhs.
// That's tricky to do using for_each_expr(), so we just keep the lhs intact.
res.push(lhs);
ControlFlow::Continue(Descend::No)
} else {
// The sign of the result of other binary operators depends on the values of the operands,
// so try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
} else {
// For other expressions, including unary operators and constants, try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
});
res
}
/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on:
/// - all the expressions being positive, or
/// - all the expressions being negative.
/// Ignores overflow.
///
/// Expressions using other operators are preserved, so we can try to evaluate them later.
fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
let mut res = vec![];
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
// We don't check for add methods here, but we could.
if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind {
if matches!(op.node, BinOpKind::Add) {
// For binary operators where both sides contribute to the sign of the result,
// collect all their operands, recursively. This ignores overflow.
ControlFlow::Continue(Descend::Yes)
} else {
// The sign of the result of other binary operators depends on the values of the operands,
// so try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
} else {
// For other expressions, including unary operators and constants, try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
});
res
}