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

incorrect UB when when a ! place is constructed (but not loaded!) #117288

Closed
RalfJung opened this issue Oct 27, 2023 · 44 comments · Fixed by #129392
Closed

incorrect UB when when a ! place is constructed (but not loaded!) #117288

RalfJung opened this issue Oct 27, 2023 · 44 comments · Fixed by #129392
Assignees
Labels
F-never_type `#![feature(never_type)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-high High priority requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-opsem Relevant to the opsem team

Comments

@RalfJung
Copy link
Member

RalfJung commented Oct 27, 2023

This code shouldn't have UB but Miri reports UB and rustc generates a SIGILL:

#![feature(never_type)]
fn main() {
    unsafe {
        let x = 3u8;
        let x: *const ! = &x as *const u8 as *const _;
        let _ = *x;
    }
}

Thanks to @Nadrieril for finding this.

Zulip discussion

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 27, 2023
@RalfJung RalfJung changed the title Miri reports UB when when a ! place is constructed (but not loaded!) UB when when a ! place is constructed (but not loaded!) Oct 27, 2023
@RalfJung RalfJung added the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Oct 27, 2023
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Oct 27, 2023
@RalfJung
Copy link
Member Author

Labeling as "unsound" since under the rules that @rust-lang/opsem decided for *, and how we've been treating _ in general, this shouldn't SIGILL.

@Nadrieril
Copy link
Member

Note that this triggers for ! but not enum Void{} so it's likely a type-based special case

@RalfJung
Copy link
Member Author

RalfJung commented Oct 27, 2023

This is already wrong in the initially generated MIR:

        _6 = &_2;
        _5 = &raw const (*_6);
        _4 = move _5 as *const ! (PtrToPtr);
        AscribeUserType(_4, o, UserTypeProjection { base: UserType(1), projs: [] });
        _3 = _4;
        StorageDead(_5);
        FakeRead(ForLet(None), _3);
        AscribeUserType(_3, o, UserTypeProjection { base: UserType(2), projs: [] });
        StorageDead(_6);
        StorageDead(_4);
        StorageLive(_7);
        StorageLive(_8);
        _8 = (*_3); // an actual load from the place!

If we use bool instead we get just a PlaceMention, as we should

        _5 = &_1;
        _4 = &raw const (*_5);
        _3 = move _4 as *const bool (PtrToPtr);
        AscribeUserType(_3, o, UserTypeProjection { base: UserType(1), projs: [] });
        _2 = _3;
        StorageDead(_4);
        FakeRead(ForLet(None), _2);
        AscribeUserType(_2, o, UserTypeProjection { base: UserType(2), projs: [] });
        StorageDead(_5);
        StorageDead(_3);
        PlaceMention((*_2)); // no load

@cjgillot any idea why we don't just get a PlaceMention for !?

@bjorn3
Copy link
Member

bjorn3 commented Oct 27, 2023

-Zunpretty=thir-tree shows a NeverToAny expression. Maybe this has something to do with it?

@saethlin saethlin added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-opsem Relevant to the opsem team and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Oct 27, 2023
@Nadrieril
Copy link
Member

Oh, then this looks like fallback behavior: if I annotate let _: ! = *x; I only get a "entering unreachable code" error

@RalfJung
Copy link
Member Author

Yeah that's also wrong, but different.^^ MIR for that case:

        _6 = &_2;
        _5 = &raw const (*_6);
        _4 = move _5 as *const ! (PtrToPtr);
        AscribeUserType(_4, o, UserTypeProjection { base: UserType(1), projs: [] });
        _3 = _4;
        StorageDead(_5);
        FakeRead(ForLet(None), _3);
        AscribeUserType(_3, o, UserTypeProjection { base: UserType(2), projs: [] });
        StorageDead(_6);
        StorageDead(_4);
        PlaceMention((*_3));
        AscribeUserType((*_3), +, UserTypeProjection { base: UserType(4), projs: [] });
        StorageDead(_3);
        StorageDead(_2);
        unreachable;

So we get a PlaceMention but then we also get a unreachable...

@RalfJung
Copy link
Member Author

-Zunpretty=thir-tree shows a NeverToAny expression. Maybe this has something to do with it?

Yeah that's probably the right trail. MIR building might not even be at fault, the THIR is already wrong since it thinks there's a never value being constructed here. These THIR nodes seem to be constructed from Adjust::NeverToAny; no idea if there's any way to dump those.

Who are the right people to ping for THIR issues...?

@RalfJung RalfJung changed the title UB when when a ! place is constructed (but not loaded!) incorrect UB when when a ! place is constructed (but not loaded!) Oct 28, 2023
@Jules-Bertholet
Copy link
Contributor

@rustbot label F-never_type

@rustbot rustbot added the F-never_type `#![feature(never_type)]` label Oct 29, 2023
@asquared31415
Copy link
Contributor

asquared31415 commented Oct 29, 2023

Note that stable workarounds to access the never type, or cases where the type might be inferred still produce identical behavior, so this does not require the feature.

trait Extractor {
    type Output;
}

impl<T> Extractor for fn() -> T {
    type Output = T;
}

type RealNever = <fn() -> ! as Extractor>::Output;

fn main() {
    unsafe {
        let x = 3u8;
        let x: *const RealNever = &x as *const u8 as *const _;
        let _ = *x;
    }
}

@asquared31415
Copy link
Contributor

#79735 mentions some similar cases that ! is special in places, though the primary problem (that deref into a let _ was allowed without unsafe) has since been resolved.

@apiraino
Copy link
Contributor

WG-prioritization assigning priority (Zulip discussion).

@rustbot label -I-prioritize +P-high

@rustbot rustbot added P-high High priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 30, 2023
@matthewjasper
Copy link
Contributor

matthewjasper commented Oct 30, 2023

This compiles, so this would also need some changes to type checking rules.

#![feature(never_type)]
fn make_string() -> String {
    unsafe {
        let x = 3u8;
        let x: *const ! = &x as *const u8 as *const _;
        let _ = *x; // Makes the unsafe block diverge, same as writing return String::new(); or let _ = panic!(); would
    }
}

@RalfJung
Copy link
Member Author

Uff, yeah that should never have compiled...
Cc @rust-lang/lang

@RalfJung RalfJung added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Oct 30, 2023
@Noratrieb
Copy link
Member

The reason this compiles is probably

// Any expression that produces a value of type `!` must have diverged
if ty.is_never() {
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
}

HIR typeck thinks that an expression of type ! cannot possibly not diverge.. which is a reasonable assumption tbh. So I think we need to somehow special case that in HIR typeck... the let _ = *x semantics make sense at the MIR level but are pretty inconsistent for HIR

@RalfJung
Copy link
Member Author

RalfJung commented Nov 14, 2023

Can HIR tell apart places and values? A value expression of type ! obviously must diverge, but place expressions are quite different.

The comment is accurate, "Any expression that produces a value of type ! must have diverged". However, let _ = *x; does not produce a value of any type, it only produces a place.

@RalfJung RalfJung added the I-lang-nominated Nominated for discussion during a lang team meeting. label Nov 14, 2023
@traviscross
Copy link
Contributor

@rustbot labels -I-lang-nominated

We had an extensive discussion of this in the 2023-12-06 T-lang planning meeting last week.

The consensus1 was general agreement with @RalfJung:

...that places of type ! should not be subject to never-to-any coercions (the coercion should apply only to values, matching the comment that justifies the coercion in the first place).

Specifically, we felt that it was 1) OK to have a place of ! type but insta-UB to ever coerce that place to a value, and 2) OK to not do never-to-any coercions on places.

Anywhere the compiler can statically determine that insta-UB is happening it would be desirable (but not required) to reject the code, preferably with a deny-by-default lint, but if it's more straightforward to emit a hard error, that's OK too.

Since we're agreeing to not do never-to-any coercions on places, anywhere that such a coercion would be required would now be a hard error due to the resulting type mismatch.

To the degree that changes to the behavior here break uses of the "never type hack", that's probably OK.


In the meeting, we settled on the general principles above, but did not go through every example. Following the logic of what we agreed, here's how this author would annotate the examples:

#![feature(never_type)]
use core::ptr::addr_of;
#[cfg(False)]
fn deref_never<T>() {
    unsafe {
        let x = 3u8;
        let x: *const ! = &x as *const u8 as *const _;
        //
        // In all cases, we do not enforce validity invariants on
        // places, and we do not perform never-to-any coercion on
        // places.
        //
        // `addr_of!` does not covert a place to a value.
        let _val = addr_of!(*x); //~ OK.
        //
        // The `_` binding does not convert a place to a value.
        let _ = *x; //~ OK.
        let _: ! = *x; //~ OK.
        //
        // Parentheses do not convert a place to a value.
        let _ = (*x); //~ OK.
        //
        // Since these do not convert a place to a value and
        // never-to-any coercion is not done on places, these do not
        // type check and are hard errors.
        let _: () = *x; //~ ERROR type mismatch
        let _: String = *x; //~ ERROR type mismatch
        let _: T = *x; //~ ERROR type mismatch
        //
        // In all cases, the compiler is free, on a best-effort basis,
        // to reject code that it can prove is insta-UB.  Preferably
        // it would do this as a deny-by-default lint, but it's also
        // OK to emit a hard error if that is much more
        // straightforward to implement.
        //
        // If a place of `!` type is converted to a value, that is
        // always insta-UB.
        //
        // The block expression converts a place to a value.
        let _ = { *x }; //~ Insta-UB.
        //
        // The tuple constructor converts a place to a value.
        let _ = (*x,); //~ Insta-UB.
        let (_,) = (*x,); //~ Insta-UB.
        //
        // Binding to a variable converts a place to a value.  Because
        // of never-to-any coercion on values these type check.
        let _val = *x; //~ Insta-UB.
        let _val: () = *x; //~ Insta-UB.
        let _val: String = *x; //~ Insta-UB.
        let _val: T = *x; //~ Insta-UB.
        //
        // Even if the resulting type of the value is explicitly `!`,
        // it's still insta-UB to convert a place of `!` type to a
        // value, since a value of type `!` is always insta-UB in
        // Rust.
        let _val: ! = *x; //~ Insta-UB.
        let _: ! = { *x }; //~ Insta-UB.
        let (_,): (!,) = (*x,); //~ Insta-UB.
    }
}

(Thanks to @WaffleLapkin for joining the meeting and for helpful discussions on this topic and to @scottmcm for staying on to the bitter end of the call to hammer out the details.)

Footnotes

  1. Some members dropped off the call on the path to this consensus but asserted their trust in those that remained to work out the proper solution.

@rustbot rustbot removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Dec 12, 2023
@RalfJung
Copy link
Member Author

@traviscross thanks for the detailed summary!

So... looks like the next step would be to actually implement these semantics. Since this occurs on the HIR layer I'm somewhat out of my depth here. Who are the HIR experts that we can ping to ask for help here? :)

@LunarLambda
Copy link

LunarLambda commented Jun 27, 2024

Why did this get marked "requires-nightly"? It reproduces on 1.79.0.

Also, I found a particularly funny version where codegen places the ud2 at the end of the function, allowing fn main to continue past the addr_of, then explode:

https://godbolt.org/z/fhdfK6jT1

trait Type { type T; }
impl<T> Type for fn() -> T { type T = T; }

// Make `!` nameable on stable
type Never = <fn() -> ! as Type>::T;

pub fn main() {
     let x = &();
     let y = (x as *const ()).cast::<Never>();

    // Should be sound (doesn't produce a value of `!`, only a place, so does not diverge)
    let z = unsafe { std::ptr::addr_of!(*y) };

    println!("huh: {z:p}");
}

If you change let z = unsafe { std::ptr::addr_of(*y) }; to let _ = unsafe { *y }; the println! gets destroyed.
let _ = unsafe { str::ptr::addr_of!(*y) }; also keeps the print. So compilation differs between addr_of!(*y) and *y, even when assigning to _.

rust-lang/unsafe-code-guidelines#261 (comment) Seems to suggest that let _ = *x; and let _ = addr_of!(*x); should be semantically identical, but they currently compile to very different MIR.

@WaffleLapkin
Copy link
Member

I'd call <fn() -> ! as Type>::T a hack to purposefully avoid stability checks. While you can use this hack on stable I think that the "requires nightly" label is correct.

(in the same way as using RUSTC_BPOTSTRAP to avoid the checks does not mean that the issue does not require nightly)

@asquared31415
Copy link
Contributor

isn't an empty enum equivalent to ! for the purposes of this place discussion?

@LunarLambda
Copy link

No this bug is specific to !. Equivalent code works fine for empty enums

@RalfJung
Copy link
Member Author

This is now blocking #129248: making addr_of!(*ptr) a safe operation.

@compiler-errors
Copy link
Member

compiler-errors commented Aug 21, 2024

Well, I can put up a hack to unblock that specifically. As @Nadrieril pointed out, this is actually two different "bugs" in HIR typeck:

#![feature(never_type)]
fn main() {
    unsafe {
        let x = 3u8;
        let x: *const ! = &x as *const u8 as *const _;
        let _ /* (1.) */ = *x;
        /* (2.) */
    }
}

In the above code:

  1. At (1.), we actually do never type fallback to (). Coercing the ! to () does constitute a read, and therefore is UB.
  2. At (2.), HIR typeck considers the fact that *x evalauted to ! to mean that all following code diverges, and therefore counts the block's type as ! as well.

Only (2.) actually matters for #129248, and if we were to land that PR right now, it would be unsound:

#![feature(never_type)]

fn make_up_a_value<T>() -> T {
    let x: *const ! = 0 as _;
    &raw const *x;
    // Since `*x` is `!`, HIR typeck thinks it diverges and allows the block to coerce to any value.
}

I will probably just put up a hack for (2.), since it's basically the only expression1 where this matters. This doesn't fix the let _: ! = *x; case, but that should remain unsafe at least, and therefore just regular UB and not unsound.

Footnotes

  1. Other combinations of HIR, such as match scrutinees that have no reads should also ideally be fixed... but that's really hard to fix I fear.

@RalfJung
Copy link
Member Author

I would say it is unsound in the sense that the reference doesn't say that this is UB. Admittedly the reference isn't clear on the entire point about let _ only evaluating the place expression, but that's what we've been consistently communicating for a while now and it has informed other decisions as well (around match, for instance).

But fixing one of two bugs is still progress, so that'd still be a great first step. :)

bors added a commit to rust-lang-ci/rust that referenced this issue Aug 22, 2024
…verge-but-more, r=<try>

[EXPERIMENT] Do not consider match/let/raw-ref of deref that evalautes to ! to diverge

This is the more involved version of rust-lang#129371. It doesn't fully fix rust-lang#117288, since we still need to fix the fact that never type fallback can cause an unintended load via the `NeverToAny` coercion. But I did want to probe crater to see if anyone relies on this behavior currently, since that's almost certainly UB.

r? `@ghost`
bors added a commit to rust-lang-ci/rust that referenced this issue Sep 6, 2024
…verge-but-more, r=<try>

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR does two things:

### Don't consider `match`/`let`/`ref`/assignment-LHS of a place expression that evaluates to `!` to diverge.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

Before this PR, it was UB since we consider the `unsafe` block to diverge which means the outer block evalutes to `!`, even though we've never actually *read* a value of type `!`.

### Do not perform coercions of those same place expressions.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

---

Detecting both of these situations is implemented in a heuristic function called `expr_consitutes_read`. It is tasked with detecting the situations where we have a place expression being passed to some parent expression that would not constitute a read necessarily, like a `let _ = *never_ptr` where `never_ptr: *const !`.

---

Specifically, for `let` and `match`, we don't consider it to be a read unless any of the subpatterns (i.e. the LHS of the `let` or the arms of the match) constitute a read. Almost all patterns constitute a read except for `_`, an `|` pattern, or the currently experimental `!` pattern.

---

I'm not totally certain that this deserves an FCP, since it's really a bugfix for UB. If it does, I'd be comfortable with it being a T-types FCP since we should be responsible with determining which coercions in the type system are sound (similar to how we adjusted subtyping behavior in rust-lang#118247 to be more sound).
bors added a commit to rust-lang-ci/rust that referenced this issue Sep 6, 2024
…verge-but-more, r=<try>

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR does two things:

### Don't consider `match`/`let`/`ref`/assignment-LHS of a place expression that evaluates to `!` to diverge.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

Before this PR, it was UB since we consider the `unsafe` block to diverge which means the outer block evalutes to `!`, even though we've never actually *read* a value of type `!`.

### Do not perform coercions of those same place expressions.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

---

Detecting both of these situations is implemented in a heuristic function called `expr_consitutes_read`. It is tasked with detecting the situations where we have a place expression being passed to some parent expression that would not constitute a read necessarily, like a `let _ = *never_ptr` where `never_ptr: *const !`.

---

Specifically, for `let` and `match`, we don't consider it to be a read unless any of the subpatterns (i.e. the LHS of the `let` or the arms of the match) constitute a read. Almost all patterns constitute a read except for `_`, an `|` pattern, or the currently experimental `!` pattern.

---

I'm not totally certain that this deserves an FCP, since it's really a bugfix for UB. If it does, I'd be comfortable with it being a T-types FCP since we should be responsible with determining which coercions in the type system are sound (similar to how we adjusted subtyping behavior in rust-lang#118247 to be more sound).
bors added a commit to rust-lang-ci/rust that referenced this issue Sep 6, 2024
…verge-but-more, r=<try>

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR does two things:

### Don't consider `match`/`let`/`ref`/assignment-LHS of a place expression that evaluates to `!` to diverge.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

Before this PR, it was UB since we consider the `unsafe` block to diverge which means the outer block evalutes to `!`, even though we've never actually *read* a value of type `!`.

### Do not perform coercions of those same place expressions.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

---

Detecting both of these situations is implemented in a heuristic function called `expr_consitutes_read`. It is tasked with detecting the situations where we have a place expression being passed to some parent expression that would not constitute a read necessarily, like a `let _ = *never_ptr` where `never_ptr: *const !`.

---

Specifically, for `let` and `match`, we don't consider it to be a read unless any of the subpatterns (i.e. the LHS of the `let` or the arms of the match) constitute a read. Almost all patterns constitute a read except for `_`, an `|` pattern, or the currently experimental `!` pattern.

---

I'm not totally certain that this deserves an FCP, since it's really a bugfix for UB. If it does, I'd be comfortable with it being a T-types FCP since we should be responsible with determining which coercions in the type system are sound (similar to how we adjusted subtyping behavior in rust-lang#118247 to be more sound).
bors added a commit to rust-lang-ci/rust that referenced this issue Oct 6, 2024
…verge-but-more, r=lcnr

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR implements a heuristic which disables two things that are currently being performed on the HIR when we have **expressions that involve place-like expressions that point to `!`**. Specifically, it will (in certain cases explained below):

### (1.) Disable the `NeverToAny` coercion we implicitly insert for `!`.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

### (2.) Disable the logic which considers expression which evaluate to `!` to diverge, which affects the type returned by the containing block.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

We disable these two operations **if** the expression is a place-like expression (locals, statics, field projections, index operations, and deref operations), and if the parent expression is either:
(1.) the LHS of an assignment
(2.) AddrOf
(3.) A match or let **unless** all of the *patterns consitute a read*, which is explained below:

And finally, a pattern currently is considered to constitute a read **unless** it is a wildcard, or an OR pattern. An OR pattern is considered to constitute a read if all of its subpatterns constitute a read, to remain as conservative as possible in cases like `_ | subpat` or `subpat | _`.

All other patterns are considered currently to constitute a read. Specifically, because `NeverToAny` is a coercion performed on a *value* and not a *place*, `Struct { .. }` on a `!` type must be a coercion currently, and we currently rely on this behavior to allow us to perform coercions like `let _: i32 = x;` where `x: !`.

This is already considered UB by [miri](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf3a2246433fe43fdc07d1389c276c9), but also means it does not affect the preexisting UB in this case:

```
let Struct { .. } = *never_ptr;
```

Even though it's likely up for debate since we're not actually reading any data out of the struct, it almost certainly causes inference changes which I do *NOT* want to fix in this PR.
bors added a commit to rust-lang-ci/rust that referenced this issue Oct 6, 2024
…verge-but-more, r=lcnr

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR implements a heuristic which disables two things that are currently being performed on the HIR when we have **expressions that involve place-like expressions that point to `!`**. Specifically, it will (in certain cases explained below):

### (1.) Disable the `NeverToAny` coercion we implicitly insert for `!`.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

### (2.) Disable the logic which considers expression which evaluate to `!` to diverge, which affects the type returned by the containing block.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

We disable these two operations **if** the expression is a place-like expression (locals, statics, field projections, index operations, and deref operations), and if the parent expression is either:
(1.) the LHS of an assignment
(2.) AddrOf
(3.) A match or let **unless** all of the *patterns consitute a read*, which is explained below:

And finally, a pattern currently is considered to constitute a read **unless** it is a wildcard, or an OR pattern. An OR pattern is considered to constitute a read if all of its subpatterns constitute a read, to remain as conservative as possible in cases like `_ | subpat` or `subpat | _`.

All other patterns are considered currently to constitute a read. Specifically, because `NeverToAny` is a coercion performed on a *value* and not a *place*, `Struct { .. }` on a `!` type must be a coercion currently, and we currently rely on this behavior to allow us to perform coercions like `let _: i32 = x;` where `x: !`.

This is already considered UB by [miri](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf3a2246433fe43fdc07d1389c276c9), but also means it does not affect the preexisting UB in this case:

```
let Struct { .. } = *never_ptr;
```

Even though it's likely up for debate since we're not actually reading any data out of the struct, it almost certainly causes inference changes which I do *NOT* want to fix in this PR.
@bors bors closed this as completed in 9aaebd4 Oct 6, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Oct 6, 2024
Rollup merge of rust-lang#129392 - compiler-errors:raw-ref-op-doesnt-diverge-but-more, r=lcnr

Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too

Fixes rust-lang#117288.

This PR implements a heuristic which disables two things that are currently being performed on the HIR when we have **expressions that involve place-like expressions that point to `!`**. Specifically, it will (in certain cases explained below):

### (1.) Disable the `NeverToAny` coercion we implicitly insert for `!`.

Which fixes this inadvertent, sneaky unsoundness:

```
unsafe {
    let x: *const ! = &0 as *const u8 as *const !;
    let _: () = *x;
}
```

which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`.

### (2.) Disable the logic which considers expression which evaluate to `!` to diverge, which affects the type returned by the containing block.

Which fixes this unsoundness:

```
fn make_up_a_value<T>() -> T {
    unsafe {
        let x: *const ! = &0 as *const u8 as *const !;
        let _ = *x;
    }
}
```

We disable these two operations **if** the expression is a place-like expression (locals, statics, field projections, index operations, and deref operations), and if the parent expression is either:
(1.) the LHS of an assignment
(2.) AddrOf
(3.) A match or let **unless** all of the *patterns consitute a read*, which is explained below:

And finally, a pattern currently is considered to constitute a read **unless** it is a wildcard, or an OR pattern. An OR pattern is considered to constitute a read if all of its subpatterns constitute a read, to remain as conservative as possible in cases like `_ | subpat` or `subpat | _`.

All other patterns are considered currently to constitute a read. Specifically, because `NeverToAny` is a coercion performed on a *value* and not a *place*, `Struct { .. }` on a `!` type must be a coercion currently, and we currently rely on this behavior to allow us to perform coercions like `let _: i32 = x;` where `x: !`.

This is already considered UB by [miri](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf3a2246433fe43fdc07d1389c276c9), but also means it does not affect the preexisting UB in this case:

```
let Struct { .. } = *never_ptr;
```

Even though it's likely up for debate since we're not actually reading any data out of the struct, it almost certainly causes inference changes which I do *NOT* want to fix in this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-never_type `#![feature(never_type)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-high High priority requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-opsem Relevant to the opsem team
Projects
None yet