Skip to content

Commit 0995420

Browse files
committed
add const_leak and reject interning non-leaked const_allocate ptrs
1 parent 8df4a58 commit 0995420

File tree

17 files changed

+169
-18
lines changed

17 files changed

+169
-18
lines changed

compiler/rustc_const_eval/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ const_eval_const_context = {$kind ->
5656
*[other] {""}
5757
}
5858
59+
const_eval_const_heap_ptr_in_final = encountered `const_allocate` pointer in final value that was not leaked
60+
.note = use `const_leak` to leak allocated pointers before returning
61+
5962
const_eval_copy_nonoverlapping_overlapping =
6063
`copy_nonoverlapping` called on overlapping ranges
6164
@@ -338,6 +341,7 @@ const_eval_realloc_or_alloc_with_offset =
338341
{$kind ->
339342
[dealloc] deallocating
340343
[realloc] reallocating
344+
[leak] leaking
341345
*[other] {""}
342346
} {$ptr} which does not point to the beginning of an object
343347

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
9393
// Since evaluation had no errors, validate the resulting constant.
9494
const_validate_mplace(ecx, &ret, cid)?;
9595

96-
// Only report this after validation, as validaiton produces much better diagnostics.
96+
// Only report this after validation, as validation produces much better diagnostics.
9797
// FIXME: ensure validation always reports this and stop making interning care about it.
9898

9999
match intern_result {

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,14 @@ pub type CompileTimeInterpCx<'tcx> = InterpCx<'tcx, CompileTimeMachine<'tcx>>;
170170
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
171171
pub enum MemoryKind {
172172
Heap,
173+
HeapLeaked,
173174
}
174175

175176
impl fmt::Display for MemoryKind {
176177
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177178
match self {
178179
MemoryKind::Heap => write!(f, "heap allocation"),
180+
MemoryKind::HeapLeaked => write!(f, "leaked heap allocation"),
179181
}
180182
}
181183
}
@@ -185,6 +187,17 @@ impl interpret::MayLeak for MemoryKind {
185187
fn may_leak(self) -> bool {
186188
match self {
187189
MemoryKind::Heap => false,
190+
MemoryKind::HeapLeaked => true,
191+
}
192+
}
193+
}
194+
195+
impl interpret::IsConstHeap for MemoryKind {
196+
#[inline(always)]
197+
fn is_const_heap(&self) -> bool {
198+
match self {
199+
MemoryKind::Heap => true,
200+
MemoryKind::HeapLeaked => false,
188201
}
189202
}
190203
}
@@ -197,6 +210,13 @@ impl interpret::MayLeak for ! {
197210
}
198211
}
199212

213+
impl interpret::IsConstHeap for ! {
214+
#[inline(always)]
215+
fn is_const_heap(&self) -> bool {
216+
*self
217+
}
218+
}
219+
200220
impl<'tcx> CompileTimeInterpCx<'tcx> {
201221
fn location_triple_for_span(&self, span: Span) -> (Symbol, u32, u32) {
202222
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);
@@ -457,6 +477,46 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
457477
)?;
458478
}
459479
}
480+
481+
sym::const_leak => {
482+
let ptr = ecx.read_pointer(&args[0])?;
483+
let size = ecx.read_scalar(&args[1])?.to_target_usize(ecx)?;
484+
let align = ecx.read_scalar(&args[2])?.to_target_usize(ecx)?;
485+
486+
let size = Size::from_bytes(size);
487+
let align = match Align::from_bytes(align) {
488+
Ok(a) => a,
489+
Err(err) => throw_ub_custom!(
490+
fluent::const_eval_invalid_align_details,
491+
name = "const_leak",
492+
err_kind = err.diag_ident(),
493+
align = err.align()
494+
),
495+
};
496+
497+
// If an allocation is created in an another const,
498+
// we don't reallocate it.
499+
let (alloc_id, _, _) = ecx.ptr_get_alloc_id(ptr, 0)?;
500+
let is_allocated_in_another_const = matches!(
501+
ecx.tcx.try_get_global_alloc(alloc_id),
502+
Some(interpret::GlobalAlloc::Memory(_))
503+
);
504+
505+
if is_allocated_in_another_const {
506+
// just return the pointer that was passed in
507+
ecx.write_pointer(ptr, dest)?;
508+
} else {
509+
let ptr = ecx.leak_const_heap_ptr(
510+
ptr,
511+
size,
512+
align,
513+
interpret::MemoryKind::Machine(MemoryKind::Heap),
514+
interpret::MemoryKind::Machine(MemoryKind::HeapLeaked),
515+
)?;
516+
ecx.write_pointer(ptr, dest)?;
517+
}
518+
}
519+
460520
// The intrinsic represents whether the value is known to the optimizer (LLVM).
461521
// We're not doing any optimizations here, so there is no optimizer that could know the value.
462522
// (We know the value here in the machine of course, but this is the runtime of that code,

compiler/rustc_const_eval/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ pub(crate) struct MutablePtrInFinal {
4343
pub kind: InternKind,
4444
}
4545

46+
#[derive(Diagnostic)]
47+
#[diag(const_eval_const_heap_ptr_in_final)]
48+
#[note]
49+
pub(crate) struct ConstHeapPtrInFinal {
50+
#[primary_span]
51+
pub span: Span,
52+
}
53+
4654
#[derive(Diagnostic)]
4755
#[diag(const_eval_unstable_in_stable_exposed)]
4856
pub(crate) struct UnstableInStableExposed {

compiler/rustc_const_eval/src/interpret/intern.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ use rustc_span::def_id::LocalDefId;
2727
use tracing::{instrument, trace};
2828

2929
use super::{
30-
AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy, err_ub, interp_ok,
30+
AllocId, Allocation, InterpCx, IsConstHeap, MPlaceTy, Machine, MemoryKind, PlaceTy, err_ub,
31+
interp_ok,
3132
};
3233
use crate::const_eval;
3334
use crate::const_eval::DummyMachine;
34-
use crate::errors::NestedStaticInThreadLocal;
35+
use crate::errors::{ConstHeapPtrInFinal, NestedStaticInThreadLocal};
3536

3637
pub trait CompileTimeMachine<'tcx, T> = Machine<
3738
'tcx,
@@ -62,7 +63,7 @@ impl HasStaticRootDefId for const_eval::CompileTimeMachine<'_> {
6263
/// already mutable (as a sanity check).
6364
///
6465
/// Returns an iterator over all relocations referred to by this allocation.
65-
fn intern_shallow<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
66+
fn intern_shallow<'tcx, T: IsConstHeap, M: CompileTimeMachine<'tcx, T>>(
6667
ecx: &mut InterpCx<'tcx, M>,
6768
alloc_id: AllocId,
6869
mutability: Mutability,
@@ -71,9 +72,16 @@ fn intern_shallow<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
7172
trace!("intern_shallow {:?}", alloc_id);
7273
// remove allocation
7374
// FIXME(#120456) - is `swap_remove` correct?
74-
let Some((_kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
75+
let Some((kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
7576
return Err(());
7677
};
78+
79+
if matches!(kind, MemoryKind::Machine(x) if x.is_const_heap()) {
80+
// emit an error but don't return an `Err` as if we did the caller assumes we found
81+
// a dangling pointer.
82+
ecx.tcx.dcx().emit_err(ConstHeapPtrInFinal { span: ecx.tcx.span });
83+
}
84+
7785
// Set allocation mutability as appropriate. This is used by LLVM to put things into
7886
// read-only memory, and also by Miri when evaluating other globals that
7987
// access this one.
@@ -321,7 +329,7 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx, const_eval
321329

322330
/// Intern `ret`. This function assumes that `ret` references no other allocation.
323331
#[instrument(level = "debug", skip(ecx))]
324-
pub fn intern_const_alloc_for_constprop<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
332+
pub fn intern_const_alloc_for_constprop<'tcx, T: IsConstHeap, M: CompileTimeMachine<'tcx, T>>(
325333
ecx: &mut InterpCx<'tcx, M>,
326334
alloc_id: AllocId,
327335
) -> InterpResult<'tcx, ()> {

compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ pub trait MayLeak: Copy {
4646
fn may_leak(self) -> bool;
4747
}
4848

49+
/// Whether this kind of memory is const heap (allocation that did not call `const_leak`)
50+
pub trait IsConstHeap {
51+
fn is_const_heap(&self) -> bool;
52+
}
53+
4954
/// The functionality needed by memory to manage its allocations
5055
pub trait AllocMap<K: Hash + Eq, V> {
5156
/// Tests if the map contains the given key.

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
274274
M::adjust_alloc_root_pointer(self, Pointer::from(id), Some(kind))
275275
}
276276

277-
/// If this grows the allocation, `init_growth` determines
278-
/// whether the additional space will be initialized.
279-
pub fn reallocate_ptr(
277+
fn reallocate_ptr_inner(
280278
&mut self,
281279
ptr: Pointer<Option<M::Provenance>>,
282280
old_size_and_align: Option<(Size, Align)>,
283281
new_size: Size,
284282
new_align: Align,
285283
kind: MemoryKind<M::MemoryKind>,
284+
new_kind: MemoryKind<M::MemoryKind>,
286285
init_growth: AllocInit,
287286
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
288287
let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?;
@@ -299,7 +298,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
299298
// If requested, we zero-init the entire allocation, to ensure that a growing
300299
// allocation has its new bytes properly set. For the part that is copied,
301300
// `mem_copy` below will de-initialize things as necessary.
302-
let new_ptr = self.allocate_ptr(new_size, new_align, kind, init_growth)?;
301+
let new_ptr = self.allocate_ptr(new_size, new_align, new_kind, init_growth)?;
303302
let old_size = match old_size_and_align {
304303
Some((size, _align)) => size,
305304
None => self.get_alloc_raw(alloc_id)?.size(),
@@ -311,6 +310,47 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
311310
interp_ok(new_ptr)
312311
}
313312

313+
/// If this grows the allocation, `init_growth` determines
314+
/// whether the additional space will be initialized.
315+
pub fn reallocate_ptr(
316+
&mut self,
317+
ptr: Pointer<Option<M::Provenance>>,
318+
old_size_and_align: Option<(Size, Align)>,
319+
new_size: Size,
320+
new_align: Align,
321+
kind: MemoryKind<M::MemoryKind>,
322+
init_growth: AllocInit,
323+
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
324+
self.reallocate_ptr_inner(
325+
ptr,
326+
old_size_and_align,
327+
new_size,
328+
new_align,
329+
kind,
330+
kind,
331+
init_growth,
332+
)
333+
}
334+
335+
pub fn leak_const_heap_ptr(
336+
&mut self,
337+
ptr: Pointer<Option<M::Provenance>>,
338+
size: Size,
339+
align: Align,
340+
kind: MemoryKind<M::MemoryKind>,
341+
new_kind: MemoryKind<M::MemoryKind>,
342+
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
343+
self.reallocate_ptr_inner(
344+
ptr,
345+
Some((size, align)),
346+
size,
347+
align,
348+
kind,
349+
new_kind,
350+
AllocInit::Uninit,
351+
)
352+
}
353+
314354
#[instrument(skip(self), level = "debug")]
315355
pub fn deallocate_ptr(
316356
&mut self,

compiler/rustc_const_eval/src/interpret/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ pub use self::intern::{
2929
HasStaticRootDefId, InternKind, InternResult, intern_const_alloc_for_constprop,
3030
intern_const_alloc_recursive,
3131
};
32-
pub use self::machine::{AllocMap, Machine, MayLeak, ReturnAction, compile_time_machine};
32+
pub use self::machine::{
33+
AllocMap, IsConstHeap, Machine, MayLeak, ReturnAction, compile_time_machine,
34+
};
3335
pub use self::memory::{AllocInfo, AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
3436
use self::operand::Operand;
3537
pub use self::operand::{ImmTy, Immediate, OpTy};

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,12 @@ pub(crate) fn check_intrinsic_type(
415415
vec![Ty::new_mut_ptr(tcx, tcx.types.u8), tcx.types.usize, tcx.types.usize],
416416
tcx.types.unit,
417417
),
418+
sym::const_leak => (
419+
0,
420+
0,
421+
vec![Ty::new_mut_ptr(tcx, tcx.types.u8), tcx.types.usize, tcx.types.usize],
422+
Ty::new_imm_ptr(tcx, tcx.types.u8),
423+
),
418424

419425
sym::ptr_offset_from => (
420426
1,

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ symbols! {
712712
const_impl_trait,
713713
const_in_array_repeat_expressions,
714714
const_indexing,
715+
const_leak,
715716
const_let,
716717
const_loop,
717718
const_mut_refs,

library/alloc/src/boxed/thin.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use core::error::Error;
66
use core::fmt::{self, Debug, Display, Formatter};
77
#[cfg(not(no_global_oom_handling))]
8-
use core::intrinsics::const_allocate;
8+
use core::intrinsics::{const_allocate, const_leak};
99
use core::marker::PhantomData;
1010
#[cfg(not(no_global_oom_handling))]
1111
use core::marker::Unsize;
@@ -342,7 +342,11 @@ impl<H> WithHeader<H> {
342342
metadata_ptr.write(ptr::metadata::<Dyn>(ptr::dangling::<T>() as *const Dyn));
343343

344344
// SAFETY: we have just written the metadata.
345-
&*(metadata_ptr)
345+
const_leak(alloc, alloc_size, alloc_align)
346+
.add(metadata_offset)
347+
.cast::<<Dyn as Pointee>::Metadata>()
348+
.as_ref()
349+
.unwrap()
346350
}
347351
};
348352

library/core/src/intrinsics/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,6 +2533,16 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize)
25332533
// Runtime NOP
25342534
}
25352535

2536+
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
2537+
#[rustc_nounwind]
2538+
#[rustc_intrinsic]
2539+
#[miri::intrinsic_fallback_is_spec]
2540+
pub const unsafe fn const_leak(ptr: *mut u8, _size: usize, _align: usize) -> *const u8 {
2541+
// const eval overrides this function.
2542+
// SAFETY: responsibility of the caller
2543+
unsafe { &*ptr }
2544+
}
2545+
25362546
/// Returns whether we should perform contract-checking at runtime.
25372547
///
25382548
/// This is meant to be similar to the ub_checks intrinsic, in terms

tests/ui/consts/const-eval/heap/alloc_intrinsic_nontransient.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const FOO_RAW: *const i32 = foo();
88

99
const fn foo() -> &'static i32 {
1010
let t = unsafe {
11-
let i = intrinsics::const_allocate(4, 4) as * mut i32;
11+
let i = intrinsics::const_allocate(4, 4) as *mut i32;
1212
*i = 20;
1313
i
1414
};
15-
unsafe { &*t }
15+
unsafe { &*(intrinsics::const_leak(t as *mut u8, 4, 4) as *mut i32) }
1616
}
1717
fn main() {
1818
assert_eq!(*FOO, 20);

tests/ui/consts/const-eval/heap/alloc_intrinsic_uninit.64bit.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
error[E0080]: constructing invalid value at .<deref>: encountered uninitialized memory, but expected an integer
22
--> $DIR/alloc_intrinsic_uninit.rs:7:1
33
|
4-
LL | const BAR: &i32 = unsafe { &*(intrinsics::const_allocate(4, 4) as *mut i32) };
4+
LL | const BAR: &i32 = unsafe { &*(intrinsics::const_leak(intrinsics::const_allocate(4, 4), 4, 4) as *mut i32) };
55
| ^^^^^^^^^^^^^^^ it is undefined behavior to use this value
66
|
77
= 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.

tests/ui/consts/const-eval/heap/alloc_intrinsic_uninit.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#![feature(const_heap)]
55
use std::intrinsics;
66

7-
const BAR: &i32 = unsafe { &*(intrinsics::const_allocate(4, 4) as *mut i32) };
7+
const BAR: &i32 = unsafe {
8+
&*(intrinsics::const_leak(intrinsics::const_allocate(4, 4), 4, 4) as *mut i32)
9+
};
810
//~^ ERROR: uninitialized memory
911
fn main() {}

tests/ui/consts/const-eval/heap/alloc_intrinsic_untyped.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ use std::intrinsics;
77

88
const BAR: *mut i32 = unsafe { intrinsics::const_allocate(4, 4) as *mut i32 };
99
//~^ error: mutable pointer in final value of constant
10+
//~| error: encountered `const_allocate` pointer in final value that was not leaked
1011

1112
fn main() {}

tests/ui/consts/const-eval/heap/dealloc_intrinsic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const _X: () = unsafe {
1212
const Y: &u32 = unsafe {
1313
let ptr = intrinsics::const_allocate(4, 4) as *mut u32;
1414
*ptr = 42;
15-
&*ptr
15+
&*(intrinsics::const_leak(ptr as *mut u8, 4, 4) as *const u32)
1616
};
1717

1818
const Z: &u32 = &42;

0 commit comments

Comments
 (0)