Auto merge of #128543 - RalfJung:const-interior-mut, r=fee1-dead
const-eval interning: accept interior mutable pointers in final value …but keep rejecting mutable references This fixes https://github.com/rust-lang/rust/issues/121610 by no longer firing the lint when there is a pointer with interior mutability in the final value of the constant. On stable, such pointers can be created with code like: ```rust pub enum JsValue { Undefined, Object(Cell<bool>), } impl Drop for JsValue { fn drop(&mut self) {} } // This does *not* get promoted since `JsValue` has a destructor. // However, the outer scope rule applies, still giving this 'static lifetime. const UNDEFINED: &JsValue = &JsValue::Undefined; ``` It's not great to accept such values since people *might* think that it is legal to mutate them with unsafe code. (This is related to how "infectious" `UnsafeCell` is, which is a [wide open question](https://github.com/rust-lang/unsafe-code-guidelines/issues/236).) However, we [explicitly document](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) that things created by `const` are immutable. Furthermore, we also accept the following even more questionable code without any lint today: ```rust let x: &'static Option<Cell<i32>> = &None; ``` This is even more questionable since it does *not* involve a `const`, and yet still puts the data into immutable memory. We could view this as promotion [potentially introducing UB](https://github.com/rust-lang/unsafe-code-guidelines/issues/493). However, we've accepted this since ~forever and it's [too late to reject this now](https://github.com/rust-lang/rust/pull/122789); the pattern is just too useful. So basically, if you think that `UnsafeCell` should be tracked fully precisely, then you should want the lint we currently emit to be removed, which this PR does. If you think `UnsafeCell` should "infect" surrounding `enum`s, the big problem is really https://github.com/rust-lang/unsafe-code-guidelines/issues/493 which does not trigger the lint -- the cases the lint triggers on are actually the "harmless" ones as there is an explicit surrounding `const` explaining why things end up being immutable. What all this goes to show is that the hard error added in https://github.com/rust-lang/rust/pull/118324 (later turned into the future-compat lint that I am now suggesting we remove) was based on some wrong assumptions, at least insofar as it concerns shared references. Furthermore, that lint does not help at all for the most problematic case here where the potential UB is completely implicit. (In fact, the lint is actively in the way of [my preferred long-term strategy](https://github.com/rust-lang/unsafe-code-guidelines/issues/493#issuecomment-2028674105) for dealing with this UB.) So I think we should go back to square one and remove that error/lint for shared references. For mutable references, it does seem to work as intended, so we can keep it. Here it serves as a safety net in case the static checks that try to contain mutable references to the inside of a const initializer are not working as intended; I therefore made the check ICE to encourage users to tell us if that safety net is triggered. Closes https://github.com/rust-lang/rust/issues/122153 by removing the lint. Cc `@rust-lang/opsem` `@rust-lang/lang`
This commit is contained in:
@@ -10,7 +10,6 @@ use rustc_middle::traits::Reveal;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, DUMMY_SP};
|
||||
use rustc_target::abi::{self, Abi};
|
||||
@@ -18,13 +17,12 @@ use tracing::{debug, instrument, trace};
|
||||
|
||||
use super::{CanAccessMutGlobal, CompileTimeInterpCx, CompileTimeMachine};
|
||||
use crate::const_eval::CheckAlignment;
|
||||
use crate::errors::{self, ConstEvalError, DanglingPtrInFinal};
|
||||
use crate::interpret::{
|
||||
create_static_alloc, eval_nullary_intrinsic, intern_const_alloc_recursive, throw_exhaust,
|
||||
CtfeValidationMode, GlobalId, Immediate, InternKind, InternResult, InterpCx, InterpError,
|
||||
InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup,
|
||||
};
|
||||
use crate::CTRL_C_RECEIVED;
|
||||
use crate::{errors, CTRL_C_RECEIVED};
|
||||
|
||||
// Returns a pointer to where the result lives
|
||||
#[instrument(level = "trace", skip(ecx, body))]
|
||||
@@ -100,18 +98,15 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
|
||||
return Err(ecx
|
||||
.tcx
|
||||
.dcx()
|
||||
.emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
|
||||
.emit_err(errors::DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
|
||||
.into());
|
||||
}
|
||||
Err(InternResult::FoundBadMutablePointer) => {
|
||||
// only report mutable pointers if there were no dangling pointers
|
||||
let err_diag = errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind };
|
||||
ecx.tcx.emit_node_span_lint(
|
||||
lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE,
|
||||
ecx.machine.best_lint_scope(*ecx.tcx),
|
||||
err_diag.span,
|
||||
err_diag,
|
||||
)
|
||||
return Err(ecx
|
||||
.tcx
|
||||
.dcx()
|
||||
.emit_err(errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind })
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,7 +438,12 @@ fn report_eval_error<'tcx>(
|
||||
error,
|
||||
DUMMY_SP,
|
||||
|| super::get_span_and_frames(ecx.tcx, ecx.stack()),
|
||||
|span, frames| ConstEvalError { span, error_kind: kind, instance, frame_notes: frames },
|
||||
|span, frames| errors::ConstEvalError {
|
||||
span,
|
||||
error_kind: kind,
|
||||
instance,
|
||||
frame_notes: frames,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -718,16 +718,29 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
|
||||
_kind: mir::RetagKind,
|
||||
val: &ImmTy<'tcx, CtfeProvenance>,
|
||||
) -> InterpResult<'tcx, ImmTy<'tcx, CtfeProvenance>> {
|
||||
// If it's a frozen shared reference that's not already immutable, make it immutable.
|
||||
// If it's a frozen shared reference that's not already immutable, potentially make it immutable.
|
||||
// (Do nothing on `None` provenance, that cannot store immutability anyway.)
|
||||
if let ty::Ref(_, ty, mutbl) = val.layout.ty.kind()
|
||||
&& *mutbl == Mutability::Not
|
||||
&& val.to_scalar_and_meta().0.to_pointer(ecx)?.provenance.is_some_and(|p| !p.immutable())
|
||||
// That next check is expensive, that's why we have all the guards above.
|
||||
&& ty.is_freeze(*ecx.tcx, ecx.param_env)
|
||||
&& val
|
||||
.to_scalar_and_meta()
|
||||
.0
|
||||
.to_pointer(ecx)?
|
||||
.provenance
|
||||
.is_some_and(|p| !p.immutable())
|
||||
{
|
||||
// That next check is expensive, that's why we have all the guards above.
|
||||
let is_immutable = ty.is_freeze(*ecx.tcx, ecx.param_env);
|
||||
let place = ecx.ref_to_mplace(val)?;
|
||||
let new_place = place.map_provenance(CtfeProvenance::as_immutable);
|
||||
let new_place = if is_immutable {
|
||||
place.map_provenance(CtfeProvenance::as_immutable)
|
||||
} else {
|
||||
// Even if it is not immutable, remember that it is a shared reference.
|
||||
// This allows it to become part of the final value of the constant.
|
||||
// (See <https://github.com/rust-lang/rust/pull/128543> for why we allow this
|
||||
// even when there is interior mutability.)
|
||||
place.map_provenance(CtfeProvenance::as_shared_ref)
|
||||
};
|
||||
Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
|
||||
} else {
|
||||
Ok(val.clone())
|
||||
|
||||
Reference in New Issue
Block a user