Skip to content

Commit 8f86833

Browse files
committed
Allow constants to refer to statics
This relaxes the dynamic and validity checks so that constants can construct pointers to all statics and read from immutable statics.
1 parent d6b6488 commit 8f86833

17 files changed

+167
-132
lines changed

src/librustc_mir/const_eval/eval_queries.rs

+33-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter, MemoryExtra};
22
use crate::interpret::eval_nullary_intrinsic;
33
use crate::interpret::{
4-
intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, Immediate, InternKind,
5-
InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst, RefTracking, Scalar,
6-
ScalarMaybeUndef, StackPopCleanup,
4+
intern_const_alloc_recursive, AllocId, Allocation, ConstValue, GlobalAlloc, GlobalId,
5+
Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst,
6+
RefTracking, Scalar, ScalarMaybeUndef, StackPopCleanup,
77
};
88
use rustc_hir::def::DefKind;
99
use rustc_middle::mir;
@@ -94,10 +94,13 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>(
9494
)
9595
}
9696

97-
pub(super) fn op_to_const<'tcx>(
97+
#[derive(Debug)]
98+
pub(super) struct RefersToStatic;
99+
100+
pub(super) fn try_op_to_const<'tcx>(
98101
ecx: &CompileTimeEvalContext<'_, 'tcx>,
99102
op: OpTy<'tcx>,
100-
) -> ConstValue<'tcx> {
103+
) -> Result<ConstValue<'tcx>, RefersToStatic> {
101104
// We do not have value optimizations for everything.
102105
// Only scalars and slices, since they are very common.
103106
// Note that further down we turn scalars of undefined bits back to `ByRef`. These can result
@@ -128,10 +131,16 @@ pub(super) fn op_to_const<'tcx>(
128131
op.try_as_mplace(ecx)
129132
};
130133

134+
let alloc_map = ecx.tcx.alloc_map.lock();
135+
let try_unwrap_memory = |alloc_id: AllocId| match alloc_map.get(alloc_id) {
136+
Some(GlobalAlloc::Memory(mem)) => Ok(mem),
137+
Some(GlobalAlloc::Static(_)) => Err(RefersToStatic),
138+
_ => bug!("expected allocation ID {} to point to memory", alloc_id),
139+
};
131140
let to_const_value = |mplace: MPlaceTy<'_>| match mplace.ptr {
132141
Scalar::Ptr(ptr) => {
133-
let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id);
134-
ConstValue::ByRef { alloc, offset: ptr.offset }
142+
let alloc = try_unwrap_memory(ptr.alloc_id)?;
143+
Ok(ConstValue::ByRef { alloc, offset: ptr.offset })
135144
}
136145
Scalar::Raw { data, .. } => {
137146
assert!(mplace.layout.is_zst());
@@ -141,22 +150,20 @@ pub(super) fn op_to_const<'tcx>(
141150
"this MPlaceTy must come from `try_as_mplace` being used on a zst, so we know what
142151
value this integer address must have",
143152
);
144-
ConstValue::Scalar(Scalar::zst())
153+
Ok(ConstValue::Scalar(Scalar::zst()))
145154
}
146155
};
147156
match immediate {
148-
Ok(mplace) => to_const_value(mplace),
157+
Ok(mplace) => Ok(to_const_value(mplace)?),
149158
// see comment on `let try_as_immediate` above
150159
Err(imm) => match *imm {
151160
Immediate::Scalar(x) => match x {
152-
ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s),
153-
ScalarMaybeUndef::Undef => to_const_value(op.assert_mem_place(ecx)),
161+
ScalarMaybeUndef::Scalar(s) => Ok(ConstValue::Scalar(s)),
162+
ScalarMaybeUndef::Undef => Ok(to_const_value(op.assert_mem_place(ecx))?),
154163
},
155164
Immediate::ScalarPair(a, b) => {
156165
let (data, start) = match a.not_undef().unwrap() {
157-
Scalar::Ptr(ptr) => {
158-
(ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes())
159-
}
166+
Scalar::Ptr(ptr) => (try_unwrap_memory(ptr.alloc_id)?, ptr.offset.bytes()),
160167
Scalar::Raw { .. } => (
161168
ecx.tcx
162169
.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])),
@@ -166,7 +173,7 @@ pub(super) fn op_to_const<'tcx>(
166173
let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap();
167174
let start = start.try_into().unwrap();
168175
let len: usize = len.try_into().unwrap();
169-
ConstValue::Slice { data, start, end: start + len }
176+
Ok(ConstValue::Slice { data, start, end: start + len })
170177
}
171178
},
172179
}
@@ -198,17 +205,20 @@ fn validate_and_turn_into_const<'tcx>(
198205
}
199206
}
200207
// Now that we validated, turn this into a proper constant.
201-
// Statics/promoteds are always `ByRef`, for the rest `op_to_const` decides
202-
// whether they become immediates.
203-
if is_static || cid.promoted.is_some() {
208+
// Statics/promoteds are always `ByRef`, for the rest `try_op_to_const`
209+
// decides whether they become immediates.
210+
let value = if !is_static && !cid.promoted.is_some() {
211+
try_op_to_const(&ecx, mplace.into())
212+
} else {
213+
Err(RefersToStatic)
214+
};
215+
Ok(value.unwrap_or_else(|RefersToStatic| {
204216
let ptr = mplace.ptr.assert_ptr();
205-
Ok(ConstValue::ByRef {
217+
ConstValue::ByRef {
206218
alloc: ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id),
207219
offset: ptr.offset,
208-
})
209-
} else {
210-
Ok(op_to_const(&ecx, mplace.into()))
211-
}
220+
}
221+
}))
212222
})();
213223

214224
val.map_err(|error| {

src/librustc_mir/const_eval/machine.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::hash::Hash;
88
use rustc_data_structures::fx::FxHashMap;
99

1010
use rustc_ast::ast::Mutability;
11-
use rustc_hir::def_id::DefId;
1211
use rustc_middle::mir::AssertMessage;
1312
use rustc_span::symbol::Symbol;
1413

@@ -353,7 +352,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter {
353352
memory_extra: &MemoryExtra,
354353
alloc_id: AllocId,
355354
allocation: &Allocation,
356-
static_def_id: Option<DefId>,
357355
is_write: bool,
358356
) -> InterpResult<'tcx> {
359357
if is_write {
@@ -368,16 +366,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter {
368366
if memory_extra.can_access_statics {
369367
// Machine configuration allows us read from anything (e.g., `static` initializer).
370368
Ok(())
371-
} else if static_def_id.is_some() {
372-
// Machine configuration does not allow us to read statics
369+
} else if allocation.mutability != Mutability::Not {
370+
// Machine configuration does not allow us to read mutable statics
373371
// (e.g., `const` initializer).
372+
// This is unsound because the content of this allocation may be different now and
373+
// at run-time, so if we permit reading it now we might return the wrong value.
374374
Err(ConstEvalErrKind::ConstAccessesStatic.into())
375375
} else {
376376
// Immutable global, this read is fine.
377-
// But make sure we never accept a read from something mutable, that would be
378-
// unsound. The reason is that as the content of this allocation may be different
379-
// now and at run-time, so if we permit reading now we might return the wrong value.
380-
assert_eq!(allocation.mutability, Mutability::Not);
381377
Ok(())
382378
}
383379
}

src/librustc_mir/const_eval/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub(crate) fn const_field<'tcx>(
4242
let field = ecx.operand_field(down, field.index()).unwrap();
4343
// and finally move back to the const world, always normalizing because
4444
// this is not called for statics.
45-
op_to_const(&ecx, field)
45+
try_op_to_const(&ecx, field).unwrap()
4646
}
4747

4848
pub(crate) fn const_caller_location(
@@ -81,7 +81,7 @@ pub(crate) fn destructure_const<'tcx>(
8181
let down = ecx.operand_downcast(op, variant).unwrap();
8282
let fields_iter = (0..field_count).map(|i| {
8383
let field_op = ecx.operand_field(down, i).unwrap();
84-
let val = op_to_const(&ecx, field_op);
84+
let val = try_op_to_const(&ecx, field_op).unwrap();
8585
ty::Const::from_value(tcx, val, field_op.layout.ty)
8686
});
8787
let fields = tcx.arena.alloc_from_iter(fields_iter);

src/librustc_mir/interpret/machine.rs

-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::hash::Hash;
77

88
use rustc_middle::mir;
99
use rustc_middle::ty::{self, Ty};
10-
use rustc_span::def_id::DefId;
1110

1211
use super::{
1312
AllocId, Allocation, AllocationExtra, Frame, ImmTy, InterpCx, InterpResult, Memory, MemoryKind,
@@ -207,13 +206,11 @@ pub trait Machine<'mir, 'tcx>: Sized {
207206
}
208207

209208
/// Called before a global allocation is accessed.
210-
/// `def_id` is `Some` if this is the "lazy" allocation of a static.
211209
#[inline]
212210
fn before_access_global(
213211
_memory_extra: &Self::MemoryExtra,
214212
_alloc_id: AllocId,
215213
_allocation: &Allocation,
216-
_static_def_id: Option<DefId>,
217214
_is_write: bool,
218215
) -> InterpResult<'tcx> {
219216
Ok(())

src/librustc_mir/interpret/memory.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -430,11 +430,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
430430
is_write: bool,
431431
) -> InterpResult<'tcx, Cow<'tcx, Allocation<M::PointerTag, M::AllocExtra>>> {
432432
let alloc = tcx.alloc_map.lock().get(id);
433-
let (alloc, def_id) = match alloc {
434-
Some(GlobalAlloc::Memory(mem)) => {
435-
// Memory of a constant or promoted or anonymous memory referenced by a static.
436-
(mem, None)
437-
}
433+
let alloc = match alloc {
434+
// Memory of a constant or promoted or anonymous memory referenced by a static.
435+
Some(GlobalAlloc::Memory(mem)) => mem,
438436
Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
439437
None => throw_ub!(PointerUseAfterFree(id)),
440438
Some(GlobalAlloc::Static(def_id)) => {
@@ -466,12 +464,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
466464
})?;
467465
// Make sure we use the ID of the resolved memory, not the lazy one!
468466
let id = raw_const.alloc_id;
469-
let allocation = tcx.alloc_map.lock().unwrap_memory(id);
470-
471-
(allocation, Some(def_id))
467+
tcx.alloc_map.lock().unwrap_memory(id)
472468
}
473469
};
474-
M::before_access_global(memory_extra, id, alloc, def_id, is_write)?;
470+
M::before_access_global(memory_extra, id, alloc, is_write)?;
475471
let alloc = Cow::Borrowed(alloc);
476472
// We got tcx memory. Let the machine initialize its "extra" stuff.
477473
let (alloc, tag) = M::init_allocation_extra(

src/librustc_mir/interpret/validity.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,11 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, 'tcx, M
411411
if !did.is_local() || self.ecx.tcx.is_foreign_item(did) {
412412
return Ok(());
413413
}
414+
// FIXME: Statics referenced from consts are skipped.
415+
// This avoids spurious "const accesses static" errors
416+
// unrelated to validity, but is similarly unsound.
414417
if !self.may_ref_to_static && self.ecx.tcx.is_static(did) {
415-
throw_validation_failure!(
416-
format_args!("a {} pointing to a static variable", kind),
417-
self.path
418-
);
418+
return Ok(());
419419
}
420420
}
421421
}

src/librustc_mir/transform/const_prop.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout};
2323
use rustc_middle::ty::subst::{InternalSubsts, Subst};
2424
use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
2525
use rustc_session::lint;
26-
use rustc_span::{def_id::DefId, Span};
26+
use rustc_span::Span;
2727
use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout};
2828
use rustc_trait_selection::traits;
2929

@@ -274,7 +274,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine {
274274
_memory_extra: &(),
275275
_alloc_id: AllocId,
276276
allocation: &Allocation<Self::PointerTag, Self::AllocExtra>,
277-
_static_def_id: Option<DefId>,
278277
is_write: bool,
279278
) -> InterpResult<'tcx> {
280279
if is_write {

src/test/ui/consts/const-points-to-static.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
// check-pass
12
// compile-flags: -Zunleash-the-miri-inside-of-you
23

34
#![allow(dead_code)]
45

56
const TEST: &u8 = &MY_STATIC;
67
//~^ skipping const checks
7-
//~| it is undefined behavior to use this value
88

99
static MY_STATIC: u8 = 4;
1010

Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
warning: skipping const checks
2-
--> $DIR/const-points-to-static.rs:5:20
2+
--> $DIR/const-points-to-static.rs:6:20
33
|
44
LL | const TEST: &u8 = &MY_STATIC;
55
| ^^^^^^^^^
66

7-
error[E0080]: it is undefined behavior to use this value
8-
--> $DIR/const-points-to-static.rs:5:1
9-
|
10-
LL | const TEST: &u8 = &MY_STATIC;
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a reference pointing to a static variable
12-
|
13-
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
14-
15-
error: aborting due to previous error; 1 warning emitted
7+
warning: 1 warning emitted
168

17-
For more information about this error, try `rustc --explain E0080`.

src/test/ui/consts/const-prop-read-static-in-const.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
// check-pass
12
// compile-flags: -Zunleash-the-miri-inside-of-you
23

34
#![allow(dead_code)]
45

5-
const TEST: u8 = MY_STATIC; //~ ERROR any use of this value will cause an error
6+
const TEST: u8 = MY_STATIC;
67
//~^ skipping const checks
78

89
static MY_STATIC: u8 = 4;
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
warning: skipping const checks
2-
--> $DIR/const-prop-read-static-in-const.rs:5:18
2+
--> $DIR/const-prop-read-static-in-const.rs:6:18
33
|
44
LL | const TEST: u8 = MY_STATIC;
55
| ^^^^^^^^^
66

7-
error: any use of this value will cause an error
8-
--> $DIR/const-prop-read-static-in-const.rs:5:18
9-
|
10-
LL | const TEST: u8 = MY_STATIC;
11-
| -----------------^^^^^^^^^-
12-
| |
13-
| constant accesses static
14-
|
15-
= note: `#[deny(const_err)]` on by default
16-
17-
error: aborting due to previous error; 1 warning emitted
7+
warning: 1 warning emitted
188

src/test/ui/consts/miri_unleashed/const_refers_to_static.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// check-pass
12
// compile-flags: -Zunleash-the-miri-inside-of-you
23
#![warn(const_err)]
34

@@ -6,12 +7,31 @@
67
use std::sync::atomic::AtomicUsize;
78
use std::sync::atomic::Ordering;
89

9-
const REF_INTERIOR_MUT: &usize = { //~ ERROR undefined behavior to use this value
10+
// Dynamically okay; does not touch any mutable static data:
11+
12+
const READ_IMMUT: &usize = {
13+
static FOO: usize = 0;
14+
&FOO
15+
//~^ WARN skipping const checks
16+
};
17+
18+
const DEREF_IMMUT: usize = *READ_IMMUT;
19+
20+
const REF_INTERIOR_MUT: &usize = {
1021
static FOO: AtomicUsize = AtomicUsize::new(0);
1122
unsafe { &*(&FOO as *const _ as *const usize) }
1223
//~^ WARN skipping const checks
1324
};
1425

26+
extern { static EXTERN: usize; }
27+
const REF_EXTERN: &usize = unsafe { &EXTERN };
28+
//~^ WARN skipping const checks
29+
30+
// Not okay; uses of these consts would read or write mutable static data:
31+
32+
const DEREF_INTERIOR_MUT: usize = *REF_INTERIOR_MUT;
33+
//~^ WARN any use of this value will cause an error
34+
1535
const MUTATE_INTERIOR_MUT: usize = {
1636
static FOO: AtomicUsize = AtomicUsize::new(0);
1737
FOO.fetch_add(1, Ordering::Relaxed) //~ WARN any use of this value will cause an error
@@ -30,10 +50,7 @@ const READ_MUT: u32 = unsafe { MUTABLE }; //~ WARN any use of this value will ca
3050
//~^ WARN skipping const checks
3151
//~| WARN skipping const checks
3252

33-
// ok some day perhaps
34-
const READ_IMMUT: &usize = { //~ ERROR it is undefined behavior to use this value
35-
static FOO: usize = 0;
36-
&FOO
37-
//~^ WARN skipping const checks
38-
};
53+
const READ_EXTERN: usize = unsafe { EXTERN }; //~ WARN any use of this value will cause an error
54+
//~^ WARN skipping const checks
55+
3956
fn main() {}

0 commit comments

Comments
 (0)