-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
arithmetic_overflow lint not triggered for overflowing shifts in promoteds #117949
Comments
I think the issue here is that the format_args macro implicitly creates a reference to its arguments. fn main() {
let _a = &(1 << 32);
} has the same issue |
@rustbot claim |
fn main() {
let _x = [3; {
1 << 32
}];
}
Edit fn main() {
let _x = [3; {
1 << 64
}];
} So this issue is only with promoted consts. |
The MIR visitor @clubby789 mentioned above is rust/compiler/rustc_mir_transform/src/const_prop_lint.rs Lines 45 to 48 in 33f6af8
Due to it the lint doesn't do anything for promoted MIR bodies. Removing the check fixes the issue and the expected overflow error now appears. So indeed the check is a direct cause of the issue. Further, note the comment on top of the check. It says the lint doesn't need to fire for promoteds because they are checked during MIR interpretation anyway. However, that may not always happen. In our case, for example, the MIR emitted is as below: promoted[0] in main: &i32 = {
let mut _0: &i32;
let mut _1: i32;
bb0: {
_1 = Shl(const 1_i32, const 32_i32);
_0 = &_1;
return;
}
} As you can see there is no I guess the reason for the missing Anyway, given all of the above I see two options for solving the issue:
Being super new to this area, I'm not sure which one is better and will cause less collateral damage. Someone with experience in this area please advise. |
The example in the issue description now produces an error about arithmetic overflow. Other variants do not: https://godbolt.org/z/4b8seYe9d |
Before we can fix this issue, we need to understand the desired behaviour with regard to promoteds. As per this doc and the discussion in #74696 we don't want overflowing promoteds in dead code to emit an error. However, in the present issue the overflowing promoted occurs in non-dead code and therefore it should ideally emit an error. Although, that in turn conflicts with the expectation in this test case rust/tests/ui/consts/promotion.rs Lines 28 to 30 in 84a554c
assert_static() here are not dead code). So I'm a bit confused as to what the expected behaviour is.
@RalfJung can you please weigh in? |
I'm not entirely sure what is being asked. The key point is that the following programs must behave the same way: if b {
let _val = 1 << 32;
} and if b {
let _val = &(1 << 32);
} Replacing In particular, all of these programs must built successfully with The docs you link are about not making the second program (the one with promotion) a hard error. That does not exclude the possibility of an err-by-default lint, those are very different. If the arithmetic_overflow lint is err-by-default (I don't remember right now whether it is), it should trigger consistently inside and outside of promoteds. Doing this correctly, and consistently across builds with and without debug assertions, is tricky due to the way we generate MIR. For instance we had issues with duplicate lints being emitted for a long time. Seems like right now we have the opposite issue where the lint is not firing often enough. |
Interestingly, this does lint as it should, both with and without the pub fn foo(b: bool) {
if b {
let _val = &(3u8 + 255u8);
}
} I don't know where that lint comes from, if it's not const_prop_lint. |
Thanks for the input @RalfJung . I tested the various types of overflow using the following code: fn main() {
let flag = true;
// let flag = false;
if flag {
let _shl = 1 << 32;
let _shl_prom = &(1 << 32);
let _shr = 1 >> 32;
let _shr_prom = &(1 >> 32);
let _sum = 3u8 + 255u8;
let _sum_prom = &(3u8 + 255u8);
let _dif = 0u8 - 1u8;
let _dif_prom = &(0u8 - 1u8);
let _ele = [1, 2, 3][4];
let _ele_prom = &([1, 2, 3][4]);
}
} When When
As you can see, for shl and shr no diagnostics are emitted for promoteds but they are emitted for normal consts. With all other types of overflow the same diagnotics are emitted both for promoteds and consts. So clearly there's an inconsistency between promoteds and consts diagnostics only for shl and shr overflows. The above observations are for Metadata:
Given the above, and @RalfJung's comments that the diagnostics between normal consts and promoteds must be identical, we need to fix the issue for shl and shr. |
The dependency on the value of But for the shifts, I agree, we should be consistent there. |
Regression in nightly-2023-03-17 found 8 bors merge commits in the specified range |
Probably introduced by #108282 then. |
Yes, it is most likely the one. |
#108282 is indeed the PR that introduced this issue. But before we go into how it did that, let's understand the mechanics of the issue itself in more detail. As alluded to above, rust/compiler/rustc_mir_transform/src/const_prop_lint.rs Lines 45 to 48 in 33f6af8
In order for the lint to the be emitted, the non-promoted MIR being checked must contain a potentially overflowing op such as Now, before The end result of this is, if your original MIR contained non-checked ops This is the root cause of the issue. What #108282 did is that it changed |
So how can we fix this? The simpler option is to stop rust/compiler/rustc_const_eval/src/transform/promote_consts.rs Lines 737 to 739 in 3166bbe
But I feel that would be a hack. Why should the promotion code be coupled to the requirements of some lint? In fact for efficiency we would not want to leave any redundant ops behind when we have promoted them. The right solution therefore is to run rust/compiler/rustc_mir_transform/src/const_prop_lint.rs Lines 45 to 48 in 33f6af8
However, if you do that, lints start firing even for dead code (i.e. even when flag is false in the above test code) which is something we don't want.
So I'll dig a bit further and see if we can enable |
With #108282 what used to be The promoted will never emit an error when executed, so it'd not make a lot of sense to make it emit lints either. |
That said, we do have another bug: if we run your test in release mode, then the warning disappears for all the promoteds (except the array indexing). If we want to fix that then we have to change this more fundamentally and have the lint check for the arithmetic operation rather than the assertion. And then indeed we have to check in promoteds as well (for regular arithmetic and for shifts). |
I was thinking I could make the promoted selectively emit the lint only when it is not under dead code. However, a given promoted could be used at multiple places I presume, some of them dead and some not. So indeed taking that path is a non starter.
Earlier I had thought of predicating the lint on the assert, but dismissed it because an assert could exist in the code for reasons other than overflow as well. I'll see if we can make it work by adding some extra checks or doing some narrow eval in that area.
Yeah, I had an inkling sooner or later we'll need something like this. That's why I too felt a more fundamental change is needed wherein we start linting the promoteds too. That said, I'll first tackle the immediate issue of shifts in debug builds by using the assert and return to the larger problem later. |
This statement is not true. I was assuming that If we use the exact same expression (like So yeah, we can then be selective about which one we lint and which one we don't depending on whether it originates from under dead code or not. (Please forgive if this doesn't make sense. I'm still learning about this area.) |
No, the assert is very specific. You can assume it only exists for shift overflow. This is the MIR terminator you are looking for, and you can check the
I wouldn't worry about dead code. It's okay if the lint fires in dead code. |
Nice. That will work. Thanks for the suggestion.
Hmm... That conflicts with the mental model I'd formed so far from your comment that the following two snippets should emit the same diagnostics for the same value of if b {
let _val = 1 << 32;
}
if b {
let _val = &(1 << 32);
} I had assumed that when I'll go through the docs again to clarify my thinking. Thanks for all your help :) |
I honestly didn't even know that for non-promoteds, we skip the lint in dead code. But we don't even do that consistently, it depends on optimization levels and might change in the future as optimizations get more clever. TBH I would prefer if we'd emit the exact same lints in debug and release builds, and that probably can only be achieved by always linting everything in dead code. I don't know if this is a consensus position though. However, we certainly don't have a clear decision to not lint in dead code; the fact that that is how it works today is mostly incidental. So as far as I'm concerned, the proper behavior for your two snippets is to always lint for all values of |
@RalfJung I've created a draft PR #119339. But I'm not too pleased with it. Not only is it a bit of a hack (which we already knew), it has also started producing a whole lot of duplicate diagnostics. The user won't see them after deduplication, but it's still not pretty. I was wondering if we can do a proper fix instead. What if we move the lints earlier so that they run right after the MIR is built. Then the lints would see all ops before they are removed/optimized away. Hence they will not only start firing for all promoted consts but also for release builds. Thoughts? |
I tried this by moving the call to rust/compiler/rustc_mir_transform/src/lib.rs Line 535 in f4d794e
rust/compiler/rustc_mir_transform/src/lib.rs Line 311 in f4d794e
mir_const which is located earlier in the call graph. It solved the problem. The lint started firing for all promoted ops not just Shl & Shr and also for both debug and release builds.
However, when I tried to build the std library with this compiler I ran into cycles like this triggered by opaque types: error[E0391]: cycle detected when computing type of `iter::traits::iterator::iter_compare::compare::{opaque#0}`
--> library\core\src\iter\traits\iterator.rs:4145:10
|
4145 | ) -> impl FnMut(X) -> ControlFlow<ControlFlow<T, Ordering>> + 'a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires computing type of opaque `iter::traits::iterator::iter_compare::compare::{opaque#0}`...
--> library\core\src\iter\traits\iterator.rs:4145:10
|
4145 | ) -> impl FnMut(X) -> ControlFlow<ControlFlow<T, Ordering>> + 'a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires borrow-checking `iter::traits::iterator::iter_compare::compare`...
--> library\core\src\iter\traits\iterator.rs:4142:5
|
4142 | / fn compare<'a, B, X, T>(
4143 | | b: &'a mut B,
4144 | | mut f: impl FnMut(X, B::Item) -> ControlFlow<T> + 'a,
4145 | | ) -> impl FnMut(X) -> ControlFlow<ControlFlow<T, Ordering>> + 'a
4146 | | where
4147 | | B: Iterator,
| |____________________^
note: ...which requires promoting constants in MIR for `iter::traits::iterator::iter_compare::compare`...
--> library\core\src\iter\traits\iterator.rs:4142:5
|
4142 | / fn compare<'a, B, X, T>(
4143 | | b: &'a mut B,
4144 | | mut f: impl FnMut(X, B::Item) -> ControlFlow<T> + 'a,
4145 | | ) -> impl FnMut(X) -> ControlFlow<ControlFlow<T, Ordering>> + 'a
4146 | | where
4147 | | B: Iterator,
| |____________________^
= note: ...which requires computing layout of `iter::traits::iterator::iter_compare::compare::{opaque#0}`...
= note: ...which requires normalizing `iter::traits::iterator::iter_compare::compare::{opaque#0}`...
= note: ...which again requires computing type of `iter::traits::iterator::iter_compare::compare::{opaque#0}`, completing the cycle
= note: cycle used when checking effective visibilities
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information Probably it is because |
…eds, r=<try> Make `ConstPropLint` lint run on promoteds Fixes rust-lang#117949 wherein the lint didn't fire for the following promoteds: - SHL or SHR operators in a non-optimized build - any arithmetic operator in an optimized build What I have done here is simply enabled `ConstPropLint` to run on promoted bodies by removing the relevant `if` check. After this change _all_ promoted arithmetic operators will lint _in both non-optimized and optimized builds_. On the flip side programs containing the above mentioned overflowing promoteds that were accepted earlier will now be rejected. Hope that is okay from a backward compatibility standpoint. I have added tests covering all overflowing promoted & non-promoted ops for both compile-time and runtime operations and for optimized as well as non-optimized builds. I had to amend some existing tests to make them pass and had to delete a couple that were set to pass despite overflows. This PR increases the number of duplicate diagnostics emitted (because the same operator might get linted in both the promoted MIR and the main MIR). I hope that is an acceptable trade-off given that we now lint overflows much more comprehensively than earlier.
…eds, r=oli-obk Make `ConstPropLint` lint run on promoteds Fixes rust-lang#117949 wherein the lint didn't fire for the following promoteds: - SHL or SHR operators in a non-optimized build - any arithmetic operator in an optimized build What I have done here is simply enabled `ConstPropLint` to run on promoted bodies by removing the relevant `if` check. After this change _all_ promoted arithmetic operators will lint _in both non-optimized and optimized builds_. On the flip side programs containing the above mentioned overflowing promoteds that were accepted earlier will now be rejected. Hope that is okay from a backward compatibility standpoint. I have added tests covering all overflowing promoted & non-promoted ops for both compile-time and runtime operations and for optimized as well as non-optimized builds. I had to amend some existing tests to make them pass and had to delete a couple that were set to pass despite overflows. This PR increases the number of duplicate diagnostics emitted (because the same operator might get linted in both the promoted MIR and the main MIR). I hope that is an acceptable trade-off given that we now lint overflows much more comprehensively than earlier.
I tried this code:
I expected to see this happen:
Same output as
which would be
Instead, this happened:
The code compiles with no error, the lint is not produced.
When running it, it overflows: panics at debug mode, and gives the wrong output in release.
Meta
Tested with the current stable, nightly and beta from the playground.
I have tried finding whether this has already been reported, it seems it hasn't
The text was updated successfully, but these errors were encountered: