Skip to content

ICE from using super let and &raw in const #142229

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

Open
theemathas opened this issue Jun 9, 2025 · 15 comments
Open

ICE from using super let and &raw in const #142229

theemathas opened this issue Jun 9, 2025 · 15 comments
Labels
C-bug Category: This is a bug. F-super_let it's super, let's go! I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@theemathas
Copy link
Contributor

Code

#![feature(super_let)]

const _: *const i32 = {
    super let x = 1;
    &raw const x
};

Meta

Reproducible on the playground with 1.89.0-nightly (2025-06-08 6ccd4476036edfce364e)

Error output

error: internal compiler error: compiler/rustc_const_eval/src/interpret/intern.rs:285:21: the static const safety checks accepted mutable references they should not have accepted
 --> src/lib.rs:3:1
  |
3 | const _: *const i32 = {
  | ^^^^^^^^^^^^^^^^^^^
Backtrace

thread 'rustc' panicked at compiler/rustc_const_eval/src/interpret/intern.rs:285:21:
Box<dyn Any>
stack backtrace:
   0: std::panicking::begin_panic::<rustc_errors::ExplicitBug>
   1: <rustc_errors::diagnostic::BugAbort as rustc_errors::diagnostic::EmissionGuarantee>::emit_producing_guarantee
   2: <rustc_errors::DiagCtxtHandle>::span_bug::<rustc_span::span_encoding::Span, alloc::string::String>
   3: rustc_middle::util::bug::opt_span_bug_fmt::<rustc_span::span_encoding::Span>::{closure#0}
   4: rustc_middle::ty::context::tls::with_opt::<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}
   5: rustc_middle::ty::context::tls::with_context_opt::<rustc_middle::ty::context::tls::with_opt<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}, !>
   6: rustc_middle::util::bug::span_bug_fmt::<rustc_span::span_encoding::Span>
   7: rustc_const_eval::interpret::intern::intern_const_alloc_recursive::<rustc_const_eval::const_eval::machine::CompileTimeMachine>
   8: rustc_const_eval::const_eval::eval_queries::eval_to_allocation_raw_provider
      [... omitted 1 frame ...]
   9: rustc_const_eval::const_eval::eval_queries::eval_to_const_value_raw_provider
      [... omitted 1 frame ...]
  10: rustc_hir_analysis::check_crate
  11: rustc_interface::passes::analysis
      [... omitted 1 frame ...]
  12: rustc_interface::passes::create_and_enter_global_ctxt::<core::option::Option<rustc_interface::queries::Linker>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2}>::{closure#2}::{closure#0}
  13: rustc_interface::interface::run_compiler::<(), rustc_driver_impl::run_compiler::{closure#0}>::{closure#1}
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: please make sure that you have updated to the latest nightly

note: please attach the file at `/playground/rustc-ice-2025-06-09T05_51_22-61.txt` to your bug report

note: compiler flags: --crate-type lib -C embed-bitcode=no -C codegen-units=1 -C debuginfo=2

note: some of the compiler flags provided by cargo are hidden

query stack during panic:
#0 [eval_to_allocation_raw] const-evaluating + checking `_`
#1 [eval_to_const_value_raw] simplifying constant for the type system `_`
#2 [analysis] running analysis passes on this crate
end of query stack
error: could not compile `playground` (lib)

@theemathas theemathas added I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. C-bug Category: This is a bug. labels Jun 9, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 9, 2025
@workingjubilee workingjubilee added the F-super_let it's super, let's go! label Jun 9, 2025
@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

Uh, yes we should entirely forbid super let in the top-level scope of a const or static IMO.^^ They would introduce new global allocations, and it is rather unclear how one should determine their mutability and what we can say about when two constants will point to the same allocation.

It is bad enough that we do "lifetime/scope extension" in consts at all, which has caused enormous amounts of headache for very little benefit. But that is at least very syntactically restricted, and in particular does not allow raw pointers. Please let us not extend this to arbitrary user-defined expressions.

Cc @rust-lang/wg-const-eval @m-ou-se

@m-ou-se
Copy link
Member

m-ou-se commented Jun 9, 2025

If lifetime/scope extension works in consts, I also think super let should work in consts. It's just a way to opt-in to lifetime extension, after all. That's the only way we can make pin!() and (later) format_args!(), etc. behave consistently in consts.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

It's just a way to opt-in to lifetime extension, after all.

Be careful when you use "just" for something that's not trivial at all. ;) Lifetime extension as of today is quite limited, and for consts, that is crucial. We do not want more lifetime extension in consts. I barely managed to carve out a sound way of having &mut in consts around the current lifetime extension rules, and what we do heavily depends on the exact shape of those rules.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

To demonstrate the problem, consider code like this:

pub const C: *mut i32 = {
    super let mut x = 1;
    &raw mut x
};

Now imagine C being used in multiple crates, some reading, some writing. (Let's say the program is single-threaded, concurrency is not the problem here.) What would you expect to happen?

The first thing that happens is that we violate the definition of consts, that every use of C acts as-if the initializer expression was copy-pasted into the place where C is used.

*C = 4;
assert_eq!(*C, 1); // this assert will fail

It gets worse, however. If C is defined in crate A and then used by multiple crates that depend on A, they can each get their own copy of C, pointing to separate versions of x. It ends up being rather random which crate can see which other crate's modifications. Since consts can depend on generics, there is fundamentally no way to guarantee that x will exist exactly once globally.

The ICE is triggered by a safety net that I added specifically because ensuring sane behavior of consts relies on a bunch of subtle invariants spread across the entire language. super let breaks one of those invariants. The safety net is not 100% airtight any more, there are some gaps around interior mutability, but lucky enough it is still airtight enough to have found this problem before stabilization.

Purely on the conceptual level, there's also the concern that the only sensible behavior of the constant above is to introduce a global mutable allocation, but global mutable state is an enormous foot-gun and I feel very strongly that we should require very explicit opt-in for creating such state (e.g. via static mut).

Note that I am talking only about the top-level scope of a const/static item (including inline const expressions). There is no problem at all with super let in const fn, or even in nested scopes of a const/static item.

@theemathas
Copy link
Contributor Author

#![feature(super_let)]
pub const C: *mut i32 = {
    super let mut x = 1;
    &raw mut x
};

Currently, this code fails to compile with the following error

error[E0764]: raw mutable pointers are not allowed in the final value of constants
 --> src/lib.rs:4:5
  |
4 |     &raw mut x
  |     ^^^^^^^^^^

For more information about this error, try `rustc --explain E0764`.
error: could not compile `playground` (lib) due to 1 previous error

@theemathas
Copy link
Contributor Author

theemathas commented Jun 10, 2025

#![feature(super_let)]
pub const C: &i32 = {
    super let x = 1;
    &x
};

This code currently compiles fine.

@theemathas
Copy link
Contributor Author

Currently, in nightly, the following code compiles without unstable features, and it macro-expands to code that uses super let in the "top-level" of a const.

use std::pin::{pin, Pin};
pub const C: () = ignore(pin!(1));
const fn ignore(_: Pin<&mut i32>) {}

@fee1-dead
Copy link
Member

fee1-dead commented Jun 10, 2025

I would support us disallowing super lets at the top level inside consts and statics as a first step, before we figure out how they are supposed to interact.

@RalfJung
Copy link
Member

Currently, this code fails to compile with the following error

Here's a variant which avoids the error (and then ICEs):

#![feature(super_let)]
pub const C: *mut i32 = {
    super let mut x = 1;
    &raw const x as *mut i32
};

At least under Tree Borrows, mutating x through this pointer is entirely fine, so this code still has all the same problems as my earlier example.

Currently, in nightly, the following code compiles without unstable features, and it macro-expands to code that uses super let in the "top-level" of a const.

So a super let in a function argument moves all the way out to the top-level scope...?

The pin! macro is probably fine since it involves an &mut and the analysis on when you can do that should be sound for arbitrary MIR -- whenever the variable truly lives at the top-level scope, the &mut will get rejected.

@RalfJung
Copy link
Member

RalfJung commented Jun 10, 2025

format_args! (#140748) is more interesting as it involves a shared reference, so this could extend the blast radius of rust-lang/unsafe-code-guidelines#493. However, it cannot lead to ICEs as the error is suppressed for pointers derived from a shared reference (precisely since otherwise rust-lang/unsafe-code-guidelines#493 and related problems with lifetime extension could be used to trigger ICEs).

I think ICEs can only occur with raw pointers, but problematic patterns can occur even with just references.


#128543 has some context for the "safety net" I mentioned before.

FWIW, we do document that

All bytes reachable through a const-promoted expression are immutable, as well as bytes reachable through borrows in static and const initializers that have been lifetime-extended to 'static.

This could be used to justify why writing to the C from my examples is UB. But this seems like such a huge footgun to me. We added that wording to the reference since it is the only way we could think of for dealing with rust-lang/unsafe-code-guidelines#493, but it does not seem like a good idea to extend the scope of where that rule needs to be applied.

@theemathas
Copy link
Contributor Author

This ICE also occurs in const blocks, such as in the following code:

#![feature(super_let)]

fn main() {
    let _ = const {
        super let x = 1;
        &raw const x
    };
}

@RalfJung
Copy link
Member

Yeah const blocks are exactly like items in almost every regard, so that's the same issue.

@RalfJung
Copy link
Member

To refresh my memory of the problem space here, I have written up a summary of the problem with lifetime extension. One conclusion is that the ICE can only occur with &raw const, so format_args! does not risk ICEing. However, semantic trouble can also occur any time a shared reference to a !Freeze type is involved -- but AFAIK even that is fine with format_args! since super let holds the argument info, which has no UnsafeCell, and which only references the user's format arguments that may have interior mutability.

What we should do more broadly, I am not sure. We could just remove the check that causes the ICE, but I am not a fan of the semantic footgun this unveils.

@fmease fmease removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 10, 2025
@danielhenrymantilla
Copy link
Contributor

I have been thinking about this, and, if &raw const/mut is conservatively forbidden on temporaries, then it's starting to look like it should be forbidden on super let places as well; since they're, in fact, a temporary wearing a fancy super let tuxedo, after all?

And once that would be in place, we might not need forbid super let in const?

@RalfJung
Copy link
Member

Yeah, if that's possible that would avoid all ICEs, and also some of the semantic headache -- at least all problematic cases would involve mutating through a pointer derived from an & that points to a value that has no UnsafeCell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-super_let it's super, let's go! I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

8 participants