Skip to content

Miscompilation: Equal pointers comparing as unequal #107975

Open

Description

@JakobDegen

I tried this code:

pub fn foo() -> (usize, usize, bool) {
    let a: *const u8;
    let b: *const u8;
    {
        let v: [u8; 16] = [std::hint::black_box(4); 16];
        a = &(v[0]);
    }
    {
        let v: [u8; 16] = [std::hint::black_box(4); 16];
        b = &(v[0]);
    }
    (a as usize, b as usize, a == b)
}

fn main() {
    println!("{:?}", foo());
}

I expected to see this happen: Either the pointers (when cast to integers) are the same and the comparison is true, or they are not the same and the comparison is false.

Instead, this happened: It printed: (140728325198984, 140728325198984, false)

Upstream LLVM issue

Meta

Reproduced via rustc +nightly -Copt-level=3 test.rs && ./test.

rustc --version --verbose:

rustc 1.69.0-nightly (5b8f28453 2023-02-12)
binary: rustc
commit-hash: 5b8f284536d00ba649ca968584bedab4820d8527
commit-date: 2023-02-12
host: x86_64-unknown-linux-gnu
release: 1.69.0-nightly
LLVM version: 15.0.7

Also reproduces on master.

@rustbot label +I-unsound +T-compiler +A-llvm

Activity

  1. added
    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 13, 2023
  2. JakobDegen commented on Feb 13, 2023

    @JakobDegen
    ContributorAuthor

    Godbolt indicates that this is EarlyCSE in LLVM optimizing the pointer comparison to false despite the two stack allocations have non-overlapping lifetime ranges.

  3. zirconium-n commented on Feb 13, 2023

    @zirconium-n
    Contributor

    This seems related to pointer provenance.

  4. JakobDegen commented on Feb 13, 2023

    @JakobDegen
    ContributorAuthor

    The bug itself or the cause of the bug? I don't know what the incorrect reasoning that LLVM is using here is, so I can't comment on the cause of the issue. But both Rust and LLVM clearly define pointer comparison to be address based, which means that I don't think we need to think about provenance in order to conclude that this is a miscompilation

  5. DoubleHyphen commented on Feb 13, 2023

    @DoubleHyphen

    This seems related to pointer provenance.

    tl;dr even if the docs were changed to take provenance into account when comparing for equality, there would still be a miscompilation, because evaluating the same expression twice leads to different results!

    fn main() {
        let a: *const u8;
        let b: *const u8;
        {
            let v: [u8; 16] = [core::hint::black_box(0); 16];
            a = &(v[0]);
        }
        {
            let v: [u8; 16] = [core::hint::black_box(0); 16];
            b = &(v[0]);
        }
        println!("{a:?} == {b:?} evaluates to {}", a==b);
        println!("{a:?} == {b:?} evaluates to {}", a==b);
    }
    0x7ffd8828d818 == 0x7ffd8828d818 evaluates to false
    0x7ffd8828d818 == 0x7ffd8828d818 evaluates to true
    

    Playground link

    This was exactly the train of thought that led me to experiment, thereby uncovering the bug: “Can two pointers compare as equal, even when they belong to different allocations?” I experimented, figured out that they cannot, and tried to refactor my println!s, whereupon the program started behaving differently. (In the second linked program, uncommenting line 18 changes the output of line 19.)

    PS: In case it matters, my experiments were on the stable channel.

  6. RalfJung commented on Feb 13, 2023

    @RalfJung
    Member

    tl;dr even if the docs were changed to take provenance into account when comparing for equality, there would still be a miscompilation, because evaluating the same expression twice leads to different results!

    In theory they could define pointer comparison to be non-deterministic, and then this would be okay. For a comparison that involves provenance, that is probably the case.

    But to my knowledge that's not how LLVM intends their ptr comparison to work.

    That last example is really strange though, why would it optimize only one of them? Also I was unable to reproduce this without printing, which is equally strange...

  7. DoubleHyphen commented on Feb 13, 2023

    @DoubleHyphen

    Also I was unable to reproduce this without printing, which is equally strange...

    I thought that was queer, so I experimented further.

    What I found is as follows:

    • As long as a pointer is never printed using println!, the behaviour remains stable. a==b always evaluates to false, and even if a and b are cast to integers and compared, the comparison still returns false. In other words, the code (continued from above)
        let (a_, b_) = (a as u128, b as u128);
        dbg!(a_ == b_, a_ ^ b_);

    outputs

    a_ == b_ = false
    a_ ^ b_ = 0
    

    to stderr, which –to the best of my knowledge– is not exactly how integers are supposed to work.

    • As soon as either pointer gets printed using println!, a==b starts evaluating to true. Casting them both to u128 before printing them does not cause that; only eg println!("{a:?}") does.
  8. Imberflur commented on Feb 13, 2023

    @Imberflur

    I tried some alternatives to println!, it looks like black_box(format_args!("{:?}", a)) is sufficient for it to start evaluating to true:

    // Produces true on following println
    
    black_box(format_args!("{:?}", a));
    //black_box(format_args!("{:?}", b));
    //format!("{:?}", a);
    
    // Doesn't
    
    //format_args!("{:?}", black_box(a));
    //black_box(format_args!("{:?}", black_box(a)));
    //format!("{:?}", black_box(a));
  9. RalfJung commented on Feb 13, 2023

    @RalfJung
    Member

    The wild thing is that black_box(a) doesn't cause this... so format_args is an even blacker box than black_box, or so? black_box is supposed to be a wildcard, and for all LLVM knows it might be calling format_args...

    In fact looks like adding black_box actually enables the optimization?!?

  10. Imberflur commented on Feb 13, 2023

    @Imberflur

    Reduced to black_box(&a) (edit: here is a playground link)

  11. JakobDegen commented on Feb 13, 2023

    @JakobDegen
    ContributorAuthor

    The wild thing is that black_box(a) doesn't cause this... so format_args is an even blacker box than black_box, or so? black_box is supposed to be a wildcard, and for all LLVM knows it might be calling format_args...

    Yeah, this isn't all too surprising, the difference between black_box(a) and black_box(&a) is quite significant

  12. 112 remaining items

  13. added a commit that references this issue on Jul 17, 2024
  14. added 2 commits that reference this issue on Jul 18, 2024
  15. added a commit that references this issue on Jul 20, 2024
  16. added
    C-external-bugCategory: issue that is caused by bugs in software beyond our control
    and removed
    C-bugCategory: This is a bug.
    on Nov 14, 2024
  17. tmiasko commented on Mar 21, 2025

    @tmiasko
    Contributor

    Two additional upstream issues that break comparison involving stack variables:

    A Rust reproducer for the second issue below. It asserts that pointers to two distinct variables live at the same time are not equal. In contrast to the earlier examples, this one makes the comparison when both variables are live.

    use std::hint::black_box;
    use std::mem::MaybeUninit;
    pub fn main() {
      let mut a = MaybeUninit::<[u8; 100]>::uninit();
      let mut c = false;
      let mut i = 0;
      while i != 1024 {
          let mut b = MaybeUninit::<[u8; 100]>::uninit();
          c = a.as_mut_ptr() == black_box(b.as_mut_ptr());
          i += 1;
      }
      black_box(a.as_mut_ptr());
      assert!(!c);
    }
    $ rustc a.rs && ./a
    $ rustc a.rs -O && ./a
    
    thread 'main' panicked at a.rs:13:3:
    assertion failed: !c
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    Edit:

    This example also happens to be miscompiled by gcc.
    $ cat a.c 
    int * black_box(int *a) {
      return a;
    }
    $ cat b.c
    int * black_box(int *a);
    int main() {
      int a[100];
      int c = 0;
      for (int i = 0; i != 1024; ++i) {
        int b[100];
        c = a == black_box(b);
      }
      black_box(a);
      return c;
    }
    $ clang-21 a.c b.c      && ./a.out; echo $?
    0
    $ clang-21 a.c b.c -O3  && ./a.out; echo $?
    1
    $ gcc a.c b.c           && ./a.out; echo $?
    0
    $ gcc a.c b.c -O3       && ./a.out; echo $?
    1

    GCC bug report https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119472

  18. added a commit that references this issue on Apr 12, 2025
  19. ThomasHabets commented on May 15, 2025

    @ThomasHabets

    #141021 is about this code compiling, and producing two u64 of the same value that don't compare equal in --release mode.

    struct Unknown {}
    impl Unknown {
        fn id(&self) -> u64 {
            &self as *const _ as u64   // <-- the `&` is the bug that's allowed, but produces uncomparable u64s
        }
    }
    
    fn main() {
        let u = Unknown {};
        let (a, b) = (u.id(), u.id());
        assert_eq!(a, b, "difference is: {} {}", a-b, b-a);
    }
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

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-external-bugCategory: issue that is caused by bugs in software beyond our controlI-miscompileIssue: Correct Rust code lowers to incorrect machine codeI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-llvmWorking group: LLVM backend code generation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions