Skip to content

Commit e33cba5

Browse files
committed
Auto merge of rust-lang#12126 - teor2345:patch-1, r=llogiq
Fix sign-handling bugs and false negatives in `cast_sign_loss` **Note: anyone should feel free to move this PR forward, I might not see notifications from reviewers.** changelog: [`cast_sign_loss`]: Fix sign-handling bugs and false negatives This PR fixes some arithmetic bugs and false negatives in PR rust-lang#11883 (and maybe earlier PRs). Cc `@J-ZhengLi` I haven't updated the tests yet. I was hoping for some initial feedback before adding tests to cover the cases listed below. Here are the issues I've attempted to fix: #### `abs()` can return a negative value in release builds Example: ```rust i32::MIN.abs() ``` https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=022d200f9ef6ee72f629c0c9c1af11b8 Docs: https://doc.rust-lang.org/std/primitive.i32.html#method.abs Other overflows that produce negative values could cause false negatives (and underflows could produce false positives), but they're harder to detect. #### Values with uncertain signs can be positive or negative Any number of values with uncertain signs cause the whole expression to have an uncertain sign, because an uncertain sign can be positive or negative. Example (from UI tests): ```rust fn main() { foo(a: i32, b: i32, c: i32) -> u32 { (a * b * c * c) as u32 //~^ ERROR: casting `i32` to `u32` may lose the sign of the value } println!("{}", foo(1, -1, 1)); } ``` https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=165d2e2676ee8343b1b9fe60db32aadd #### Handle `expect()` the same way as `unwrap()` Since we're ignoring `unwrap()` we might as well do the same with `expect()`. This doesn't seem to have tests but I'm happy to add some like `Some(existing_test).unwrap() as u32`. #### A negative base to an odd exponent is guaranteed to be negative An integer `pow()`'s sign is only uncertain when its operants are uncertain. (Ignoring overflow.) Example: ```rust ((-2_i32).pow(3) * -2) as u32 ``` This offsets some of the false positives created by one or more uncertain signs producing an uncertain sign. (Rather than just an odd number of uncertain signs.) #### Both sides of a multiply or divide should be peeled recursively I'm not sure why the lhs was peeled recursively, and the rhs was left intact. But the sign of any sequence of multiplies and divides is determined by the signs of its operands. (Ignoring overflow.) I'm not sure what to use as an example here, because most expressions I want to use are const-evaluable. But if `p()` is [a non-const function that returns a positive value](https://doc.rust-lang.org/std/primitive.i32.html#method.isqrt), and if the lint handles unary negation, these should all lint: ```rust fn peel_all(x: i32) { (-p(x) * -p(x) * -p(x)) as u32; ((-p(x) * -p(x)) * -p(x)) as u32; (-p(x) * (-p(x) * -p(x))) as u32; } ``` #### The right hand side of a Rem doesn't change the sign Unlike Mul and Div, > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend. https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators I'm not sure what to use as an example here, because most expressions I want to use are const-evaluable. But if `p()` is [a non-const function that returns a positive value](https://doc.rust-lang.org/std/primitive.i32.html#method.isqrt), and if the lint handles unary negation, only the first six expressions should lint. The expressions that start with a constant should lint (or not lint) regardless of whether the lint supports `p()` or unary negation, because only the dividend's sign matters. Example: ```rust fn rem_lhs(x: i32) { (-p(x) % -1) as u32; (-p(x) % 1) as u32; (-1 % -p(x)) as u32; (-1 % p(x)) as u32; (-1 % -x) as u32; (-1 % x) as u32; // These shouldn't lint: (p(x) % -1) as u32; (p(x) % 1) as u32; (1 % -p(x)) as u32; (1 % p(x)) as u32; (1 % -x) as u32; (1 % x) as u32; } ``` #### There's no need to bail on other expressions When peeling, any other operators or expressions can be left intact and sent to the constant evaluator. If these expressions can be evaluated, this offsets some of the false positives created by one or more uncertain signs producing an uncertain sign. If not, they end up marked as having uncertain sign.
2 parents fb06081 + 1e3c55e commit e33cba5

File tree

3 files changed

+503
-144
lines changed

3 files changed

+503
-144
lines changed
+246-64
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
1+
use std::convert::Infallible;
2+
use std::ops::ControlFlow;
3+
14
use clippy_utils::consts::{constant, Constant};
25
use clippy_utils::diagnostics::span_lint;
3-
use clippy_utils::{clip, method_chain_args, sext};
6+
use clippy_utils::visitors::{for_each_expr, Descend};
7+
use clippy_utils::{method_chain_args, sext};
48
use rustc_hir::{BinOpKind, Expr, ExprKind};
59
use rustc_lint::LateContext;
6-
use rustc_middle::ty::{self, Ty, UintTy};
10+
use rustc_middle::ty::{self, Ty};
711

812
use super::CAST_SIGN_LOSS;
913

10-
const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
14+
/// A list of methods that can never return a negative value.
15+
/// Includes methods that panic rather than returning a negative value.
16+
///
17+
/// Methods that can overflow and return a negative value must not be included in this list,
18+
/// because casting their return values can still result in sign loss.
19+
const METHODS_RET_POSITIVE: &[&str] = &[
20+
"checked_abs",
21+
"saturating_abs",
22+
"isqrt",
23+
"checked_isqrt",
24+
"rem_euclid",
25+
"checked_rem_euclid",
26+
"wrapping_rem_euclid",
27+
];
28+
29+
/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details.
30+
///
31+
/// Methods that can overflow and return a negative value must not be included in this list,
32+
/// because casting their return values can still result in sign loss.
33+
const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"];
1134

12-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
35+
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
36+
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
37+
38+
pub(super) fn check<'cx>(
39+
cx: &LateContext<'cx>,
40+
expr: &Expr<'_>,
41+
cast_op: &Expr<'_>,
42+
cast_from: Ty<'cx>,
43+
cast_to: Ty<'_>,
44+
) {
1345
if should_lint(cx, cast_op, cast_from, cast_to) {
1446
span_lint(
1547
cx,
@@ -20,35 +52,27 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
2052
}
2153
}
2254

23-
fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool {
55+
fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool {
2456
match (cast_from.is_integral(), cast_to.is_integral()) {
2557
(true, true) => {
2658
if !cast_from.is_signed() || cast_to.is_signed() {
2759
return false;
2860
}
2961

30-
// Don't lint if `cast_op` is known to be positive.
62+
// Don't lint if `cast_op` is known to be positive, ignoring overflow.
3163
if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) {
3264
return false;
3365
}
3466

35-
let (mut uncertain_count, mut negative_count) = (0, 0);
36-
// Peel off possible binary expressions, e.g. x * x * y => [x, x, y]
37-
let Some(exprs) = exprs_with_selected_binop_peeled(cast_op) else {
38-
// Assume cast sign lose if we cannot determine the sign of `cast_op`
39-
return true;
40-
};
41-
for expr in exprs {
42-
let ty = cx.typeck_results().expr_ty(expr);
43-
match expr_sign(cx, expr, ty) {
44-
Sign::Negative => negative_count += 1,
45-
Sign::Uncertain => uncertain_count += 1,
46-
Sign::ZeroOrPositive => (),
47-
};
67+
if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) {
68+
return false;
69+
}
70+
71+
if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) {
72+
return false;
4873
}
4974

50-
// Lint if there are odd number of uncertain or negative results
51-
uncertain_count % 2 == 1 || negative_count % 2 == 1
75+
true
5276
},
5377

5478
(false, true) => !cast_to.is_signed(),
@@ -57,7 +81,13 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
5781
}
5882
}
5983

60-
fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option<i128> {
84+
fn get_const_signed_int_eval<'cx>(
85+
cx: &LateContext<'cx>,
86+
expr: &Expr<'_>,
87+
ty: impl Into<Option<Ty<'cx>>>,
88+
) -> Option<i128> {
89+
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
90+
6191
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
6292
&& let ty::Int(ity) = *ty.kind()
6393
{
@@ -66,29 +96,52 @@ fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Opti
6696
None
6797
}
6898

99+
fn get_const_unsigned_int_eval<'cx>(
100+
cx: &LateContext<'cx>,
101+
expr: &Expr<'_>,
102+
ty: impl Into<Option<Ty<'cx>>>,
103+
) -> Option<u128> {
104+
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
105+
106+
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
107+
&& let ty::Uint(_ity) = *ty.kind()
108+
{
109+
return Some(n);
110+
}
111+
None
112+
}
113+
114+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
69115
enum Sign {
70116
ZeroOrPositive,
71117
Negative,
72118
Uncertain,
73119
}
74120

75-
fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
121+
fn expr_sign<'cx>(cx: &LateContext<'cx>, expr: &Expr<'_>, ty: impl Into<Option<Ty<'cx>>>) -> Sign {
76122
// Try evaluate this expr first to see if it's positive
77-
if let Some(val) = get_const_int_eval(cx, expr, ty) {
123+
if let Some(val) = get_const_signed_int_eval(cx, expr, ty) {
78124
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
79125
}
126+
if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) {
127+
return Sign::ZeroOrPositive;
128+
}
129+
80130
// Calling on methods that always return non-negative values.
81131
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
82132
let mut method_name = path.ident.name.as_str();
83133

84-
if method_name == "unwrap"
85-
&& let Some(arglist) = method_chain_args(expr, &["unwrap"])
134+
// Peel unwrap(), expect(), etc.
135+
while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name)
136+
&& let Some(arglist) = method_chain_args(expr, &[found_name])
86137
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
87138
{
139+
// The original type has changed, but we can't use `ty` here anyway, because it has been
140+
// moved.
88141
method_name = inner_path.ident.name.as_str();
89142
}
90143

91-
if method_name == "pow"
144+
if METHODS_POW.iter().any(|&name| method_name == name)
92145
&& let [arg] = args
93146
{
94147
return pow_call_result_sign(cx, caller, arg);
@@ -100,53 +153,182 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
100153
Sign::Uncertain
101154
}
102155

103-
/// Return the sign of the `pow` call's result.
156+
/// Return the sign of the `pow` call's result, ignoring overflow.
104157
///
105-
/// If the caller is a positive number, the result is always positive,
106-
/// If the `power_of` is a even number, the result is always positive as well,
107-
/// Otherwise a [`Sign::Uncertain`] will be returned.
108-
fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign {
109-
let caller_ty = cx.typeck_results().expr_ty(caller);
110-
if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty)
111-
&& caller_val >= 0
112-
{
113-
return Sign::ZeroOrPositive;
158+
/// If the base is positive, the result is always positive.
159+
/// If the exponent is a even number, the result is always positive,
160+
/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always
161+
/// negative.
162+
///
163+
/// Otherwise, returns [`Sign::Uncertain`].
164+
fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
165+
let base_sign = expr_sign(cx, base, None);
166+
167+
// Rust's integer pow() functions take an unsigned exponent.
168+
let exponent_val = get_const_unsigned_int_eval(cx, exponent, None);
169+
let exponent_is_even = exponent_val.map(|val| val % 2 == 0);
170+
171+
match (base_sign, exponent_is_even) {
172+
// Non-negative bases always return non-negative results, ignoring overflow.
173+
(Sign::ZeroOrPositive, _) |
174+
// Any base raised to an even exponent is non-negative.
175+
// These both hold even if we don't know the value of the base.
176+
(_, Some(true))
177+
=> Sign::ZeroOrPositive,
178+
179+
// A negative base raised to an odd exponent is non-negative.
180+
(Sign::Negative, Some(false)) => Sign::Negative,
181+
182+
// Negative/unknown base to an unknown exponent, or unknown base to an odd exponent.
183+
// Could be negative or positive depending on the actual values.
184+
(Sign::Negative | Sign::Uncertain, None) |
185+
(Sign::Uncertain, Some(false)) => Sign::Uncertain,
114186
}
187+
}
115188

116-
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of)
117-
&& clip(cx.tcx, n, UintTy::U32) % 2 == 0
118-
{
119-
return Sign::ZeroOrPositive;
189+
/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`],
190+
/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details.
191+
///
192+
/// Returns the sign of the list of peeled expressions.
193+
fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
194+
let mut negative_count = 0;
195+
196+
// Peel off possible binary expressions, for example:
197+
// x * x / y => [x, x, y]
198+
// a % b => [a]
199+
let exprs = exprs_with_muldiv_binop_peeled(expr);
200+
for expr in exprs {
201+
match expr_sign(cx, expr, None) {
202+
Sign::Negative => negative_count += 1,
203+
// A mul/div is:
204+
// - uncertain if there are any uncertain values (because they could be negative or positive),
205+
Sign::Uncertain => return Sign::Uncertain,
206+
Sign::ZeroOrPositive => (),
207+
};
120208
}
121209

122-
Sign::Uncertain
210+
// A mul/div is:
211+
// - negative if there are an odd number of negative values,
212+
// - positive or zero otherwise.
213+
if negative_count % 2 == 1 {
214+
Sign::Negative
215+
} else {
216+
Sign::ZeroOrPositive
217+
}
218+
}
219+
220+
/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive.
221+
/// See [`exprs_with_add_binop_peeled()`] for details.
222+
///
223+
/// Returns the sign of the list of peeled expressions.
224+
fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
225+
let mut negative_count = 0;
226+
let mut positive_count = 0;
227+
228+
// Peel off possible binary expressions, for example:
229+
// a + b + c => [a, b, c]
230+
let exprs = exprs_with_add_binop_peeled(expr);
231+
for expr in exprs {
232+
match expr_sign(cx, expr, None) {
233+
Sign::Negative => negative_count += 1,
234+
// A sum is:
235+
// - uncertain if there are any uncertain values (because they could be negative or positive),
236+
Sign::Uncertain => return Sign::Uncertain,
237+
Sign::ZeroOrPositive => positive_count += 1,
238+
};
239+
}
240+
241+
// A sum is:
242+
// - positive or zero if there are only positive (or zero) values,
243+
// - negative if there are only negative (or zero) values, or
244+
// - uncertain if there are both.
245+
// We could split Zero out into its own variant, but we don't yet.
246+
if negative_count == 0 {
247+
Sign::ZeroOrPositive
248+
} else if positive_count == 0 {
249+
Sign::Negative
250+
} else {
251+
Sign::Uncertain
252+
}
123253
}
124254

125255
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
126-
/// which the result could always be positive under certain condition.
256+
/// where the result depends on:
257+
/// - the number of negative values in the entire expression, or
258+
/// - the number of negative values on the left hand side of the expression.
259+
/// Ignores overflow.
127260
///
128-
/// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will
129-
/// return `None`
130-
fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option<Vec<&'a Expr<'a>>> {
131-
#[inline]
132-
fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> {
133-
match expr.kind {
134-
ExprKind::Binary(op, lhs, rhs) => {
135-
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) {
136-
collect_operands(lhs, operands);
137-
operands.push(rhs);
138-
} else {
139-
// Things are complicated when there are other binary ops exist,
140-
// abort checking by returning `None` for now.
141-
return None;
142-
}
143-
},
144-
_ => operands.push(expr),
261+
///
262+
/// Expressions using other operators are preserved, so we can try to evaluate them later.
263+
fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
264+
let mut res = vec![];
265+
266+
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
267+
// We don't check for mul/div/rem methods here, but we could.
268+
if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
269+
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
270+
// For binary operators where both sides contribute to the sign of the result,
271+
// collect all their operands, recursively. This ignores overflow.
272+
ControlFlow::Continue(Descend::Yes)
273+
} else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) {
274+
// For binary operators where the left hand side determines the sign of the result,
275+
// only collect that side, recursively. Overflow panics, so this always holds.
276+
//
277+
// Large left shifts turn negatives into zeroes, so we can't use it here.
278+
//
279+
// > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend
280+
// > ...
281+
// > Arithmetic right shift on signed integer types
282+
// https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
283+
284+
// We want to descend into the lhs, but skip the rhs.
285+
// That's tricky to do using for_each_expr(), so we just keep the lhs intact.
286+
res.push(lhs);
287+
ControlFlow::Continue(Descend::No)
288+
} else {
289+
// The sign of the result of other binary operators depends on the values of the operands,
290+
// so try to evaluate the expression.
291+
res.push(sub_expr);
292+
ControlFlow::Continue(Descend::No)
293+
}
294+
} else {
295+
// For other expressions, including unary operators and constants, try to evaluate the expression.
296+
res.push(sub_expr);
297+
ControlFlow::Continue(Descend::No)
145298
}
146-
Some(())
147-
}
299+
});
148300

301+
res
302+
}
303+
304+
/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on:
305+
/// - all the expressions being positive, or
306+
/// - all the expressions being negative.
307+
/// Ignores overflow.
308+
///
309+
/// Expressions using other operators are preserved, so we can try to evaluate them later.
310+
fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
149311
let mut res = vec![];
150-
collect_operands(expr, &mut res)?;
151-
Some(res)
312+
313+
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
314+
// We don't check for add methods here, but we could.
315+
if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind {
316+
if matches!(op.node, BinOpKind::Add) {
317+
// For binary operators where both sides contribute to the sign of the result,
318+
// collect all their operands, recursively. This ignores overflow.
319+
ControlFlow::Continue(Descend::Yes)
320+
} else {
321+
// The sign of the result of other binary operators depends on the values of the operands,
322+
// so try to evaluate the expression.
323+
res.push(sub_expr);
324+
ControlFlow::Continue(Descend::No)
325+
}
326+
} else {
327+
// For other expressions, including unary operators and constants, try to evaluate the expression.
328+
res.push(sub_expr);
329+
ControlFlow::Continue(Descend::No)
330+
}
331+
});
332+
333+
res
152334
}

0 commit comments

Comments
 (0)