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

const checking: do not do value-based reasoning for interior mutability #121786

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,10 @@ pub fn const_validate_mplace<'mir, 'tcx>(
_ if cid.promoted.is_some() => CtfeValidationMode::Promoted,
Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static`
None => {
// In normal `const` (not promoted), the outermost allocation is always only copied,
// so having `UnsafeCell` in there is okay despite them being in immutable memory.
let allow_immutable_unsafe_cell = cid.promoted.is_none() && !inner;
CtfeValidationMode::Const { allow_immutable_unsafe_cell }
// This is a normal `const` (not promoted).
// The outermost allocation is always only copied, so having `UnsafeCell` in there
// is okay despite them being in immutable memory.
CtfeValidationMode::Const { allow_immutable_unsafe_cell: !inner }
}
};
ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?;
Expand Down
47 changes: 39 additions & 8 deletions compiler/rustc_const_eval/src/transform/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type QualifResults<'mir, 'tcx, Q> =
rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'mir, 'mir, 'tcx, Q>>;

#[derive(Default)]
pub struct Qualifs<'mir, 'tcx> {
pub(crate) struct Qualifs<'mir, 'tcx> {
has_mut_interior: Option<QualifResults<'mir, 'tcx, HasMutInterior>>,
needs_drop: Option<QualifResults<'mir, 'tcx, NeedsDrop>>,
needs_non_const_drop: Option<QualifResults<'mir, 'tcx, NeedsNonConstDrop>>,
Expand Down Expand Up @@ -373,6 +373,21 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
}
}
}

fn has_interior_mut(&self, ty: Ty<'tcx>) -> bool {
match ty.kind() {
// Empty arrays have no interior mutability no matter their element type.
ty::Array(_elem, count)
if count
.try_eval_target_usize(self.tcx, self.param_env)
.is_some_and(|v| v == 0) =>
{
false
}
// Fallback to checking `Freeze`.
_ => !ty.is_freeze(self.tcx, self.param_env),
}
}
}

impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Expand Down Expand Up @@ -484,11 +499,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {

Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake, place)
| Rvalue::AddressOf(Mutability::Not, place) => {
let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>(
self.ccx,
&mut |local| self.qualifs.has_mut_interior(self.ccx, local, location),
place.as_ref(),
);
// We don't do value-based reasoning here, since the rules for interior mutability
// are not finalized yet and they seem likely to not be full value-based in the end.
let borrowed_place_has_mut_interior =
self.has_interior_mut(place.ty(self.body, self.tcx).ty);

// If the place is indirect, this is basically a reborrow. We have a reborrow
// special case above, but for raw pointers and pointers/references to `static` and
Expand All @@ -498,6 +512,17 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// `TransientMutBorrow`), but such reborrows got accidentally stabilized already and
// it is too much of a breaking change to take back.
if borrowed_place_has_mut_interior && !place.is_indirect() {
// We used to do a value-based check here, so when we changed to a purely
// type-based check we started rejecting code that used to work on stable. So
// for that reason we already stabilize "transient borrows of interior mutable
// borrows where value-based reasoning says that there actually is no interior
// mutability". We don't do anything like that for non-transient borrows since
// those are and will remain hard errors.
let allow_transient_on_stable = !qualifs::in_place::<HasMutInterior, _>(
self.ccx,
&mut |local| self.qualifs.has_mut_interior(self.ccx, local, location),
place.as_ref(),
);
match self.const_kind() {
// In a const fn all borrows are transient or point to the places given via
// references in the arguments (so we already checked them with
Expand All @@ -506,7 +531,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// NOTE: Once we have heap allocations during CTFE we need to figure out
// how to prevent `const fn` to create long-lived allocations that point
// to (interior) mutable memory.
hir::ConstContext::ConstFn => self.check_op(ops::TransientCellBorrow),
hir::ConstContext::ConstFn => {
if !allow_transient_on_stable {
self.check_op(ops::TransientCellBorrow)
}
}
_ => {
// Locals with StorageDead are definitely not part of the final constant value, and
// it is thus inherently safe to permit such locals to have their
Expand All @@ -517,7 +546,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// The good news is that interning will detect if any unexpected mutable
// pointer slips through.
if self.local_has_storage_dead(place.local) {
self.check_op(ops::TransientCellBorrow);
if !allow_transient_on_stable {
self.check_op(ops::TransientCellBorrow);
}
} else {
self.check_op(ops::CellBorrow);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ where
if Q::in_adt_inherently(cx, def, args) {
return true;
}
// Don't do any value-based reasoning for unions.
if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) {
return true;
}
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_mir_transform/src/promote_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,24 @@ impl<'tcx> Validator<'_, 'tcx> {
if has_mut_interior {
return Err(Unpromotable);
}
// Let's just see what happens if we reject anything `!Freeze`...
// (Except ZST which definitely can't have interior mut)
let ty = place.ty(self.body, self.tcx).ty;
let has_interior_mut = match ty.kind() {
// Empty arrays have no interior mutability no matter their element type.
ty::Array(_elem, count)
if count
.try_eval_target_usize(self.tcx, self.param_env)
.is_some_and(|v| v == 0) =>
{
false
}
// Fallback to checking `Freeze`.
_ => !ty.is_freeze(self.tcx, self.param_env),
};
if has_interior_mut {
return Err(Unpromotable);
}
}

// FIXME: consider changing this to only promote &mut [] for default borrows,
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/consts/enclosing-scope-rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//@build-pass
// Some code that looks like it might be relying on promotion, but actually this is using the
// enclosing-scope rule, meaning the reference is "extended" to outlive its block and live as long
// as the surrounding block (which in this case is the entire program). There are multiple
// allocations being interned at once.

struct Gen<T>(T);
impl<'a, T> Gen<&'a T> {
// Can't be promoted because `T` might not be `'static`.
const C: &'a [T] = &[];
}

// Can't be promoted because of `Drop`.
const V: &Vec<i32> = &Vec::new();

fn main() {}
9 changes: 9 additions & 0 deletions tests/ui/consts/promote-not.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![allow(unconditional_panic)]

use std::cell::Cell;
use std::mem::ManuallyDrop;

// We do not promote mutable references.
static mut TEST1: Option<&mut [i32]> = Some(&mut [1, 2, 3]); //~ ERROR temporary value dropped while borrowed
Expand Down Expand Up @@ -69,4 +70,12 @@ fn main() {
let _val: &'static _ = &[&TEST_DROP; 1];
//~^ ERROR temporary value dropped while borrowed
//~| ERROR temporary value dropped while borrowed

// Make sure there is no value-based reasoning for unions.
union UnionWithCell {
f1: i32,
f2: ManuallyDrop<Cell<i32>>,
}
let x: &'static _ = &UnionWithCell { f1: 0 };
//~^ ERROR temporary value dropped while borrowed
}
Loading
Loading