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

E0492: borrow of an interior mutable value may end up in the final value during const eval when no inner mutability is involved #121250

Closed
sarah-ek opened this issue Feb 18, 2024 · 26 comments
Labels
A-const-eval Area: constant evaluation (mir interpretation) disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. P-medium Medium priority regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@sarah-ek
Copy link

sarah-ek commented Feb 18, 2024

This code works on stable, but not latest nightly

#[derive(Copy, Clone)]
pub struct S<T>(T);

#[doc(hidden)]
pub trait DynTrait {
    type VTable: Copy + 'static;
    const VTABLE: &'static Self::VTable;
}

impl<T: DynTrait> DynTrait for S<T> {
    type VTable = T::VTable;
    const VTABLE: &'static Self::VTable = &{ *T::VTABLE };
}

Error:

error[E0492]: constants cannot refer to interior mutable data
  --> src/lib.rs:12:43
   |
12 |     const VTABLE: &'static Self::VTable = &{ *T::VTABLE };
   |                                           ^^^^^^^^^^^^^^^ this borrow of an interior mutable value may end up in the final value

rustc --version --verbose:

rustc 1.78.0-nightly (6672c16af 2024-02-17)
binary: rustc
commit-hash: 6672c16afcd4db8acdf08a6984fd4107bf07632c
commit-date: 2024-02-17
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 18.1.0
@sarah-ek sarah-ek added the C-bug Category: This is a bug. label Feb 18, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 18, 2024
@RalfJung RalfJung added the regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. label Feb 18, 2024
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Feb 18, 2024
@RalfJung
Copy link
Member

RalfJung commented Feb 18, 2024

This is probably a regression introduced by #120932.

However, the error is right: this is a reference to Self::VTable, and the impl is generic, so for all this code knows, Self::VTable might have interior mutability.

#120932 fixed a bug in our interior-mutablity-checking code. I think what this actually shows is that the code never should have been accepted in the first place. But we may need a crater run to figure out how bad this regression is. Is there a way to retroactively do a crater run for #120932?

Cc @oli-obk

@sarah-ek
Copy link
Author

is there a trait bound or something similar that can be added to tell the compiler that there won't be any internal mutability?

@RalfJung
Copy link
Member

is there a trait bound or something similar that can be added to tell the compiler that there won't be any internal mutability?

Unfortunately not on stable -- see #60715.

@RalfJung
Copy link
Member

RalfJung commented Feb 18, 2024

Interestingly I think there is no soundness issue on stable here: in &{ *T::VTABLE }, T::VTABLE is itself a const, and consts can never point to anything interior mutable without const_refs_to_static, and therefore just saying that every *place expression in a const has no interior mutability is sound. And with statics we can't write generic code, so we'd need a concrete type that is both Copy and !Freeze, which does not exist. So by sheer luck there is no soundness issue here.


@sarah-ek is there a reason why you wrote &{ *T::VTABLE } instead of &*T::VTABLE? The extra curly braces make a big difference here. With curly braces, this means "please create a new temporary memory allocation storing a copy of *T::VTABLE, and then create a reference to that". Without curly braces, this means "please create a reference to the existing allocation that T::VTABLE points to". Creating temporary allocations with interior mutability is something we try to prevent, but due to a bug the code you showed slipped through those checks.

Without curly braces, the code still works fine on nightly with the fixed checks:

#[derive(Copy, Clone)]
pub struct S<T>(T);

#[doc(hidden)]
pub trait DynTrait {
    type VTable: Copy + 'static;
    const VTABLE: &'static Self::VTable;
}

impl<T: DynTrait> DynTrait for S<T> {
    type VTable = T::VTable;
    const VTABLE: &'static Self::VTable = &*T::VTABLE;
}

@sarah-ek
Copy link
Author

this is a simplification of a different scenario. the actual code is here https://github.com/sarah-ek/equator/blob/444beec918bb83e6c9e400a1849dcf015795a7b0/equator/src/lib.rs#L581-L584

the reason i want to borrow is so i can force the promotion to static when used as a part of my own assert! macro

@RalfJung
Copy link
Member

But promotion to static is exactly what we don't want to do when there might be interior mutability. So that kind of code is definitely not intended to work.

I don't know how constrained you are with the types you can use here; would something like this work for you?

impl<Lhs: DynDebug, Rhs: DynDebug> DynDebug for AndExpr<Lhs, Rhs> {
    type VTable = (&'static Lhs::VTable, &'static Rhs::VTable);
    const VTABLE: &'static Self::VTable = &(Lhs::VTABLE, Rhs::VTABLE);
}

@sarah-ek
Copy link
Author

yeah, i ended up going with that

@RalfJung
Copy link
Member

Okay, glad to hear you have a work-around.

We still need to decide what to do with this accidental regression though. It is IMO a bugfix in our analysis, even though the stars align to make it not obviously unsound -- though it's also possible that I missed something in my analysis above.

Let's see what Oli says.

@oli-obk
Copy link
Contributor

oli-obk commented Feb 18, 2024

I think the right thing would be to error out as TooGeneric if we encounter such a constant.

Or just not intern generic constants at all, but error out as TooGeneric before interning and validation in order to still get the const eval coverage of the rest.

Evaluating generic constants is best effort anyway, and repeated from scratch for every monomorphization, at which point we'll only error if there is an actual mutable allocation there

@saethlin saethlin added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-const-eval Area: constant evaluation (mir interpretation) and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Feb 18, 2024
@RalfJung
Copy link
Member

RalfJung commented Feb 18, 2024

I think the right thing would be to error out as TooGeneric if we encounter such a constant.

This is not an interpreter error, it is a const-checking error (i.e., it is raised in compiler/rustc_const_eval/src/transform/check_consts/check.rs). Those can't stop with "too generic", I think?

@oli-obk
Copy link
Contributor

oli-obk commented Feb 18, 2024

Oh 🤦 I was wondering how we got that error wording from interning.

I'll need to think about this some more.

@juntyr
Copy link
Contributor

juntyr commented Feb 19, 2024

I just stumbled across the same issue in my weekly CI. In my nightly crate, I am at one point building a HList that will be layout-equivalent to an array. The following code now fails (this is somewhat simplified to collapse helper traits):

pub trait ComputeSet {
    type TyHList: 'static + Copy;
    const TYS: &'static Self::TyHList;
}

impl<H2: TypeLayout, T: ComputeSet> ComputeSet for Cons<H2, T> {
    type TyHList = Cons<&'static crate::TypeLayoutInfo<'static>, T::TyHList>;

    const TYS: &'static Self::TyHList = &Cons {
        head: &H2::TYPE_LAYOUT,
        tail: *T::TYS,
    };
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct Cons<H, T> {
    head: H,
    tail: T,
}

pub unsafe trait TypeLayout: Sized {
    const TYPE_LAYOUT: TypeLayoutInfo<'static>;
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypeLayoutInfo<'a> {
    ...
}

Error:

error[E0492]: constants cannot refer to interior mutable data
   --> src/typeset.rs:163:45
    |
163 |           const TYS: &'static Self::TyHList = &Cons {
    |  _____________________________________________^
164 | |             head: &H2::TYPE_LAYOUT,
165 | |             tail: *T::TYS,
166 | |         };
    | |_________^ this borrow of an interior mutable value may end up in the final value

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

The complete version can be found here:
https://github.com/juntyr/const-type-layout/blob/990a562c62a44dcc0c96e5ebf9790ce4352d6de5/src/typeset.rs#L152-L161

rustc --version --verbose:

rustc 1.78.0-nightly (2bf78d12d 2024-02-18)
binary: rustc
commit-hash: 2bf78d12d33ae02d10010309a0d85dd04e7cff72
commit-date: 2024-02-18
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 18.1.0

@apiraino
Copy link
Contributor

WG-prioritization assigning priority (Zulip discussion).

@rustbot label -I-prioritize +P-medium

@rustbot rustbot added P-medium Medium priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Feb 19, 2024
@RalfJung
Copy link
Member

Crater results are in. The equator crate (the crate of the OP) is the only regression across all of crates.io.

So I am inclined to classify this as a bugfix -- we shouldn't have allowed such temporaries with potentially interior mutable data to be creates in the first place. We should prioritize letting people write generic code while preserving the information that a type has no interior mutability, i.e., letting people use Freeze bounds on stable.

@oli-obk oli-obk added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. C-bug Category: This is a bug. labels Feb 23, 2024
@oli-obk
Copy link
Contributor

oli-obk commented Feb 23, 2024

@rust-lang/lang We accidentally fixed a bug (by cleaning up const checking code in a way that inherently forbade the bug). This bug has been found to have a single regression. The issue is already being worked around.

The bug fix is that we now correctly forbid using associated constants that have generic parameters (either themselves whenever we get GACs, or from their trait), if those constants are used behind references. This was a hole in our checks for preventing interior mutability behind references in constants. These are not stable yet, and if the constant is generic, we cannot know whether it has interior mutability and need to reject it.

@juntyr
Copy link
Contributor

juntyr commented Feb 23, 2024

What workaround could I use for my usage of this pattern? In my case, I need to use the associated type alias to hide a generic array length and instead use an hlist, which is made up of a reference to a type that is non-generic and has no interior mutability, and the existing hlist (which is another hlist). The hlist is stored behind a reference since it is eventually converted into a slice (still inside const code), for which it already needs to be a valid allocation.

@oli-obk
Copy link
Contributor

oli-obk commented Feb 23, 2024

Since you are using a nightly crate anyway, you can enable the const_refs_to_cell feature. We're fairly confident that we'll be able to finally stabilize it this year.

@RalfJung
Copy link
Member

RalfJung commented Feb 23, 2024

No the feature is not sufficient. The problem isn't that we allowed something that should be unstable, the problem is that we allowed something unsound. (Or at least our soundness argument was wrong. There turns out to be a much more complicated soundness argument, but that relies on an accidental invariant that we do not intend to maintain. And we have a second line of defense during interning and validity checking, but that is post-monomorphization and so far we've generally avoided relying on it for these things.)

Even with const_refs_to_cell, you can only do "transient" borrows to interior mutable data. &{ *T::VTABLE } is a borrow that ends up in the final value of the const, and it may have interior mutability, and therefore it cannot be allowed.

@juntyr I'm afraid I don't know a work-around for your usage. Sorry for that. :( We never intended to allow the code you wrote, it was an accident. Now we asked the lang team evaluate whether the existing usage of this accident is sufficient precedent to make us contort the language so that we can keep accepting this code in a proper, non-accidental way -- or whether we're better off rejecting such code.

@RalfJung
Copy link
Member

I don't know a work-around for your usage

I should have said -- with current compilers. Your usecase will be possible once #60715 is resolved.

@juntyr
Copy link
Contributor

juntyr commented Feb 23, 2024

@juntyr I'm afraid I don't know a work-around for your usage. Sorry for that. :( We never intended to allow the code you wrote, it was an accident. Now we asked the lang team evaluate whether the existing usage of this accident is sufficient precedent to make us contort the language so that we can keep accepting this code in a proper, non-accidental way -- or whether we're better off rejecting such code.

Thank you @RalfJung for the clarification! I'll revert to pinning my nightly again (which in this crate has happened before when the const trait rework started). Hopefully we get Freeze back at some point, which I would love, or I find another workaround.

@joshtriplett
Copy link
Member

We discussed this in today's lang meeting, and concluded that we do want to keep the behavior of current nightly. Starting an FCP to confirm that consensus:

@rfcbot merge

@rfcbot
Copy link

rfcbot commented Feb 28, 2024

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Feb 28, 2024
@nikomatsakis
Copy link
Contributor

@rfcbot reviewed

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Feb 28, 2024
@rfcbot
Copy link

rfcbot commented Feb 28, 2024

🔔 This is now entering its final comment period, as per the review above. 🔔

@traviscross
Copy link
Contributor

@rustbot labels -I-lang-nominated

As reflected above, we discussed this in lang triage today. It's now in FCP, so let's unnominate.

@rustbot rustbot removed the I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. label Feb 28, 2024
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Mar 9, 2024
@rfcbot
Copy link

rfcbot commented Mar 9, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-eval Area: constant evaluation (mir interpretation) disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. P-medium Medium priority regression-from-stable-to-nightly Performance or correctness regression from stable to nightly. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests