Skip to content

Commit 6265a95

Browse files
committed
Auto merge of rust-lang#119044 - RalfJung:intern-without-types, r=oli-obk
const-eval interning: get rid of type-driven traversal This entirely replaces our const-eval interner, i.e. the code that takes the final result of a constant evaluation from the local memory of the const-eval machine to the global `tcx` memory. The main goal of this change is to ensure that we can detect mutable references that sneak into this final value -- this is something we want to reject for `static` and `const`, and while const-checking performs some static analysis to ensure this, I would be much more comfortable stabilizing const_mut_refs if we had a dynamic check that sanitizes the final value. (This is generally the approach we have been using on const-eval: do a static check to give nice errors upfront, and then do a dynamic check to be really sure that the properties we need for soundness, actually hold.) We can do this now that rust-lang#118324 landed and each pointer comes with a bit (completely independent of its type) storing whether mutation is permitted through this pointer or not. The new interner is a lot simpler than the old one: previously we did a complete type-driven traversal to determine the mutability of all memory we see, and then a second pass to intern any leftover raw pointers. The new interner simply recursively traverses the allocation holding the final result, and all allocations reachable from it (which can be determined from the raw bytes of the result, without knowing anything about types), and ensures they all get interned. The initial allocation is interned as immutable for `const` and pomoted and non-interior-mutable `static`; all other allocations are interned as immutable for `static`, `const`, and promoted. The main subtlety is justifying that those inner allocations may indeed be interned immutably, i.e., that mutating them later would anyway already be UB: - for promoteds, we rely on the analysis that does promotion to ensure that this is sound. - for `const` and `static`, we check that all pointers in the final result that point to things that are new (i.e., part of this const evaluation) are immutable, i.e., were created via `&<expr>` at a non-interior-mutable type. Mutation through immutable pointers is UB so we are free to intern that memory as immutable. Interning raises an error if it encounters a dangling pointer or a mutable pointer that violates the above rules. I also extended our type-driven const validity checks to ensure that `&mut T` in the final value of a const points to mutable memory, at least if `T` is not zero-sized. This catches cases of people turning `&i32` into `&mut i32` (which would still be considered a read-only pointer). Similarly, when these checks encounter an `UnsafeCell`, they are checking that it lives in mutable memory. (Both of these only traverse the newly created values; if those point to other consts/promoteds, the check stops there. But that's okay, we don't have to catch all the UB.) I co-developed this with the stricter interner changes but I can split it out into a separate PR if you prefer. This PR does have the immediate effect of allowing some new code on stable, for instance: ```rust const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _; ``` Previously that code got rejected since the type-based interner didn't know what to do with that pointer. It's a raw pointer, we cannot trust its type. The new interner does not care about types so it sees no issue with this code; there's an immutable pointer pointing to some read-only memory (storing a `Vec<i32>`), all is good. Accepting this code pretty much commits us to non-type-based interning, but I think that's the better strategy anyway. This PR also leads to slightly worse error messages when the final value of a const contains a dangling reference. Previously we would complete interning and then the type-based validation would detect this dangling reference and show a nice error saying where in the value (i.e., in which field) the dangling reference is located. However, the new interner cannot distinguish dangling references from dangling raw pointers, so it must throw an error when it encounters either of them. It doesn't have an understanding of the value structure so all it can say is "somewhere in this constant there's a dangling pointer". (Later parts of the compiler don't like dangling pointers/references so we have to reject them either during interning or during validation.) This could potentially be improved by doing validation before interning, but that's a larger change that I have not attempted yet. (It's also subtle since we do want validation to use the final mutability bits of all involved allocations, and currently it is interning that marks a bunch of allocations as immutable -- that would have to still happen before validation.) `@rust-lang/wg-const-eval` I hope you are okay with this plan. :) `@rust-lang/lang` paging you in since this accepts new code on stable as explained above. Please let me know if you think FCP is necessary.
2 parents 0e42435 + 2ab85e4 commit 6265a95

File tree

57 files changed

+1110
-707
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1110
-707
lines changed

compiler/rustc_const_eval/messages.ftl

+22-16
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ const_eval_dangling_int_pointer =
4646
{$bad_pointer_message}: {$pointer} is a dangling pointer (it has no provenance)
4747
const_eval_dangling_null_pointer =
4848
{$bad_pointer_message}: null pointer is a dangling pointer (it has no provenance)
49-
const_eval_dangling_ptr_in_final = encountered dangling pointer in final constant
5049
50+
const_eval_dangling_ptr_in_final = encountered dangling pointer in final value of {const_eval_intern_kind}
5151
const_eval_dead_local =
5252
accessing a dead local variable
5353
const_eval_dealloc_immutable =
@@ -134,6 +134,14 @@ const_eval_interior_mutable_data_refer =
134134
This would make multiple uses of a constant to be able to see different values and allow circumventing
135135
the `Send` and `Sync` requirements for shared mutable data, which is unsound.
136136
137+
const_eval_intern_kind = {$kind ->
138+
[static] static
139+
[static_mut] mutable static
140+
[const] constant
141+
[promoted] promoted
142+
*[other] {""}
143+
}
144+
137145
const_eval_invalid_align =
138146
align has to be a power of 2
139147
@@ -205,6 +213,8 @@ const_eval_modified_global =
205213
const_eval_mut_deref =
206214
mutation through a reference is not allowed in {const_eval_const_context}s
207215
216+
const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind}
217+
208218
const_eval_non_const_fmt_macro_call =
209219
cannot call non-const formatting macro in {const_eval_const_context}s
210220
@@ -327,7 +337,7 @@ const_eval_too_many_caller_args =
327337
328338
const_eval_transient_mut_borrow = mutable references are not allowed in {const_eval_const_context}s
329339
330-
const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {const_eval_const_context}s
340+
const_eval_transient_mut_raw = raw mutable pointers are not allowed in {const_eval_const_context}s
331341
332342
const_eval_try_block_from_output_non_const =
333343
`try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s
@@ -341,21 +351,21 @@ const_eval_unallowed_heap_allocations =
341351
342352
const_eval_unallowed_inline_asm =
343353
inline assembly is not allowed in {const_eval_const_context}s
344-
const_eval_unallowed_mutable_refs =
345-
mutable references are not allowed in the final value of {const_eval_const_context}s
354+
const_eval_unallowed_mutable_raw =
355+
raw mutable pointers are not allowed in the final value of {const_eval_const_context}s
346356
.teach_note =
357+
References in statics and constants may only refer to immutable values.
358+
359+
347360
Statics are shared everywhere, and if they refer to mutable data one might violate memory
348361
safety since holding multiple mutable references to shared data is not allowed.
349362
350363
351364
If you really want global mutable state, try using static mut or a global UnsafeCell.
352365
353-
const_eval_unallowed_mutable_refs_raw =
354-
raw mutable references are not allowed in the final value of {const_eval_const_context}s
366+
const_eval_unallowed_mutable_refs =
367+
mutable references are not allowed in the final value of {const_eval_const_context}s
355368
.teach_note =
356-
References in statics and constants may only refer to immutable values.
357-
358-
359369
Statics are shared everywhere, and if they refer to mutable data one might violate memory
360370
safety since holding multiple mutable references to shared data is not allowed.
361371
@@ -392,9 +402,6 @@ const_eval_unstable_in_stable =
392402
.unstable_sugg = if it is not part of the public API, make this function unstably const
393403
.bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks
394404
395-
const_eval_unsupported_untyped_pointer = unsupported untyped pointer in constant
396-
.note = memory only reachable via raw pointers is not supported
397-
398405
const_eval_unterminated_c_string =
399406
reading a null-terminated string starting at {$pointer} with no null found before end of allocation
400407
@@ -406,7 +413,6 @@ const_eval_upcast_mismatch =
406413
407414
## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`.
408415
## (We'd love to sort this differently to make that more clear but tidy won't let us...)
409-
const_eval_validation_box_to_mut = {$front_matter}: encountered a box pointing to mutable memory in a constant
410416
const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant
411417
const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty}
412418
const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance)
@@ -441,7 +447,8 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu
441447
const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
442448
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
443449
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
444-
const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const`
450+
const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` or `static`
451+
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
445452
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
446453
const_eval_validation_null_box = {$front_matter}: encountered a null box
447454
const_eval_validation_null_fn_ptr = {$front_matter}: encountered a null function pointer
@@ -451,15 +458,14 @@ const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but
451458
const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers
452459
const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected}
453460
const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range}
454-
const_eval_validation_ref_to_mut = {$front_matter}: encountered a reference pointing to mutable memory in a constant
455461
const_eval_validation_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant
456462
const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty}
457463
const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes})
458464
const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes})
459465
const_eval_validation_uninhabited_enum_variant = {$front_matter}: encountered an uninhabited enum variant
460466
const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}`
461467
const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected}
462-
const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const`
468+
const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in read-only memory
463469
464470
const_eval_write_through_immutable_pointer =
465471
writing through a pointer that was derived from a shared (immutable) reference

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+19-13
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
293293
cid: GlobalId<'tcx>,
294294
is_static: bool,
295295
) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> {
296+
// `is_static` just means "in static", it could still be a promoted!
297+
debug_assert_eq!(is_static, ecx.tcx.static_mutability(cid.instance.def_id()).is_some());
298+
296299
let res = ecx.load_mir(cid.instance.def, cid.promoted);
297300
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body)) {
298301
Err(error) => {
@@ -330,8 +333,7 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
330333
Ok(mplace) => {
331334
// Since evaluation had no errors, validate the resulting constant.
332335
// This is a separate `try` block to provide more targeted error reporting.
333-
let validation =
334-
const_validate_mplace(&ecx, &mplace, is_static, cid.promoted.is_some());
336+
let validation = const_validate_mplace(&ecx, &mplace, cid);
335337

336338
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
337339

@@ -350,22 +352,26 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
350352
pub fn const_validate_mplace<'mir, 'tcx>(
351353
ecx: &InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
352354
mplace: &MPlaceTy<'tcx>,
353-
is_static: bool,
354-
is_promoted: bool,
355+
cid: GlobalId<'tcx>,
355356
) -> InterpResult<'tcx> {
356357
let mut ref_tracking = RefTracking::new(mplace.clone());
357358
let mut inner = false;
358359
while let Some((mplace, path)) = ref_tracking.todo.pop() {
359-
let mode = if is_static {
360-
if is_promoted {
361-
// Promoteds in statics are allowed to point to statics.
362-
CtfeValidationMode::Const { inner, allow_static_ptrs: true }
363-
} else {
364-
// a `static`
365-
CtfeValidationMode::Regular
360+
let mode = match ecx.tcx.static_mutability(cid.instance.def_id()) {
361+
Some(_) if cid.promoted.is_some() => {
362+
// Promoteds in statics are consts that re allowed to point to statics.
363+
CtfeValidationMode::Const {
364+
allow_immutable_unsafe_cell: false,
365+
allow_static_ptrs: true,
366+
}
367+
}
368+
Some(mutbl) => CtfeValidationMode::Static { mutbl }, // a `static`
369+
None => {
370+
// In normal `const` (not promoted), the outermost allocation is always only copied,
371+
// so having `UnsafeCell` in there is okay despite them being in immutable memory.
372+
let allow_immutable_unsafe_cell = cid.promoted.is_none() && !inner;
373+
CtfeValidationMode::Const { allow_immutable_unsafe_cell, allow_static_ptrs: false }
366374
}
367-
} else {
368-
CtfeValidationMode::Const { inner, allow_static_ptrs: false }
369375
};
370376
ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?;
371377
inner = true;

compiler/rustc_const_eval/src/const_eval/machine.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
723723
&& ty.is_freeze(*ecx.tcx, ecx.param_env)
724724
{
725725
let place = ecx.ref_to_mplace(val)?;
726-
let new_place = place.map_provenance(|p| p.map(CtfeProvenance::as_immutable));
726+
let new_place = place.map_provenance(CtfeProvenance::as_immutable);
727727
Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
728728
} else {
729729
Ok(val.clone())

compiler/rustc_const_eval/src/errors.rs

+33-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::borrow::Cow;
2+
13
use rustc_errors::{
24
DiagCtxt, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee,
35
IntoDiagnostic, Level,
@@ -13,12 +15,24 @@ use rustc_middle::ty::{self, Ty};
1315
use rustc_span::Span;
1416
use rustc_target::abi::call::AdjustForForeignAbiError;
1517
use rustc_target::abi::{Size, WrappingRange};
18+
use rustc_type_ir::Mutability;
19+
20+
use crate::interpret::InternKind;
1621

1722
#[derive(Diagnostic)]
1823
#[diag(const_eval_dangling_ptr_in_final)]
1924
pub(crate) struct DanglingPtrInFinal {
2025
#[primary_span]
2126
pub span: Span,
27+
pub kind: InternKind,
28+
}
29+
30+
#[derive(Diagnostic)]
31+
#[diag(const_eval_mutable_ptr_in_final)]
32+
pub(crate) struct MutablePtrInFinal {
33+
#[primary_span]
34+
pub span: Span,
35+
pub kind: InternKind,
2236
}
2337

2438
#[derive(Diagnostic)]
@@ -100,8 +114,8 @@ pub(crate) struct TransientMutBorrowErr {
100114
}
101115

102116
#[derive(Diagnostic)]
103-
#[diag(const_eval_transient_mut_borrow_raw, code = "E0658")]
104-
pub(crate) struct TransientMutBorrowErrRaw {
117+
#[diag(const_eval_transient_mut_raw, code = "E0658")]
118+
pub(crate) struct TransientMutRawErr {
105119
#[primary_span]
106120
pub span: Span,
107121
pub kind: ConstContext,
@@ -142,8 +156,8 @@ pub(crate) struct UnallowedMutableRefs {
142156
}
143157

144158
#[derive(Diagnostic)]
145-
#[diag(const_eval_unallowed_mutable_refs_raw, code = "E0764")]
146-
pub(crate) struct UnallowedMutableRefsRaw {
159+
#[diag(const_eval_unallowed_mutable_raw, code = "E0764")]
160+
pub(crate) struct UnallowedMutableRaw {
147161
#[primary_span]
148162
pub span: Span,
149163
pub kind: ConstContext,
@@ -194,14 +208,6 @@ pub(crate) struct UnallowedInlineAsm {
194208
pub kind: ConstContext,
195209
}
196210

197-
#[derive(Diagnostic)]
198-
#[diag(const_eval_unsupported_untyped_pointer)]
199-
#[note]
200-
pub(crate) struct UnsupportedUntypedPointer {
201-
#[primary_span]
202-
pub span: Span,
203-
}
204-
205211
#[derive(Diagnostic)]
206212
#[diag(const_eval_interior_mutable_data_refer, code = "E0492")]
207213
pub(crate) struct InteriorMutableDataRefer {
@@ -615,18 +621,16 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
615621
PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static,
616622
PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_static,
617623

618-
PtrToMut { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_mut,
619-
PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_mut,
620-
621624
PointerAsInt { .. } => const_eval_validation_pointer_as_int,
622625
PartialPointer => const_eval_validation_partial_pointer,
623626
MutableRefInConst => const_eval_validation_mutable_ref_in_const,
627+
MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable,
624628
NullFnPtr => const_eval_validation_null_fn_ptr,
625629
NeverVal => const_eval_validation_never_val,
626630
NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range,
627631
PtrOutOfRange { .. } => const_eval_validation_ptr_out_of_range,
628632
OutOfRange { .. } => const_eval_validation_out_of_range,
629-
UnsafeCell => const_eval_validation_unsafe_cell,
633+
UnsafeCellInImmutable => const_eval_validation_unsafe_cell,
630634
UninhabitedVal { .. } => const_eval_validation_uninhabited_val,
631635
InvalidEnumTag { .. } => const_eval_validation_invalid_enum_tag,
632636
UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant,
@@ -772,11 +776,11 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
772776
}
773777
NullPtr { .. }
774778
| PtrToStatic { .. }
775-
| PtrToMut { .. }
776779
| MutableRefInConst
780+
| MutableRefToImmutable
777781
| NullFnPtr
778782
| NeverVal
779-
| UnsafeCell
783+
| UnsafeCellInImmutable
780784
| InvalidMetaSliceTooLarge { .. }
781785
| InvalidMetaTooLarge { .. }
782786
| DanglingPtrUseAfterFree { .. }
@@ -905,3 +909,14 @@ impl ReportErrorExt for ResourceExhaustionInfo {
905909
}
906910
fn add_args<G: EmissionGuarantee>(self, _: &DiagCtxt, _: &mut DiagnosticBuilder<'_, G>) {}
907911
}
912+
913+
impl rustc_errors::IntoDiagnosticArg for InternKind {
914+
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
915+
DiagnosticArgValue::Str(Cow::Borrowed(match self {
916+
InternKind::Static(Mutability::Not) => "static",
917+
InternKind::Static(Mutability::Mut) => "static_mut",
918+
InternKind::Constant => "const",
919+
InternKind::Promoted => "promoted",
920+
}))
921+
}
922+
}

0 commit comments

Comments
 (0)