Skip to content

Borrow checker allows "place mention"-dereferencing an uninitialized reference #164705

Open
0 of 1 issue completed

Description

@RalfJung

@Amanieu found this fun example:

fn main() {
    let x: &'static String;
    let _ = *x;
}

The borrow checker accepts this code. Miri says it has UB. Who is right?^^

The UB arises because let _ = place compiles to a "place mention", and PlaceMention(*x) still tries to construct the place *x which is ill-defined as x is uninitialized. (If you remove the *, it's fine because the place x exists and can be computed, it just denotes memory that hasn't been initialized yet. But *x describes a place that has no meaning.)

Cc @Nadrieril @rust-lang/types

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Oct 21, 2026
  2. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Oct 21, 2026
  3. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Oct 21, 2026
  4. changed the title [-]Borrow checker allows "place mention"-dereferencing an uninitialized refeernce[/-] [+]Borrow checker allows "place mention"-dereferencing an uninitialized reference[/+] on Oct 21, 2026
  5. added
    T-langRelevant to the language team
    A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html
    T-opsemRelevant to the opsem team
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Oct 21, 2026
  6. theemathas commented on Oct 21, 2026

    @theemathas
    Contributor

    Another variant (UB in miri):

    enum Thing {
        Wut
    }
    
    fn main() {
        let x: &Thing;
        let Thing::Wut = *x else { panic!() };
    }
  7. added
    A-patternsRelating to patterns and pattern matching
    A-miriArea: The miri tool
    on Oct 21, 2026
  8. theemathas commented on Oct 21, 2026

    @theemathas
    Contributor

    This, however, is apparently not UB according to miri.

    enum Thing {
        Wut
    }
    
    fn main() {
        let x: &Thing;
        let &Thing::Wut = x else { panic!() };
    }
  9. theemathas commented on Oct 22, 2026

    @theemathas
    Contributor

    miri, why do you think this is ok???

    #[repr(u8)]
    enum Thing {
        Wut,
    }
    
    fn main() {
        let x: &mut &mut Thing;
        let Thing::Wut = x else { panic!() };
    }
  10. saethlin commented on Oct 22, 2026

    @saethlin
    Member

    @theemathas I think that is a different bug. All your examples bisect to this:

    Regression in nightly-2023-10-28
    
      commit[0] 2023-10-26: Auto merge of #116581 - Kobzol:bootstrap-cmd-run, r=onur-ozkan
      commit[1] 2023-10-26: Auto merge of #117249 - matthiaskrgr:rollup-h4og5rv, r=matthiaskrgr
      commit[2] 2023-10-27: Auto merge of #113183 - estebank:redundant-sized-errors, r=davidtwco
      commit[3] 2023-10-27: Auto merge of #116035 - lqd:mcp-510-target-specs, r=petrochenkov
      commit[4] 2023-10-27: Auto merge of #117260 - okaneco:ascii_branchless, r=thomcc
      commit[5] 2023-10-27: Auto merge of #116205 - WaffleLapkin:stabilize_pointer_byte_offsets, r=dtolnay
      commit[6] 2023-10-27: Auto merge of #117272 - matthiaskrgr:rollup-upg122z, r=matthiaskrgr
      commit[7] 2023-10-27: Auto merge of #116858 - estebank:issue-22488, r=petrochenkov
      commit[8] 2023-10-27: Auto merge of #116751 - Nadrieril:lint-overlap-per-column, r=davidtwco
      commit[9] 2023-10-27: Auto merge of #117166 - oli-obk:mir_const_qualif_perf, r=petrochenkov
      commit[10] 2023-10-27: Auto merge of #103208 - cjgillot:match-fake-read, r=oli-obk,RalfJung
      commit[11] 2023-10-27: Auto merge of #117294 - matthiaskrgr:rollup-xylsec7, r=matthiaskrgr
    

    My guess is #103208, before that nightly your examples encounter a compile error:

    
    error[E0381]: used binding `x` isn't initialized
     --> <source>:8:22
      |
    7 |     let x: &mut &mut Thing;
      |         - binding declared here but left uninitialized
    8 |     let Thing::Wut = x else { panic!() };
      |                      ^ `x` used here but it isn't initialized
    

    The original example has always compiled.

  11. theemathas commented on Oct 22, 2026

    @theemathas
    Contributor

    This compiles since 1.0.0:

    enum Thing {
        Wut,
    }
    
    fn main() {
        let x: &mut &mut Thing;
        let &mut &mut Thing::Wut = x;
    }

    Current miri thinks this is OK.

  12. RalfJung commented on Oct 22, 2026

    @RalfJung
    MemberAuthor

    @Nadrieril I think we have a bug somewhere about such & patterns not being represented in MIR, don't we?

  13. maxdexh commented on Oct 22, 2026

    @maxdexh
    Member

    This also works for Boxes that have been dropped:

    let x = Box::new(1);
    drop(x);
    _ = *x; // miri sad :c

    I suspect we will find this in more existing code than the version that does it on an uninit local.

    Do we have any other types with builtin dereference logic? Other than references, raw pointers, and Box?

  14. Nadrieril commented on Oct 22, 2026

    @Nadrieril
    Member

    @Nadrieril I think we have a bug somewhere about such & patterns not being represented in MIR, don't we?

    Hmmm, unsure if that bit is a bug: the place being tested would have derefs in it so nothing is missing, it's just that the enum has a single variant so we don't emit any read/read_discriminant of that place. Would we want to make sure that "we could read the discriminant" maybe, by adding a PlaceMention of the place being not-tested?

    The OP however is very much a borrow-checker bug imo, since *x reads a pointer fromn *x and there is none. Not unrelated to rust-lang/reference#2121.

  15. maxdexh commented on Oct 22, 2026

    @maxdexh
    Member

    I found an unsound miscompile based on this: #164738

    I think we should just fix this by making all of these examples fail to compile outright.

  16. RalfJung commented on Oct 22, 2026

    @RalfJung
    MemberAuthor

    it's just that the enum has a single variant so we don't emit any read/read_discriminant of that place.

    I do feel like we had an issue for that, since IMO we totally should emit such a read -- in particular for repr(C) enums where there can be actual data that has to be valid even for single-variant enums...

    I think it was this issue: #147722

  17. BoxyUwU commented on Oct 27, 2026

    @BoxyUwU
    Member

    With my types hat on I don't really have much of a mental model about the validity requirements of places. I would be more than happy for opsem to just figure that out and then someone can implement whatever makes sense.

    My mental model here was that wildcard patterns just let you write whatever you want for the place expr being matched on. Given that we support let _ = *null, *uninit doesn't seem particularly different to me and would find it kind of subtle and surprising if there was a difference (though I think it would be fine language design wise to say there is a difference). let &Foo = *uninit working is surprising to me though 😅

  18. Nadrieril commented on Oct 27, 2026

    @Nadrieril
    Member

    Opsemwise places are memory locations, so place evaluation should produce something that has a known address. Then let _ just means "do nothing with that place". That's the criterion I'd want: borrow-checking should ensure that all places can be evaluated enough to know their address. This concretely means that:

    1. If the place is projections on a local, the local must be allocated (so with the move elimination work either let _ = uninit; causes the local to be allocated, or let _ = uninit; is statically rejected, I don't care);
    2. If the place is projections on a pointer dereference, that pointer value must be readable, so the place that contains it must be initialized.

    This allows let _ = *null; (the null pointer has a known address) and rejects let _ = *uninit;.

  19. BoxyUwU commented on Oct 27, 2026

    @BoxyUwU
    Member

    Seems reasonable

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-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlA-borrow-checkerArea: The borrow checkerA-miriArea: The miri toolA-patternsRelating to patterns and pattern matchingC-bugCategory: This is a bug.I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-langRelevant to the language teamT-opsemRelevant to the opsem teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions