Skip to content

Commit 69d855c

Browse files
committed
Add a static analysis that prevents references to inner mutability only in the trailing expression of a constant
1 parent 35c1268 commit 69d855c

36 files changed

+409
-218
lines changed

compiler/rustc_feature/src/active.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,9 @@ declare_features! (
623623
/// Allows arbitrary expressions in key-value attributes at parse time.
624624
(active, extended_key_value_attributes, "1.50.0", Some(78835), None),
625625

626+
/// Allows references to types with interior mutability within constants
627+
(active, const_refs_to_cell, "1.51.0", Some(80384), None),
628+
626629
// -------------------------------------------------------------------------
627630
// feature-group-end: actual feature gates
628631
// -------------------------------------------------------------------------

compiler/rustc_mir/src/dataflow/framework/fmt.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub trait DebugWithContext<C>: Eq + fmt::Debug {
3333
}
3434

3535
write!(f, "\u{001f}-")?;
36-
self.fmt_with(ctxt, f)
36+
old.fmt_with(ctxt, f)
3737
}
3838
}
3939

@@ -133,6 +133,30 @@ where
133133
}
134134
}
135135

136+
impl<T, C, V> DebugWithContext<C> for rustc_index::vec::IndexVec<T, V>
137+
where
138+
T: Idx + DebugWithContext<C>,
139+
V: DebugWithContext<C> + Eq,
140+
{
141+
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142+
f.debug_set().entries(self.iter_enumerated().map(|this| DebugWithAdapter { this, ctxt })).finish()
143+
}
144+
145+
fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146+
assert_eq!(self.len(), old.len());
147+
148+
for (new, old) in self.iter_enumerated().zip(old.iter_enumerated()) {
149+
write!(f, "{:?}", DebugDiffWithAdapter {
150+
new,
151+
old,
152+
ctxt,
153+
})?;
154+
}
155+
156+
Ok(())
157+
}
158+
}
159+
136160
impl<T, C> DebugWithContext<C> for &'_ T
137161
where
138162
T: DebugWithContext<C>,
@@ -146,6 +170,39 @@ where
146170
}
147171
}
148172

173+
impl<T, C> DebugWithContext<C> for Option<T>
174+
where
175+
T: DebugWithContext<C>,
176+
{
177+
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178+
match self {
179+
None => Ok(()),
180+
Some(v) => v.fmt_with(ctxt, f),
181+
}
182+
}
183+
184+
fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185+
match (self, old) {
186+
(None, None) => Ok(()),
187+
(Some(a), Some(b)) => a.fmt_diff_with(b, ctxt, f),
188+
(Some(a), None) => {
189+
write!(f, "\u{001f}+")?;
190+
a.fmt_with(ctxt, f)
191+
}
192+
(None, Some(b)) => {
193+
write!(f, "\u{001f}-")?;
194+
b.fmt_with(ctxt, f)
195+
}
196+
}
197+
}
198+
}
199+
200+
impl<T, U, C> DebugWithContext<C> for (T, U)
201+
where
202+
T: DebugWithContext<C>,
203+
U: DebugWithContext<C>,
204+
{}
205+
149206
impl<C> DebugWithContext<C> for rustc_middle::mir::Local {}
150207
impl<C> DebugWithContext<C> for crate::dataflow::move_paths::InitIndex {}
151208

compiler/rustc_mir/src/transform/check_consts/ops.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,29 @@ impl NonConstOp for LiveDrop {
208208
}
209209
}
210210

211+
#[derive(Debug)]
212+
pub struct CellBorrowBehindRef;
213+
impl NonConstOp for CellBorrowBehindRef {
214+
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
215+
Status::Unstable(sym::const_refs_to_cell)
216+
}
217+
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
218+
feature_err(
219+
&ccx.tcx.sess.parse_sess,
220+
sym::const_refs_to_cell,
221+
span,
222+
"cannot borrow here, since the borrowed element may contain interior mutability",
223+
)
224+
}
225+
}
226+
211227
#[derive(Debug)]
212228
pub struct CellBorrow;
213229
impl NonConstOp for CellBorrow {
230+
fn importance(&self) -> DiagnosticImportance {
231+
// The problematic cases will already emit a `CellBorrowBehindRef`
232+
DiagnosticImportance::Secondary
233+
}
214234
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
215235
struct_span_err!(
216236
ccx.tcx.sess,

compiler/rustc_mir/src/transform/check_consts/qualifs.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_span::DUMMY_SP;
99
use rustc_trait_selection::traits;
1010
use rustc_index::vec::IndexVec;
1111
use rustc_index::bit_set::BitSet;
12-
use crate::dataflow::JoinSemiLattice;
12+
use crate::dataflow::{JoinSemiLattice, fmt::DebugWithContext};
1313

1414
use super::ConstCx;
1515

@@ -44,7 +44,7 @@ pub(crate) trait Qualif {
4444
/// The dataflow result type. If it's just qualified/not qualified, then
4545
/// you can just use a `()` (most qualifs do that). But if you need more state, use a
4646
/// custom enum.
47-
type Result: SetChoice = ();
47+
type Result: SetChoice + std::fmt::Debug = ();
4848
type Set: QualifsPerLocal<Self::Result> = <Self::Result as SetChoice>::Set;
4949

5050
/// Whether this `Qualif` is cleared when a local is moved from.
@@ -62,6 +62,12 @@ pub(crate) trait Qualif {
6262
/// It also determines the `Qualif`s for primitive types.
6363
fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option<Self::Result>;
6464

65+
/// Sometimes const fn calls cannot possibly contain the qualif, so we can treat function
66+
/// calls special here.
67+
fn in_any_function_call(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>, _args: Option<Self::Result>) -> Option<Self::Result> {
68+
Self::in_any_value_of_ty(cx, ty)
69+
}
70+
6571
/// Returns `true` if this `Qualif` is inherent to the given struct or enum.
6672
///
6773
/// By default, `Qualif`s propagate into ADTs in a structural way: An ADT only becomes
@@ -75,6 +81,10 @@ pub(crate) trait Qualif {
7581
adt: &'tcx AdtDef,
7682
substs: SubstsRef<'tcx>,
7783
) -> Option<Self::Result>;
84+
85+
fn in_value_behind_ref(qualif: Option<Self::Result>) -> Option<Self::Result> {
86+
qualif
87+
}
7888
}
7989

8090
pub(crate) trait SetChoice: Sized + Clone + JoinSemiLattice {
@@ -116,7 +126,7 @@ impl<T: Clone + Eq + JoinSemiLattice> QualifsPerLocal<T> for IndexVec<Local, Opt
116126
IndexVec::from_elem_n(None, n)
117127
}
118128
fn insert(&mut self, local: Local, val: T) {
119-
self[local] = Some(val);
129+
self[local].join(&Some(val));
120130
}
121131
fn remove(&mut self, local: Local) {
122132
self[local] = None;
@@ -156,6 +166,66 @@ impl Qualif for HasMutInterior {
156166
}
157167
}
158168

169+
/// Constant containing interior mutability (`UnsafeCell<T>`) behind a reference.
170+
/// This must be ruled out to make sure that evaluating the constant at compile-time
171+
/// and at *any point* during the run-time would produce the same result. In particular,
172+
/// promotion of temporaries must not change program behavior; if the promoted could be
173+
/// written to, that would be a problem.
174+
pub struct HasMutInteriorBehindRef;
175+
176+
#[derive(Clone, Eq, PartialEq, Debug)]
177+
pub enum HasMutInteriorBehindRefState {
178+
Yes,
179+
/// As long as we haven't encountered a reference yet, we use this state
180+
/// which is equivalent to the `HasMutInterior` qualif.
181+
OnlyHasMutInterior,
182+
}
183+
impl SetChoice for HasMutInteriorBehindRefState {}
184+
impl<C> DebugWithContext<C> for HasMutInteriorBehindRefState {}
185+
186+
impl JoinSemiLattice for HasMutInteriorBehindRefState {
187+
fn join(&mut self, other: &Self) -> bool {
188+
match (&self, other) {
189+
(Self::Yes, _) => false,
190+
(Self::OnlyHasMutInterior, Self::Yes) => {
191+
*self = Self::Yes;
192+
true
193+
},
194+
(Self::OnlyHasMutInterior, Self::OnlyHasMutInterior) => false,
195+
}
196+
}
197+
}
198+
199+
impl Qualif for HasMutInteriorBehindRef {
200+
const ANALYSIS_NAME: &'static str = "flow_has_mut_interior_behind_ref";
201+
type Result = HasMutInteriorBehindRefState;
202+
203+
fn in_qualifs(qualifs: &ConstQualifs) -> Option<HasMutInteriorBehindRefState> {
204+
HasMutInterior::in_qualifs(qualifs).map(|()| HasMutInteriorBehindRefState::OnlyHasMutInterior)
205+
}
206+
207+
fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option<HasMutInteriorBehindRefState> {
208+
match ty.builtin_deref(false) {
209+
None => HasMutInterior::in_any_value_of_ty(cx, ty).map(|()| HasMutInteriorBehindRefState::OnlyHasMutInterior),
210+
Some(tam) => HasMutInterior::in_any_value_of_ty(cx, tam.ty).map(|()| HasMutInteriorBehindRefState::Yes),
211+
}
212+
}
213+
214+
#[instrument(skip(cx))]
215+
fn in_any_function_call(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>, mut args: Option<Self::Result>) -> Option<Self::Result> {
216+
args.join(&HasMutInterior::in_any_value_of_ty(cx, ty).map(|()| HasMutInteriorBehindRefState::OnlyHasMutInterior));
217+
args
218+
}
219+
220+
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &'tcx AdtDef, substs: SubstsRef<'tcx>) -> Option<HasMutInteriorBehindRefState> {
221+
HasMutInterior::in_adt_inherently(cx, adt, substs).map(|()| HasMutInteriorBehindRefState::OnlyHasMutInterior)
222+
}
223+
224+
fn in_value_behind_ref(qualif: Option<HasMutInteriorBehindRefState>) -> Option<HasMutInteriorBehindRefState> {
225+
qualif.map(|_| HasMutInteriorBehindRefState::Yes)
226+
}
227+
}
228+
159229
/// Constant containing an ADT that implements `Drop`.
160230
/// This must be ruled out (a) because we cannot run `Drop` during compile-time
161231
/// as that might not be a `const fn`, and (b) because implicit promotion would
@@ -212,6 +282,7 @@ impl Qualif for CustomEq {
212282
// FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return.
213283

214284
/// Returns `true` if this `Rvalue` contains qualif `Q`.
285+
#[instrument(skip(cx, in_local), fields(Q=std::any::type_name::<Q>()))]
215286
pub(crate) fn in_rvalue<Q, F>(cx: &ConstCx<'_, 'tcx>, in_local: &mut F, rvalue: &Rvalue<'tcx>) -> Option<Q::Result>
216287
where
217288
Q: Qualif,
@@ -250,7 +321,7 @@ where
250321
}
251322
}
252323

253-
in_place::<Q, _>(cx, in_local, place.as_ref())
324+
Q::in_value_behind_ref(in_place::<Q, _>(cx, in_local, place.as_ref()))
254325
}
255326

256327
Rvalue::Aggregate(kind, operands) => {
@@ -270,6 +341,7 @@ where
270341
}
271342

272343
/// Returns `true` if this `Place` contains qualif `Q`.
344+
#[instrument(skip(cx, in_local), fields(Q=std::any::type_name::<Q>()))]
273345
pub(crate) fn in_place<Q, F>(cx: &ConstCx<'_, 'tcx>, in_local: &mut F, place: PlaceRef<'tcx>) -> Option<Q::Result>
274346
where
275347
Q: Qualif,
@@ -305,6 +377,7 @@ where
305377
}
306378

307379
/// Returns `true` if this `Operand` contains qualif `Q`.
380+
#[instrument(skip(cx, in_local), fields(Q=std::any::type_name::<Q>()))]
308381
pub(crate) fn in_operand<Q, F>(cx: &ConstCx<'_, 'tcx>, in_local: &mut F, operand: &Operand<'tcx>) -> Option<Q::Result>
309382
where
310383
Q: Qualif,

compiler/rustc_mir/src/transform/check_consts/resolver.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_middle::mir::{self, BasicBlock, Location};
88
use std::marker::PhantomData;
99

1010
use super::{qualifs, ConstCx, Qualif};
11-
use crate::dataflow;
11+
use crate::dataflow::{self, JoinSemiLattice};
1212
use super::qualifs::QualifsPerLocal;
1313

1414
/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
@@ -43,6 +43,7 @@ where
4343
}
4444
}
4545

46+
#[instrument(skip(self))]
4647
fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, value: Option<Q::Result>) {
4748
debug_assert!(!place.is_indirect());
4849

@@ -67,13 +68,25 @@ where
6768
&mut self,
6869
_block: BasicBlock,
6970
_func: &mir::Operand<'tcx>,
70-
_args: &[mir::Operand<'tcx>],
71+
args: &[mir::Operand<'tcx>],
7172
return_place: mir::Place<'tcx>,
7273
) {
7374
// We cannot reason about another function's internals, so use conservative type-based
7475
// qualification for the result of a function call.
7576
let return_ty = return_place.ty(self.ccx.body, self.ccx.tcx).ty;
76-
let qualif = Q::in_any_value_of_ty(self.ccx, return_ty);
77+
78+
// Though some qualifs merge the call arguments' qualifs into the result qualif.
79+
let mut args_qualif = None;
80+
for arg in args {
81+
let arg = qualifs::in_operand::<Q, _>(
82+
self.ccx,
83+
&mut |l| self.qualifs_per_local.get(l),
84+
arg,
85+
);
86+
args_qualif.join(&arg);
87+
}
88+
89+
let qualif = Q::in_any_function_call(self.ccx, return_ty, args_qualif);
7790

7891
if !return_place.is_indirect() {
7992
self.assign_qualif_direct(&return_place, qualif);

0 commit comments

Comments
 (0)