Skip to content

overlong bit shifts cause undefined behaviour #10183

Closed

Description

@thestinger
1 >> 24353125315

Activity

  1. nikomatsakis commented on Oct 31, 2013

    @nikomatsakis
    Contributor

    At some point we discussed this in a meeting and decided it was acceptable.

  2. thestinger commented on Oct 31, 2013

    @thestinger
    ContributorAuthor

    @nikomatsakis: it's not just an unspecified result as I previously thought though, it breaks memory safety

  3. nikomatsakis commented on Oct 31, 2013

    @nikomatsakis
    Contributor

    Do you have a concrete example where this leads to breaking memory safety? I can imagine how it might theoretically happen (that is, the behavior of the program as a whole is technically undefined), but I'm not sure that it actually does? I guess I can imagine some compiler saying that "since i1 >> i2 implies i2 < 32, then I'll do some wacky optimization to some expression involving i2..."

  4. thestinger commented on Oct 31, 2013

    @thestinger
    ContributorAuthor

    @nikomatsakis: LLVM won't reserve a register for the return value, and will just read arbitrary registers on a read of the value, so for example a bounds check could fail to work

  5. nikomatsakis commented on Oct 31, 2013

    @nikomatsakis
    Contributor

    @thestinger return value of what function? read of what value? Can you spell it out more?

  6. thestinger commented on Oct 31, 2013

    @thestinger
    ContributorAuthor

    For example, a pattern like this:

    let mut xs = [1, 2, 3];
    let index = 1 >> 128; // this is `undef`
    xs[index] = 100;

    The indexing operation will essentially expand to this:

    if index < 3 {
        *unsafe_index(xs, index) = 100;
    } else {
        fail_bounds_check(...);
    }
    

    The index value as read for the comparison may be different than the value passed to unsafe_index.

    http://llvm.org/docs/LangRef.html#undefined-values

    In practice, it likely won't be in most cases, but in a more complex example where there was register pressure it would be.

  7. thestinger commented on Oct 31, 2013

    @thestinger
    ContributorAuthor

    The key part:

    an ‘undef‘ “variable” can arbitrarily change its value over its “live range”. This is true because the variable doesn’t actually have a live range. Instead, the value is logically read from arbitrary registers that happen to be around when needed, so the value is not necessarily consistent over time.

  8. brson commented on Oct 31, 2013

    @brson
    Contributor

    Nominating.

  9. nikomatsakis commented on Oct 31, 2013

    @nikomatsakis
    Contributor

    I see. Fascinating! Is there a way to "capture" an undefined value and make it constant? For example, storing it to a stack slot and reading it back? (Presumably this load/store could be optimized away, so prob not). If so, we could capture the output of shift operations in this way.

  10. pnkfelix commented on Nov 7, 2013

    @pnkfelix
    Contributor

    Accepted for P-backcompat lang. There are a variety of ways to deal with this. We need to choose one. Will discuss further (e.g. in tues mtg).

  11. pcwalton commented on Nov 22, 2013

    @pcwalton
    Contributor

    I don't think this is backwards incompatible at a language level. It will not cause code that was working OK to stop working. Nominating.

  12. pnkfelix commented on Dec 19, 2013

    @pnkfelix
    Contributor

    changing priority to P-high. not assigning to 1.0 milestone.

  13. added a commit that references this issue on Jul 3, 2014
  14. pnkfelix commented on Oct 1, 2014

    @pnkfelix
    Contributor

    @pcwalton Is it not true that the logic of your last comment generalize to cover e.g. "integer overflow is undefined behavior.", in the sense that one could argue "code that is not exercising the conditions that yield undefined behavior will continue to work fine" in that scenario too?

    I had thought that we considered it an important property that we actually tie down the semantics for operations in order to not head off into the undefined behavior weeds.

    (Sorry for revisiting an old topic; it came up on IRC.)

  15. pnkfelix commented on Oct 1, 2014

    @pnkfelix
    Contributor
  16. 13 remaining items

  17. thestinger commented on Nov 25, 2014

    @thestinger
    ContributorAuthor

    It's not fixed for shifts by constants either. The optional warning doesn't remove the responsibility of the compiler to prevent memory unsafety.

  18. ruuda commented on Feb 16, 2015

    @ruuda
    Contributor

    Currently, the following code panics in debug mode but runs fine in release mode:

    fn main() {
        let sh = 32;
        assert_eq!(0, 1_i32 << sh);
    }

    (Or try it in the playpen.)

    I am not sure whether this is by design or whether it is a bug, but either way, the documentation of shl nor the documentation of i32 mentions this behaviour.

  19. steveklabnik commented on Feb 16, 2015

    @steveklabnik
    Contributor

    The overflow RFC addresses this, and I have a bug open to reconcile all RFCs with the reference, so this will be taken care of as part of that.

  20. thestinger commented on Feb 17, 2015

    @thestinger
    ContributorAuthor

    This is a memory safety hole in the implementation. It's an issue regardless of how the language is intended to work per RFCs...

  21. srh commented on Feb 17, 2015

    @srh

    @steveklabnik How does the overflow RFC address this?

  22. steveklabnik commented on Feb 17, 2015

    @steveklabnik
    Contributor

    Sorry, by 'address' I mean a decision was made, and I'll be documenting the result. Not that it's any different than today.

  23. thestinger commented on Feb 17, 2015

    @thestinger
    ContributorAuthor

    This bug isn't asking for a change in semantics, it's filed to cover a known memory safety hole in the language. The fact that a clear solution has been specified doesn't mean the bug is fixed... pretending memory safety holes don't exist in the implementation doesn't make them go away.

  24. Gankra commented on Feb 17, 2015

    @Gankra
    Contributor

    Agreed this issue should not be closed until UB in safe Rust does not occur.

  25. reopened this on Feb 17, 2015
  26. ben0x539 commented on Feb 17, 2015

    @ben0x539
    Contributor

    I don't think the RFC addresses this issue at all. Under "unresolved questions", it lists "shifts by an excessive number of bits". It just proposes debug assertions and doesn't prevent undefined behavior in release builds.

  27. pnkfelix commented on Mar 20, 2015

    @pnkfelix
    Contributor

    Here is a variant of @ruud-v-a 's example that assert fails (in different places) in both -O0 and -O2 modes. (It was adapted from #23551.)

    #![allow(exceeding_bitshifts)]
    
    fn id<T>(x: T) -> T { x }
    
    fn main() {
        let sh = 32;
        assert_eq!(0, 1_i32 << 32);     // note that this line "is the same" as the two below
        assert_eq!(0, 1_i32 << sh);     // fails here with -O0
        assert_eq!(0, 1_i32 << id(sh)); // fails here with -O2
    }

    playpen

  28. pnkfelix commented on Mar 20, 2015

    @pnkfelix
    Contributor

    @ben0x539 I believe the intent of the RFC is to ensure that the fallback behaviors, when reasonable, do not expose undefined behavior from LLVM.

    In these cases, it seems like the fallbacks should simply mask-out the higher order bits as appropriate for each type.

  29. added a commit that references this issue on Mar 20, 2015
    19fef72
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions