Skip to content
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

Lint overflowing integer casts in const prop #67676

Merged
merged 5 commits into from
Jan 2, 2020

Conversation

wesleywiser
Copy link
Member

This extends the invalid cases we catch in const prop to include
overflowing integer casts using the same machinery as the overflowing
binary and unary operation logic.

r? @oli-obk

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 28, 2019
src/librustc_mir/transform/const_prop.rs Outdated Show resolved Hide resolved
src/librustc_mir/transform/const_prop.rs Outdated Show resolved Hide resolved
src/test/mir-opt/const_prop/cast.rs Show resolved Hide resolved
src/librustc_mir/transform/const_prop.rs Outdated Show resolved Hide resolved
@wesleywiser
Copy link
Member Author

Force pushed with new error message

@rust-highfive

This comment has been minimized.

@wesleywiser
Copy link
Member Author

Blessed tests

@wesleywiser wesleywiser force-pushed the lint_overflowing_int_casts branch 2 times, most recently from 9c5b7e2 to df79825 Compare December 28, 2019 22:06
@oli-obk
Copy link
Contributor

oli-obk commented Dec 29, 2019

r=me with a comment about the weird zst situation added

@wesleywiser
Copy link
Member Author

@bors r=oli-bok

@bors
Copy link
Contributor

bors commented Dec 29, 2019

📌 Commit d2eddfe7d139051df4ad569224b5d97dee395698 has been approved by oli-bok

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 29, 2019
This extends the invalid cases we catch in const prop to include
overflowing integer casts using the same machinery as the overflowing
binary and unary operation logic.
@wesleywiser
Copy link
Member Author

Rebased

@bors r=oli-obk

@bors
Copy link
Contributor

bors commented Dec 31, 2019

📌 Commit 001cea4 has been approved by oli-obk

Centril added a commit to Centril/rust that referenced this pull request Jan 1, 2020
…sts, r=oli-obk

Lint overflowing integer casts in const prop

This extends the invalid cases we catch in const prop to include
overflowing integer casts using the same machinery as the overflowing
binary and unary operation logic.

r? @oli-obk
@Dylan-DPC-zz
Copy link

Failed in rollup

@bors r-

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jan 1, 2020
@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jan 1, 2020
@bors
Copy link
Contributor

bors commented Jan 1, 2020

⌛ Testing commit e8c1c4c with merge 0ec3706...

bors added a commit that referenced this pull request Jan 1, 2020
Lint overflowing integer casts in const prop

This extends the invalid cases we catch in const prop to include
overflowing integer casts using the same machinery as the overflowing
binary and unary operation logic.

r? @oli-obk
@bors
Copy link
Contributor

bors commented Jan 2, 2020

☀️ Test successful - checks-azure
Approved by: oli-obk
Pushing 0ec3706 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jan 2, 2020
@bors bors merged commit e8c1c4c into rust-lang:master Jan 2, 2020
@rust-highfive
Copy link
Collaborator

📣 Toolstate changed by #67676!

Tested on commit 0ec3706.
Direct link to PR: #67676

💔 miri on windows: test-pass → test-fail (cc @oli-obk @eddyb @RalfJung, @rust-lang/infra).
💔 miri on linux: test-pass → test-fail (cc @oli-obk @eddyb @RalfJung, @rust-lang/infra).

rust-highfive added a commit to rust-lang-nursery/rust-toolstate that referenced this pull request Jan 2, 2020
Tested on commit rust-lang/rust@0ec3706.
Direct link to PR: <rust-lang/rust#67676>

💔 miri on windows: test-pass → test-fail (cc @oli-obk @eddyb @RalfJung, @rust-lang/infra).
💔 miri on linux: test-pass → test-fail (cc @oli-obk @eddyb @RalfJung, @rust-lang/infra).
@RalfJung
Copy link
Member

RalfJung commented Jan 2, 2020

To me it looks like the lint is handling enum variants incorrectly:

enum Signed {
    Bar = -42,
    Baz,
    Quux = 100,
}
fn signed() -> [i8; 3] {
    let baz = Signed::Baz; // let-expansion changes the MIR significantly
    [Signed::Bar as i8, baz as i8, Signed::Quux as i8]
}

This errors at Signed::Bar as i8, but why would it?

Cc https://github.com/rust-lang/miri/pull/1138/files#r362443894

@wesleywiser
Copy link
Member Author

@RalfJung Yeah, you're totally correct. I'm surprised we don't have any tests that exercise this code in rustc for enums with unsigned values. I'm investigating a fix or potentially a revert for this PR since I'm on vacation next week.

@SimonSapin
Copy link
Contributor

The premise of this PR seems flawed. Truncating is in some cases the legitimate desired behavior, and as is the only way available (so far) to convert to a smaller integer type.

Compare with overflow checking in +, where wrapping_add has existed since before Rust 1.0, possibly for as long as this overflow checking.

@wesleywiser
Copy link
Member Author

@SimonSapin The lint does not fire on every instance of as. The intent is to only lint situations where the as cast results in a different value, not just a smaller integer type.

To draw a comparison with the wrapping_add function, if the intent is to truly discard the higher-order bits, masking them off first will not cause the lint to fire:

    let x = 1026u16 as u8; // lint fires here
    let y = (1026u16 & 0x00FFu16) as u8; // lint does not fire here

@SimonSapin
Copy link
Contributor

(1026u16 & 0x00FFu16) as u8 is a work-around designed specifically for this lint. 1026u16 as u8 has been the idiomatic way to truncate for many years. Making it an error an telling everyone to just use the work-around doesn’t seem a good approach to me, given Rust’s stability goal.

It is unfortunate that there is no way to tell intentional truncation apart from accidental truncation, and https://internals.rust-lang.org/t/pre-rfc-add-explicitly-named-numeric-conversion-apis/11395 tries to fix that, but it is a fact of today’s Rust.

@oli-obk
Copy link
Contributor

oli-obk commented Jan 2, 2020

It's not an error and not a stability problem because it's just a lint that you can turn off (even locally). I suggest you disable the lint for your code instead of using a workaround.

@SimonSapin
Copy link
Contributor

https://perf.rust-lang.org/status.html currently shows:

Benchmarks for last commit:

SHA: 0a58f5864659ddfe1d95c122abaa75c88220aed0, date: 2020-01-02T10:20:09+00:00

[…]

error: truncating cast: the value 4294967292 requires 32 bits but the target type is only 16 bits
     --> /tmp/.tmpmCOmcQ/target/debug/build/style-f46d78432390fdd0/out/gecko_properties.rs:10050:21
      |
10050 |                     structs::NS_FONT_STRETCH_ULTRA_CONDENSED as i16,
      |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[deny(const_err)]` on by default

(I don’t know if there is a permanent URL for this log.)

https://raw.githubusercontent.com/rust-lang-nursery/rustc-perf/06e8bd4fb5648f6c8c03265150b1d50c529415e1/collector/benchmarks/style-servo/components/style/gecko/generated/structs.rs defines:

    pub const NS_FONT_STRETCH_ULTRA_CONDENSED: ::std::os::raw::c_int = -4;

However I didn’t manage to reproduce the error with this reduction:

pub const A: ::std::os::raw::c_int = -4;
pub const B: i16 = A as i16;

fn main() {
   dbg!(B);
}

and

rustup-toolchain-install-master 0ec370670220b712b042ee09aab067ec7e5878d5
rustc +0ec370670220b712b042ee09aab067ec7e5878d5 a.rs

It's not an error and not a stability problem because it's just a lint that you can turn off (even locally). I suggest you disable the lint for your code instead of using a workaround.

The above shows there’s at least one case in the wild of previously-valid code where this lint triggers. This suggests that Crater is needed to determine how much of a stability issue it is to make this lint deny by default.

@lqd
Copy link
Member

lqd commented Jan 2, 2020

This may be a case similar to the bug Ralf pointed out earlier

@oli-obk
Copy link
Contributor

oli-obk commented Jan 2, 2020

Yes, the lint has a signedness bug

@SimonSapin
Copy link
Contributor

Two separate things:

  1. The lint as implemented is buggy. Unless a fix can be ready very soon, please consider reverting for now.

    fn main() {
        let _ = -1_i16 as i8;
    }
    error: truncating cast: the value 65535 requires 16 bits but the target type is only 8 bits
     --> a.rs:2:13
      |
    2 |     let _ = -1_i16 as i8;
      |             ^^^^^^^^^^^^
      |
      = note: `#[deny(const_err)]` on by default
  2. Even with that bug fixed, I believe that making it deny by default is not acceptable under our stability policy since it triggers for a valid idiomatic pattern.

    Even making it warn by default feels dubious in my opinion. I’d prefer to see an alternative available for intentional truncation (better than manually masking), and an evaluation (perhaps with Crater?) of negative impact of churn in cases of intentional truncation v.s. positive impact in cases the lint catches real bugs.

@wesleywiser
Copy link
Member Author

  1. I'm going to revert the lint tonight.

  2. I'm not sure I see how 256u16 as u8 is any less "wrong" than, for example, 255u8 + 1u8. This seems like exactly the same category of issue to me. It would be helpful to have an example of code which is triggering this lint (modulo the bug with negative numbers) which you believe should be allowed.

@SimonSapin
Copy link
Contributor

SimonSapin commented Jan 2, 2020

Thank you for taking care of the revert.

Intentional cases are not gonna look like literally 256u16 as u8. Consider for example:

const BITS: u16 = 0x1234;
let upper = (BITS >> 8) as u8;
let lower = BITS as u8;

@oli-obk
Copy link
Contributor

oli-obk commented Jan 2, 2020

it triggers for a valid idiomatic pattern.

I disagree that value as u8 is an idiomatic way to truncate (and some ppl seem to think it should in fact panic: https://users.rust-lang.org/t/safe-casting-of-primitives/24240). Also in your example, you should be using to_be_bytes to get the components if you want the u8 components. Ok, maybe your real example truncates u32 to u16, but that discussion is not relevant here imo. The relevant point here is twofold. First:

I’d prefer to see an alternative available for intentional truncation (better than manually masking),

Something like Into and TryInto for explicitly casting losslessly? So rust-lang/rfcs#2484

negative impact of churn in cases of intentional truncation v.s. positive impact in cases the lint catches real bugs.

That seems nearly impossible to do. Just like it's super hard to really prove with data that using Into over as will catch accidental changes to types that would have made the as cast lossy.

Though since we are doing that in clippy, if we had a LossyInto, clippy could also take care of complaining about any kind of integral as cast instead of just the few it does complain about right now.

Second: So... while there are ways to move forward for general truncation casts, the situation we have here is that we statically know that you are truncating a value. We are allowed to add more lints for statically knowing that the user is doing something buggy as per RFC 1229. The example from that RFC is

let x = 5u32 << 42;

which actually used to be a hard error and we weakened it to the const_err lint for consistency.

That example from the RFC is in fact also a truncation causing the bitshift to be 5u32 << 10 because the higher bits are subtracted. I'm sure someone has a use case for using a constant instead of the 10 and the silent trucation working. They can allow the lint and keep doing that. The differences to your example are that

  1. There's no way to truncate an int other than as casting
  2. The bitshift is also checked with debug assertions at runtime in debug mode
    • we also emit the lint since 1.0 in release mode for unary negation overflow, binary math overflow and bit shift truncation
  3. The lint about it did not exist since 1.0
    • this point by itself in isolation is moot due to RFC 1227

So the big difference between this new trigger of the lint is that it's not triggering on behaviour that panics at runtime with debug assertions.

Which brings us back to the question "should as panic on truncation?". I don't have an answer to that, but since you can in fact mask and then cast without this causing worse assembly, I think we'd be able to do this.

For now, let's leave such lints to clippy until we have a way to truncate via LossyInto.

wesleywiser added a commit to wesleywiser/rust that referenced this pull request Jan 3, 2020
wesleywiser added a commit to wesleywiser/rust that referenced this pull request Jan 3, 2020
@SimonSapin
Copy link
Contributor

That seems nearly impossible to do. Just like it's super hard to really prove with data that using Into over as will catch accidental changes to types that would have made the as cast lossy.

Yes, and we’re not making lossless as a deny-by-default lint in rustc.

For now, let's leave such lints to clippy until we have a way to truncate via LossyInto.

This is pretty much my point, but not what this PR does.

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Jan 4, 2020
Revert `const_err` lint checking of casts

Reverts part of rust-lang#67676

r? @oli-obk

cc @SimonSapin
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants