Skip to content

"!" is the only type that is unsafe to raw-ptr-deref into a "_" pattern #79735

Description

@RalfJung

This code compiles:

fn foo(ptr: *const bool) {
    let _ = *ptr;
}

The MIR is empty, i.e., no ptr deref happens.

But this does not:

fn foo(ptr: *const !) {
    let _ = *ptr;
}

The MIR is unreachable;, i.e., the ptr deref conceptually somehow did actually happen.

That is a very strange inconsistency -- the ! type is just yet another type, why does it behave differently here?

Curiously, this does not yield any unreachable:

fn bar(ptr: *const (i32, !)) { unsafe {
    let (x, _) = *ptr;
} }

Activity

  1. RalfJung commented on Dec 5, 2020

    @RalfJung
    MemberAuthor

    One possible explanation of this behavior is that "it is UB to create a place of an uninhabited type". That would be a rather surprising special case, I think, but I could get behind this since I am actually proposing to similarly treat uninhabites types specially for &T validity (see rust-lang/unsafe-code-guidelines#77).

    However, under that explanation, the following code has UB:

    enum Void {}
    
    fn foo(ptr: *const Void) {
        let _ = *ptr;
    }
    
    foo(&3 as *const _ as *const _);

    And yet this is all accepted as safe code. So I think something is very wrong here.

  2. camelid commented on Dec 7, 2020

    @camelid
    Member

    Should this be nominated?

  3. DutchGhost commented on Dec 7, 2020

    @DutchGhost

    Is this what is happening with #77694 ?

  4. RalfJung commented on Dec 7, 2020

    @RalfJung
    MemberAuthor

    @DutchGhost I don't think so, there is no _ pattern there.

  5. nikomatsakis commented on Dec 15, 2020

    @nikomatsakis
    Contributor

    We discussed this in our @rust-lang/lang meeting today. Clearly the current behavior is unsound. The question is what behavior we expect. There seem to be two related questions:

    • Should let _ = *foo be unsafe when foo is a raw pointer, independent of everything else?
    • Should let _ = *foo be UB if foo is not a valid pointer?
      • Right now it is inconsistent, with that ultimately depending on the type of the referent, seems not great
      • let _ = place is generally a no-op, but it does evaluate place for side-effects
      • so the question perhaps is whether evaluating *foo (where foo is a raw pointer) to a place has the side effect of asserting that foo is a valid pointer which could be loaded, even though we are not currently loading

    Does that analysis sound about right to you, @RalfJung? (It doesn't yet say the answers to those questions, especially the second one)

  6. RalfJung commented on Dec 16, 2020

    @RalfJung
    MemberAuthor

    Yes that sounds about right, modulo the vagueness of the term "valid pointer". ;)

    One way to look at this is by being explicit about place-to-value coercions (p2v). The regular let x = *ptr then becomes

    let x = p2v(*ptr);

    Now there are two axes we can adjust:

    • Does let _ = *ptr perform a place-to-value coercion or not? If yes, certainly the ptr needs to be as valid as in let x = *ptr as the same things are happening.
    • What exactly does the mere construction of the place *ptr, without being followed by a p2v, require in terms of validity? Does the ptr need to be dereferencable (so far I always assumed the answer is yes), does it need to point to data that is valid for the place's type (so far I always assumed the answer is no)? Does this depend on the type of ptr, i.e., do we have "raw places" whose construction is governed by different rules?

    One aspect to consider for the second question is expressions like &(*ptr).field and &raw (*ptr).field, where there is no implicit place-to-value coercion (assuming here that ptr is a value expression). Any validity requirement we impose on place construction will also be in force here. &raw (*ptr).field currently compiles to getelementptr inbounds, and if we want to stick to that, some amount of dereferencability needs to be required even when a "raw place" is being constructed. But there's also some demand for relaxing this to e.g. make offset_of! easier to implement -- there are proposals to instead use a currently-hypothetical getelementptr nw for &raw (*ptr).field that ensures no-overflow but does not say anything about allocation bounds. This would correspond to (raw) place construction only requiring that the place (viewed as a base address and a size) does not overflow the high end of memory, so this is another option that's on the table.

  7. RalfJung commented on Dec 28, 2020

    @RalfJung
    MemberAuthor
  8. nikomatsakis commented on Feb 10, 2021

    @nikomatsakis
    Contributor

    We discussed this in the @rust-lang/lang meeting on 2021-02-09 and we concluded that:

    • We want unsafe to be required here, but that is covered by Unsafe checking skips pointer dereferences in unused places #80059 specifically.
    • We don't want to settle whether let _ = *ptr is UB or not at this time, it doesn't seem urgent. In other words, it is presently considered UB, and we're ok with that, but we would also definitely consider changing that in the future.

    I'm going to remove nomination as there isn't anything urgent to solve at this time.

  9. RalfJung commented on Feb 13, 2021

    @RalfJung
    MemberAuthor

    We don't want to settle whether let _ = *ptr is UB or not at this time, it doesn't seem urgent. In other words, it is presently considered UB, and we're ok with that,

    FWIW, Miri cannot detect this UB since it is not represented in the MIR.

  10. nikomatsakis commented on Feb 15, 2021

    @nikomatsakis
    Contributor

    In truth, my opinion is that let _ = *ptr should not be UB, regardless of the type of ptr, but it should be unsafe if ptr is a raw pointer.

  11. syvb commented on Jun 21, 2021

    @syvb
    Contributor

    It seems that the THIR unsafety checker now disallows (while the MIR checker allows) dereferencing pointers to wildcards:

    fn foo(ptr: *const bool) {
        let _ = *ptr;
    }
  12. RalfJung commented on Oct 30, 2023

    @RalfJung
    MemberAuthor

    This code compiles:

    This does not compile any more. I think @cjgillot fixed it? So the issue can be closed if we have a test.

    In truth, my opinion is that let _ = *ptr should not be UB, regardless of the type of ptr, but it should be unsafe if ptr is a raw pointer.

    That is indeed the semantics we are going for. For bool and enum Void {}, this is already the case. For ! it is not but that's a bug: #117288.

  13. RalfJung commented on Oct 30, 2023

    @RalfJung
    MemberAuthor

    #102256 added a test so this can be closed.

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

    C-bugCategory: This is a bug.F-never_type`#![feature(never_type)]`T-langRelevant to the language teamfixed-by-thir-unsafeck`-Z thir-unsafeck` handles this correctly.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions