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

[const-prop] Fix ICE due to underflow when calculating enum discriminant #66857

Closed
wants to merge 1 commit into from

Conversation

wesleywiser
Copy link
Member

Fixes #66787

r? @oli-obk
cc @RalfJung

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 29, 2019
.checked_sub(variants_start)
.expect("overflow computing relative variant idx");

let (variant_index_relative, op) = if variant_index.as_u32() >= variants_start {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use overflowing_sub here to remove some duplication. The add/sub decision can be made on the overflow bool returned by overflowing_sub

@oli-obk
Copy link
Contributor

oli-obk commented Nov 29, 2019

cc @RalfJung

@RalfJung
Copy link
Member

Cc @eddyb does that look right to you? I thought enum discriminant always use subtraction here.

// We need to use machine arithmetic when taking into account `niche_start`:
// discr_val = variant_index_relative + niche_start_val
let discr_layout = self.layout_of(discr_layout.value.to_int_ty(*self.tcx))?;
let niche_start_val = ImmTy::from_uint(niche_start, discr_layout);
let variant_index_relative_val =
ImmTy::from_uint(variant_index_relative, discr_layout);
let discr_val = self.binary_op(
mir::BinOp::Add,
op,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right to me... in the 2nd branch above, what happens (compared to earlier) is that variant_index_relative is negative, but here by changing the operator you are negating the second operand, niche_start_val. So the entire result is the negation of what it used to be. Don't you also have to swap the operands?

Either way, this needs a detailed comment why both branches above are correct. Also we should understand why LLVM codegen doesn't need a branch like this.

// compile-flags: --crate-type lib

// Regression test for ICE which occurred when const propagating an enum whose discriminant
// niche triggered an integer underflow conmupting a delta.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to also trigger this ICE in standalone Miri? I'd prefer to have such important parts of the Miri engine not just covered indirectly through an optimization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given #66857 (comment) I'm not sure that's possible, since this is necessarily an initialization of an uninhabited variant, and that's dead code.

Miri likely wouldn't have had this bug for so long if it was actually executing the code that is being constant-folded here.

@eddyb
Copy link
Member

eddyb commented Nov 29, 2019

I'm sorry, what is going on? How can any niche variant be before the first niche variant?
These are not custom discriminants, they are indices, this problem should be impossible.

Are you sure miri isn't missing a check for uninhabited variants?
EDIT: indeed that seems to be the case. In codegen we do this first:

if self.layout.for_variant(bx.cx(), variant_index).abi.is_uninhabited() {
return;
}

EDIT2: okay I was very shocked at first but I guess this would generally be dead code and only the const-prop optimization would attempt to execute it, so that's why miri didn't need to handle uninhabited variants like codegen does.

@@ -1082,17 +1082,21 @@ where
}
if variant_index != dataful_variant {
let variants_start = niche_variants.start().as_u32();
let variant_index_relative = variant_index.as_u32()
.checked_sub(variants_start)
.expect("overflow computing relative variant idx");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In codegen this isn't even checked, it's a plain -, because this is impossible for indices.

@@ -1082,17 +1082,21 @@ where
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, btw, this check seems pointless, it's impossible for variant_index to be out of range (without rustc being broken), so you could just ICE here if you wanted to (but there's not really any point, dest.layout.for_variant(variant_index) would panic trying to index with it, anyway).

@eddyb
Copy link
Member

eddyb commented Nov 29, 2019

@RalfJung It occurs to me that miri might not want to handle SetDiscriminant of uninhabited variants like codegen (where it's a noop). May I suggest replacing the entire ADT with undef bytes?
That way even if there's no tag/niche for the discriminant, the value is still invalidated.

@RalfJung
Copy link
Member

RalfJung commented Nov 29, 2019

It occurs to me that miri might not want to handle SetDiscriminant of uninhabited variants like codegen (where it's a noop)

Could you explain in a bit more detail what this problem here has to do with uninhabited variants?

May I suggest replacing the entire ADT with undef bytes?

So the semantics of SetDiscriminant would be to clear that storage, and set the discriminant? Is it the case that we always do SetDiscriminant before setting the fields? How would that fix the ICE here?

@eddyb
Copy link
Member

eddyb commented Nov 29, 2019

Could you explain in a bit more detail what this problem here has to do with uninhabited variants?

The bug here is that miri sees a variant_index outside of niche_variants (because it's an uninhabited variant), which is why the subtraction fails. But codegen never does the subtraction, because it bails out much sooner, and effectively does nothing in SetDiscriminant.

So the semantics of SetDiscriminant would be to clear that storage, and set the discriminant? Is it the case that we always do SetDiscriminant before setting the fields? How would that fix the ICE here?

Uhhh I was a bit unclear. I meant writing undef bytes specifically for uninhabited variants.
It's not the fix itself, but the fix is to not try to write a tag/niche value for uninhabited variants, and I was thinking you might find not find that ideal.

For the record, SetDiscriminant(place, variant_index) must happen after initializing all the of the variant fields (i.e. fields of Downcast(place, variant_index)).

cc @matthewjasper @spastorino perhaps borrowck could use such a rule for SetDiscriminant(place, variant_index) (i.e. require Downcast(place, variant_index) to be initialized, piece-wise, and mark place as initialized) to allow us to remove Rvalue::Aggregate (but this is really offtopic for this PR).

@oli-obk
Copy link
Contributor

oli-obk commented Nov 30, 2019

Since only const prop cares about this, maybe we can insert a machine hook that allows const prop to overwrite the destination with undef.

We only need this if the destination has gotten anything else written before the SetDiscriminant, since otherwise erroring with a UB error will cause no writes to the destination, leaving it undef. It seems generally safer to overwrite with undef (or we assert that the destination is undef?)

@eddyb
Copy link
Member

eddyb commented Nov 30, 2019

I was under the assumption this wouldn't be considered an UB error, hence suggesting doing something other than a noop (which is what codegen does).

But maybe it makes sense as an error, just not an UB InvalidDiscriminant one. What does Unreachable do? Attempting to SetDiscriminant an uninhabited variant should probably be similar.

@oli-obk
Copy link
Contributor

oli-obk commented Nov 30, 2019

We can emit the Unreachable UB error: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/mir/interpret/enum.UndefinedBehaviorInfo.html#variant.Unreachable

const prop already ignores it

@wesleywiser
Copy link
Member Author

wesleywiser commented Nov 30, 2019

Are you sure miri isn't missing a check for uninhabited variants?

I think @eddyb hit the nail on the head here. If I add a similar check as the one in codegen, that resolves the issue as well.

wesleywiser@aec7714

Does anyone object to doing that patch instead?

Edit: @eddyb you mentioned to me that you'd prefer if this used something named more meaningful than InvalidDiscriminant. InvalidDiscriminant seems fine to me, could you explain that a bit more?

@wesleywiser
Copy link
Member Author

Closing in favor of #66960

@wesleywiser wesleywiser closed this Dec 2, 2019
@eddyb
Copy link
Member

eddyb commented Dec 2, 2019

InvalidDiscriminant seems fine to me, could you explain that a bit more?

I explained on the new PR, but I think InvalidDiscriminant makes more sense for reading discriminants.

Centril added a commit to Centril/rust that referenced this pull request Dec 3, 2019
…obk,RalfJung

[const-prop] Fix ICE calculating enum discriminant

Fixes rust-lang#66787

Different approach than rust-lang#66857

r? @oli-obk
cc @RalfJung @eddyb
Centril added a commit to Centril/rust that referenced this pull request Dec 3, 2019
…obk,RalfJung

[const-prop] Fix ICE calculating enum discriminant

Fixes rust-lang#66787

Different approach than rust-lang#66857

r? @oli-obk
cc @RalfJung @eddyb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compiler panic while building crate with tokio, rocket, and snafu
5 participants