Skip to content

Commit b1cc426

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

21 files changed

+204
-16
lines changed

compiler/rustc_const_eval/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ 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 made global
60+
.note = use `const_make_global` to make allocated pointers immutable before returning
61+
62+
const_eval_const_make_global_invalid_pointer = invalid pointer passed to `const_make_global`
63+
5964
const_eval_copy_nonoverlapping_overlapping =
6065
`copy_nonoverlapping` called on overlapping ranges
6166
@@ -338,6 +343,7 @@ const_eval_realloc_or_alloc_with_offset =
338343
{$kind ->
339344
[dealloc] deallocating
340345
[realloc] reallocating
346+
[leak] leaking
341347
*[other] {""}
342348
} {$ptr} which does not point to the beginning of an object
343349

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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,37 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
457457
)?;
458458
}
459459
}
460+
461+
sym::const_make_global => {
462+
let ptr = ecx.read_pointer(&args[0])?;
463+
let size = ecx.read_scalar(&args[1])?.to_target_usize(ecx)?;
464+
let align = ecx.read_scalar(&args[2])?.to_target_usize(ecx)?;
465+
466+
let _size: Size = Size::from_bytes(size);
467+
let _align = match Align::from_bytes(align) {
468+
Ok(a) => a,
469+
Err(err) => throw_ub_custom!(
470+
fluent::const_eval_invalid_align_details,
471+
name = "const_make_global",
472+
err_kind = err.diag_ident(),
473+
align = err.align()
474+
),
475+
};
476+
477+
let (alloc_id, _, _) = ecx.ptr_get_alloc_id(ptr, 0)?;
478+
let is_allocated_in_another_const = matches!(
479+
ecx.tcx.try_get_global_alloc(alloc_id),
480+
Some(interpret::GlobalAlloc::Memory(_))
481+
);
482+
483+
if !is_allocated_in_another_const {
484+
ecx.leak_const_heap_ptr(ptr)?;
485+
ecx.write_pointer(ptr, dest)?;
486+
} else {
487+
throw_ub_custom!(fluent::const_eval_const_make_global_invalid_pointer);
488+
}
489+
}
490+
460491
// The intrinsic represents whether the value is known to the optimizer (LLVM).
461492
// We're not doing any optimizations here, so there is no optimizer that could know the value.
462493
// (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: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use super::{
3131
};
3232
use crate::const_eval;
3333
use crate::const_eval::DummyMachine;
34-
use crate::errors::NestedStaticInThreadLocal;
34+
use crate::errors::{ConstHeapPtrInFinal, NestedStaticInThreadLocal};
3535

3636
pub trait CompileTimeMachine<'tcx, T> = Machine<
3737
'tcx,
@@ -55,14 +55,36 @@ impl HasStaticRootDefId for const_eval::CompileTimeMachine<'_> {
5555
}
5656
}
5757

58+
pub enum DisallowInternReason {
59+
ConstHeap,
60+
}
61+
62+
pub trait CanIntern {
63+
fn disallows_intern(&self) -> Option<DisallowInternReason>;
64+
}
65+
66+
impl CanIntern for const_eval::MemoryKind {
67+
fn disallows_intern(&self) -> Option<DisallowInternReason> {
68+
match self {
69+
const_eval::MemoryKind::Heap => Some(DisallowInternReason::ConstHeap),
70+
}
71+
}
72+
}
73+
74+
impl CanIntern for ! {
75+
fn disallows_intern(&self) -> Option<DisallowInternReason> {
76+
*self
77+
}
78+
}
79+
5880
/// Intern an allocation. Returns `Err` if the allocation does not exist in the local memory.
5981
///
6082
/// `mutability` can be used to force immutable interning: if it is `Mutability::Not`, the
6183
/// allocation is interned immutably; if it is `Mutability::Mut`, then the allocation *must be*
6284
/// already mutable (as a sanity check).
6385
///
6486
/// Returns an iterator over all relocations referred to by this allocation.
65-
fn intern_shallow<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
87+
fn intern_shallow<'tcx, T: CanIntern, M: CompileTimeMachine<'tcx, T>>(
6688
ecx: &mut InterpCx<'tcx, M>,
6789
alloc_id: AllocId,
6890
mutability: Mutability,
@@ -71,9 +93,22 @@ fn intern_shallow<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
7193
trace!("intern_shallow {:?}", alloc_id);
7294
// remove allocation
7395
// FIXME(#120456) - is `swap_remove` correct?
74-
let Some((_kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
96+
let Some((kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
7597
return Err(());
7698
};
99+
100+
match kind {
101+
MemoryKind::Machine(x) if let Some(reason) = x.disallows_intern() => match reason {
102+
// attempting to intern a `const_allocate`d pointer that was not made global via
103+
// `const_make_global`. We emit an error here but don't return an `Err` as if we
104+
// did the caller assumes we found a dangling pointer.
105+
DisallowInternReason::ConstHeap => {
106+
ecx.tcx.dcx().emit_err(ConstHeapPtrInFinal { span: ecx.tcx.span });
107+
}
108+
},
109+
MemoryKind::Machine(_) | MemoryKind::Stack | MemoryKind::CallerLocation => {}
110+
}
111+
77112
// Set allocation mutability as appropriate. This is used by LLVM to put things into
78113
// read-only memory, and also by Miri when evaluating other globals that
79114
// access this one.
@@ -99,7 +134,7 @@ fn intern_shallow<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
99134
} else {
100135
ecx.tcx.set_alloc_id_memory(alloc_id, alloc);
101136
}
102-
Ok(alloc.0.0.provenance().ptrs().iter().map(|&(_, prov)| prov))
137+
Ok(alloc.inner().provenance().ptrs().iter().map(|&(_, prov)| prov))
103138
}
104139

105140
/// Creates a new `DefId` and feeds all the right queries to make this `DefId`
@@ -321,7 +356,7 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx, const_eval
321356

322357
/// Intern `ret`. This function assumes that `ret` references no other allocation.
323358
#[instrument(level = "debug", skip(ecx))]
324-
pub fn intern_const_alloc_for_constprop<'tcx, T, M: CompileTimeMachine<'tcx, T>>(
359+
pub fn intern_const_alloc_for_constprop<'tcx, T: CanIntern, M: CompileTimeMachine<'tcx, T>>(
325360
ecx: &mut InterpCx<'tcx, M>,
326361
alloc_id: AllocId,
327362
) -> InterpResult<'tcx, ()> {

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,24 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
311311
interp_ok(new_ptr)
312312
}
313313

314+
pub fn leak_const_heap_ptr(
315+
&mut self,
316+
ptr: Pointer<Option<CtfeProvenance>>,
317+
) -> InterpResult<'tcx>
318+
where
319+
M: Machine<'tcx, Provenance = CtfeProvenance, AllocExtra = (), Bytes = Box<[u8]>>,
320+
{
321+
let (alloc_id, _, _) = self.ptr_get_alloc_id(ptr, 0)?;
322+
let Some((_kind, mut alloc)) = self.memory.alloc_map.remove(&alloc_id) else {
323+
return Err(err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccess)))
324+
.into();
325+
};
326+
alloc.mutability = Mutability::Not;
327+
let alloc = self.tcx.mk_const_alloc(alloc);
328+
self.tcx.set_alloc_id_memory(alloc_id, alloc);
329+
interp_ok(())
330+
}
331+
314332
#[instrument(skip(self), level = "debug")]
315333
pub fn deallocate_ptr(
316334
&mut self,

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_make_global => (
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
@@ -714,6 +714,7 @@ symbols! {
714714
const_indexing,
715715
const_let,
716716
const_loop,
717+
const_make_global,
717718
const_mut_refs,
718719
const_panic,
719720
const_panic_fmt,

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_make_global};
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_make_global(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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,6 +2533,15 @@ 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_make_global(ptr: *mut u8, _size: usize, _align: usize) -> *const u8 {
2541+
// const eval overrides this function.
2542+
ptr
2543+
}
2544+
25362545
/// Returns whether we should perform contract-checking at runtime.
25372546
///
25382547
/// 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_make_global(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.32bit.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 {
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.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 {
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(const_heap)]
55
use std::intrinsics;
66

7-
const BAR: &i32 = unsafe { &*(intrinsics::const_allocate(4, 4) as *mut i32) };
8-
//~^ ERROR: uninitialized memory
7+
const BAR: &i32 = unsafe { //~ ERROR: uninitialized memory
8+
&*(intrinsics::const_make_global(intrinsics::const_allocate(4, 4), 4, 4) as *mut i32)
9+
};
910
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 made global
1011

1112
fn main() {}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
error: encountered `const_allocate` pointer in final value that was not made global
2+
--> $DIR/alloc_intrinsic_untyped.rs:8:1
3+
|
4+
LL | const BAR: *mut i32 = unsafe { intrinsics::const_allocate(4, 4) as *mut i32 };
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: use `const_make_global` to make allocated pointers immutable before returning
8+
19
error: encountered mutable pointer in final value of constant
210
--> $DIR/alloc_intrinsic_untyped.rs:8:1
311
|
412
LL | const BAR: *mut i32 = unsafe { intrinsics::const_allocate(4, 4) as *mut i32 };
513
| ^^^^^^^^^^^^^^^^^^^
614

7-
error: aborting due to 1 previous error
15+
error: aborting due to 2 previous errors
816

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_make_global(ptr as *mut u8, 4, 4) as *const u32)
1616
};
1717

1818
const Z: &u32 = &42;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![feature(core_intrinsics)]
2+
#![feature(const_heap)]
3+
use std::intrinsics;
4+
5+
const A: &u8 = unsafe {
6+
let ptr = intrinsics::const_allocate(1, 1);
7+
*ptr = 1;
8+
let ptr: *const u8 = intrinsics::const_make_global(ptr, 1, 1);
9+
*(ptr as *mut u8) = 2;
10+
//~^ error: writing to ALLOC0 which is read-only
11+
&*ptr
12+
};
13+
14+
fn main() {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0080]: writing to ALLOC0 which is read-only
2+
--> $DIR/ptr_made_global_mutated.rs:9:5
3+
|
4+
LL | *(ptr as *mut u8) = 2;
5+
| ^^^^^^^^^^^^^^^^^^^^^ evaluation of `A` failed here
6+
7+
error: aborting due to 1 previous error
8+
9+
For more information about this error, try `rustc --explain E0080`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![feature(core_intrinsics)]
2+
#![feature(const_heap)]
3+
use std::intrinsics;
4+
5+
const FOO: &i32 = foo();
6+
//~^ error: encountered `const_allocate` pointer in final value that was not made global
7+
const FOO_RAW: *const i32 = foo();
8+
//~^ error: encountered `const_allocate` pointer in final value that was not made global
9+
10+
const fn foo() -> &'static i32 {
11+
let t = unsafe {
12+
let i = intrinsics::const_allocate(4, 4) as *mut i32;
13+
*i = 20;
14+
i
15+
};
16+
unsafe { &*t }
17+
}
18+
19+
fn main() {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: encountered `const_allocate` pointer in final value that was not made global
2+
--> $DIR/ptr_not_made_global.rs:5:1
3+
|
4+
LL | const FOO: &i32 = foo();
5+
| ^^^^^^^^^^^^^^^
6+
|
7+
= note: use `const_make_global` to make allocated pointers immutable before returning
8+
9+
error: encountered `const_allocate` pointer in final value that was not made global
10+
--> $DIR/ptr_not_made_global.rs:7:1
11+
|
12+
LL | const FOO_RAW: *const i32 = foo();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
14+
|
15+
= note: use `const_make_global` to make allocated pointers immutable before returning
16+
17+
error: aborting due to 2 previous errors
18+

0 commit comments

Comments
 (0)