allow mutable references in const values when they point to no memory

This commit is contained in:
Ralf Jung 2024-02-16 09:58:53 +01:00
parent 0f806a9812
commit 0702701297
8 changed files with 93 additions and 84 deletions

View File

@ -453,7 +453,7 @@ const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, bu
const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` or `static` const_eval_validation_mutable_ref_in_const_or_static = {$front_matter}: encountered mutable reference in a `const` or `static`
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!` const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
const_eval_validation_null_box = {$front_matter}: encountered a null box const_eval_validation_null_box = {$front_matter}: encountered a null box

View File

@ -603,18 +603,18 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => { PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => {
const_eval_validation_box_to_uninhabited const_eval_validation_box_to_uninhabited
} }
PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => { PtrToUninhabited { ptr_kind: PointerKind::Ref(_), .. } => {
const_eval_validation_ref_to_uninhabited const_eval_validation_ref_to_uninhabited
} }
PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static, PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static,
PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_static, PtrToStatic { ptr_kind: PointerKind::Ref(_) } => const_eval_validation_ref_to_static,
PointerAsInt { .. } => const_eval_validation_pointer_as_int, PointerAsInt { .. } => const_eval_validation_pointer_as_int,
PartialPointer => const_eval_validation_partial_pointer, PartialPointer => const_eval_validation_partial_pointer,
ConstRefToMutable => const_eval_validation_const_ref_to_mutable, ConstRefToMutable => const_eval_validation_const_ref_to_mutable,
ConstRefToExtern => const_eval_validation_const_ref_to_extern, ConstRefToExtern => const_eval_validation_const_ref_to_extern,
MutableRefInConst => const_eval_validation_mutable_ref_in_const, MutableRefInConstOrStatic => const_eval_validation_mutable_ref_in_const_or_static,
MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable, MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable,
NullFnPtr => const_eval_validation_null_fn_ptr, NullFnPtr => const_eval_validation_null_fn_ptr,
NeverVal => const_eval_validation_never_val, NeverVal => const_eval_validation_never_val,
@ -630,37 +630,39 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => { InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
const_eval_validation_invalid_box_slice_meta const_eval_validation_invalid_box_slice_meta
} }
InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => { InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref(_) } => {
const_eval_validation_invalid_ref_slice_meta const_eval_validation_invalid_ref_slice_meta
} }
InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => { InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => {
const_eval_validation_invalid_box_meta const_eval_validation_invalid_box_meta
} }
InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => { InvalidMetaTooLarge { ptr_kind: PointerKind::Ref(_) } => {
const_eval_validation_invalid_ref_meta const_eval_validation_invalid_ref_meta
} }
UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_validation_unaligned_ref, UnalignedPtr { ptr_kind: PointerKind::Ref(_), .. } => {
const_eval_validation_unaligned_ref
}
UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_validation_unaligned_box, UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_validation_unaligned_box,
NullPtr { ptr_kind: PointerKind::Box } => const_eval_validation_null_box, NullPtr { ptr_kind: PointerKind::Box } => const_eval_validation_null_box,
NullPtr { ptr_kind: PointerKind::Ref } => const_eval_validation_null_ref, NullPtr { ptr_kind: PointerKind::Ref(_) } => const_eval_validation_null_ref,
DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => { DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
const_eval_validation_dangling_box_no_provenance const_eval_validation_dangling_box_no_provenance
} }
DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => { DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref(_), .. } => {
const_eval_validation_dangling_ref_no_provenance const_eval_validation_dangling_ref_no_provenance
} }
DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => { DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
const_eval_validation_dangling_box_out_of_bounds const_eval_validation_dangling_box_out_of_bounds
} }
DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => { DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref(_) } => {
const_eval_validation_dangling_ref_out_of_bounds const_eval_validation_dangling_ref_out_of_bounds
} }
DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => { DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
const_eval_validation_dangling_box_use_after_free const_eval_validation_dangling_box_use_after_free
} }
DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => { DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref(_) } => {
const_eval_validation_dangling_ref_use_after_free const_eval_validation_dangling_ref_use_after_free
} }
InvalidBool { .. } => const_eval_validation_invalid_bool, InvalidBool { .. } => const_eval_validation_invalid_bool,
@ -766,7 +768,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
} }
NullPtr { .. } NullPtr { .. }
| PtrToStatic { .. } | PtrToStatic { .. }
| MutableRefInConst | MutableRefInConstOrStatic
| ConstRefToMutable | ConstRefToMutable
| ConstRefToExtern | ConstRefToExtern
| MutableRefToImmutable | MutableRefToImmutable

View File

@ -445,22 +445,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// Determine whether this pointer expects to be pointing to something mutable. // Determine whether this pointer expects to be pointing to something mutable.
let ptr_expected_mutbl = match ptr_kind { let ptr_expected_mutbl = match ptr_kind {
PointerKind::Box => Mutability::Mut, PointerKind::Box => Mutability::Mut,
PointerKind::Ref => { PointerKind::Ref(mutbl) => {
let tam = value.layout.ty.builtin_deref(false).unwrap(); // We do not take into account interior mutability here since we cannot know if
// ZST never require mutability. We do not take into account interior mutability // there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check
// here since we cannot know if there really is an `UnsafeCell` inside // that in the recursive descent behind this reference (controlled by
// `Option<UnsafeCell>` -- so we check that in the recursive descent behind this // `allow_immutable_unsafe_cell`).
// reference. mutbl
if size == Size::ZERO { Mutability::Not } else { tam.mutbl }
} }
}; };
// Proceed recursively even for ZST, no reason to skip them! // Proceed recursively even for ZST, no reason to skip them!
// `!` is a ZST and we want to validate it. // `!` is a ZST and we want to validate it.
if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) { if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) {
let mut skip_recursive_check = false;
// Let's see what kind of memory this points to. // Let's see what kind of memory this points to.
// `unwrap` since dangling pointers have already been handled. // `unwrap` since dangling pointers have already been handled.
let alloc_kind = self.ecx.tcx.try_get_global_alloc(alloc_id).unwrap(); let alloc_kind = self.ecx.tcx.try_get_global_alloc(alloc_id).unwrap();
match alloc_kind { let alloc_actual_mutbl = match alloc_kind {
GlobalAlloc::Static(did) => { GlobalAlloc::Static(did) => {
// Special handling for pointers to statics (irrespective of their type). // Special handling for pointers to statics (irrespective of their type).
assert!(!self.ecx.tcx.is_thread_local_static(did)); assert!(!self.ecx.tcx.is_thread_local_static(did));
@ -474,12 +474,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
.no_bound_vars() .no_bound_vars()
.expect("statics should not have generic parameters") .expect("statics should not have generic parameters")
.is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()); .is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all());
// Mutability check.
if ptr_expected_mutbl == Mutability::Mut {
if !is_mut {
throw_validation_failure!(self.path, MutableRefToImmutable);
}
}
// Mode-specific checks // Mode-specific checks
match self.ctfe_mode { match self.ctfe_mode {
Some( Some(
@ -494,15 +488,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// trigger cycle errors if we try to compute the value of the other static // trigger cycle errors if we try to compute the value of the other static
// and that static refers back to us (potentially through a promoted). // and that static refers back to us (potentially through a promoted).
// This could miss some UB, but that's fine. // This could miss some UB, but that's fine.
return Ok(()); skip_recursive_check = true;
} }
Some(CtfeValidationMode::Const { .. }) => { Some(CtfeValidationMode::Const { .. }) => {
// For consts on the other hand we have to recursively check;
// pattern matching assumes a valid value. However we better make
// sure this is not mutable.
if is_mut {
throw_validation_failure!(self.path, ConstRefToMutable);
}
// We can't recursively validate `extern static`, so we better reject them. // We can't recursively validate `extern static`, so we better reject them.
if self.ecx.tcx.is_foreign_item(did) { if self.ecx.tcx.is_foreign_item(did) {
throw_validation_failure!(self.path, ConstRefToExtern); throw_validation_failure!(self.path, ConstRefToExtern);
@ -510,25 +498,38 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
} }
None => {} None => {}
} }
// Return alloc mutability
if is_mut { Mutability::Mut } else { Mutability::Not }
} }
GlobalAlloc::Memory(alloc) => { GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
if alloc.inner().mutability == Mutability::Mut
&& matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. }))
{
throw_validation_failure!(self.path, ConstRefToMutable);
}
if ptr_expected_mutbl == Mutability::Mut
&& alloc.inner().mutability == Mutability::Not
{
throw_validation_failure!(self.path, MutableRefToImmutable);
}
}
GlobalAlloc::Function(..) | GlobalAlloc::VTable(..) => { GlobalAlloc::Function(..) | GlobalAlloc::VTable(..) => {
// These are immutable, we better don't allow mutable pointers here. // These are immutable, we better don't allow mutable pointers here.
if ptr_expected_mutbl == Mutability::Mut { Mutability::Not
throw_validation_failure!(self.path, MutableRefToImmutable);
}
} }
};
// Mutability check.
// If this allocation has size zero, there is no actual mutability here.
let (size, _align, _alloc_kind) = self.ecx.get_alloc_info(alloc_id);
if size != Size::ZERO {
if ptr_expected_mutbl == Mutability::Mut
&& alloc_actual_mutbl == Mutability::Not
{
throw_validation_failure!(self.path, MutableRefToImmutable);
}
if ptr_expected_mutbl == Mutability::Mut
&& self.ctfe_mode.is_some_and(|c| !c.may_contain_mutable_ref())
{
throw_validation_failure!(self.path, MutableRefInConstOrStatic);
}
if alloc_actual_mutbl == Mutability::Mut
&& matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. }))
{
throw_validation_failure!(self.path, ConstRefToMutable);
}
}
// Potentially skip recursive check.
if skip_recursive_check {
return Ok(());
} }
} }
let path = &self.path; let path = &self.path;
@ -598,16 +599,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
} }
Ok(true) Ok(true)
} }
ty::Ref(_, ty, mutbl) => { ty::Ref(_, _ty, mutbl) => {
if self.ctfe_mode.is_some_and(|c| !c.may_contain_mutable_ref()) self.check_safe_pointer(value, PointerKind::Ref(*mutbl))?;
&& *mutbl == Mutability::Mut
{
let layout = self.ecx.layout_of(*ty)?;
if !layout.is_zst() {
throw_validation_failure!(self.path, MutableRefInConst);
}
}
self.check_safe_pointer(value, PointerKind::Ref)?;
Ok(true) Ok(true)
} }
ty::FnPtr(_sig) => { ty::FnPtr(_sig) => {

View File

@ -12,6 +12,7 @@ use rustc_macros::HashStable;
use rustc_session::CtfeBacktrace; use rustc_session::CtfeBacktrace;
use rustc_span::{def_id::DefId, Span, DUMMY_SP}; use rustc_span::{def_id::DefId, Span, DUMMY_SP};
use rustc_target::abi::{call, Align, Size, VariantIdx, WrappingRange}; use rustc_target::abi::{call, Align, Size, VariantIdx, WrappingRange};
use rustc_type_ir::Mutability;
use std::borrow::Cow; use std::borrow::Cow;
use std::{any::Any, backtrace::Backtrace, fmt}; use std::{any::Any, backtrace::Backtrace, fmt};
@ -367,7 +368,7 @@ pub enum UndefinedBehaviorInfo<'tcx> {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum PointerKind { pub enum PointerKind {
Ref, Ref(Mutability),
Box, Box,
} }
@ -375,7 +376,7 @@ impl IntoDiagnosticArg for PointerKind {
fn into_diagnostic_arg(self) -> DiagnosticArgValue { fn into_diagnostic_arg(self) -> DiagnosticArgValue {
DiagnosticArgValue::Str( DiagnosticArgValue::Str(
match self { match self {
Self::Ref => "ref", Self::Ref(_) => "ref",
Self::Box => "box", Self::Box => "box",
} }
.into(), .into(),
@ -408,7 +409,7 @@ impl From<PointerKind> for ExpectedKind {
fn from(x: PointerKind) -> ExpectedKind { fn from(x: PointerKind) -> ExpectedKind {
match x { match x {
PointerKind::Box => ExpectedKind::Box, PointerKind::Box => ExpectedKind::Box,
PointerKind::Ref => ExpectedKind::Reference, PointerKind::Ref(_) => ExpectedKind::Reference,
} }
} }
} }
@ -419,7 +420,7 @@ pub enum ValidationErrorKind<'tcx> {
PartialPointer, PartialPointer,
PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> }, PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> },
PtrToStatic { ptr_kind: PointerKind }, PtrToStatic { ptr_kind: PointerKind },
MutableRefInConst, MutableRefInConstOrStatic,
ConstRefToMutable, ConstRefToMutable,
ConstRefToExtern, ConstRefToExtern,
MutableRefToImmutable, MutableRefToImmutable,

View File

@ -1,6 +1,8 @@
// check-pass // check-pass
#![feature(const_mut_refs)] #![feature(const_mut_refs)]
use std::sync::Mutex;
struct Foo { struct Foo {
x: usize x: usize
} }
@ -28,6 +30,10 @@ const fn bazz(foo: &mut Foo) -> usize {
foo.x foo.x
} }
// Empty slices get promoted so this passes the static checks.
// Make sure it also passes the dynamic checks.
static MUTABLE_REFERENCE_HOLDER: Mutex<&mut [u8]> = Mutex::new(&mut []);
fn main() { fn main() {
let _: [(); foo().bar()] = [(); 1]; let _: [(); foo().bar()] = [(); 1];
let _: [(); baz(&mut foo())] = [(); 2]; let _: [(); baz(&mut foo())] = [(); 2];

View File

@ -1,19 +1,24 @@
error[E0080]: it is undefined behavior to use this value error[E0080]: it is undefined behavior to use this value
--> $DIR/mut_ref_in_final_dynamic_check.rs:15:1 --> $DIR/mut_ref_in_final_dynamic_check.rs:16:1
| |
LL | const A: Option<&mut i32> = helper(); LL | const A: Option<&mut i32> = helper();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static` | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static`
| |
= 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. = 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.
= note: the raw bytes of the constant (size: 4, align: 4) { = note: the raw bytes of the constant (size: 4, align: 4) {
2a 00 00 00 │ *... ╾ALLOC0╼ │ ╾──╼
} }
error: encountered dangling pointer in final value of constant error[E0080]: it is undefined behavior to use this value
--> $DIR/mut_ref_in_final_dynamic_check.rs:22:1 --> $DIR/mut_ref_in_final_dynamic_check.rs:18:1
| |
LL | const B: Option<&mut i32> = helper2(); LL | static A_STATIC: Option<&mut i32> = helper();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static`
|
= 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.
= note: the raw bytes of the constant (size: 4, align: 4) {
╾ALLOC0╼ │ ╾──╼
}
error: aborting due to 2 previous errors error: aborting due to 2 previous errors

View File

@ -1,19 +1,24 @@
error[E0080]: it is undefined behavior to use this value error[E0080]: it is undefined behavior to use this value
--> $DIR/mut_ref_in_final_dynamic_check.rs:15:1 --> $DIR/mut_ref_in_final_dynamic_check.rs:16:1
| |
LL | const A: Option<&mut i32> = helper(); LL | const A: Option<&mut i32> = helper();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static` | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static`
| |
= 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. = 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.
= note: the raw bytes of the constant (size: 8, align: 8) { = note: the raw bytes of the constant (size: 8, align: 8) {
2a 00 00 00 00 00 00 00 │ *....... ╾ALLOC0╼ │ ╾──────╼
} }
error: encountered dangling pointer in final value of constant error[E0080]: it is undefined behavior to use this value
--> $DIR/mut_ref_in_final_dynamic_check.rs:22:1 --> $DIR/mut_ref_in_final_dynamic_check.rs:18:1
| |
LL | const B: Option<&mut i32> = helper2(); LL | static A_STATIC: Option<&mut i32> = helper();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in a `const` or `static`
|
= 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.
= note: the raw bytes of the constant (size: 8, align: 8) {
╾ALLOC0╼ │ ╾──────╼
}
error: aborting due to 2 previous errors error: aborting due to 2 previous errors

View File

@ -1,5 +1,5 @@
// stderr-per-bitwidth // stderr-per-bitwidth
#![feature(const_mut_refs)] #![feature(const_mut_refs, const_refs_to_static)]
#![feature(raw_ref_op)] #![feature(raw_ref_op)]
// This file checks that our dynamic checks catch things that the static checks miss. // This file checks that our dynamic checks catch things that the static checks miss.
@ -8,17 +8,14 @@
// do that without causing the borrow checker to complain (see the B4/helper test in // do that without causing the borrow checker to complain (see the B4/helper test in
// mut_ref_in_final.rs). // mut_ref_in_final.rs).
static mut BUFFER: i32 = 42;
const fn helper() -> Option<&'static mut i32> { unsafe { const fn helper() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (integer as pointer), who doesn't love tests like this. Some(&mut *std::ptr::addr_of_mut!(BUFFER))
Some(&mut *(42 as *mut i32))
} } } }
const A: Option<&mut i32> = helper(); //~ ERROR it is undefined behavior to use this value const A: Option<&mut i32> = helper(); //~ ERROR it is undefined behavior to use this value
//~^ encountered mutable reference in a `const` //~^ encountered mutable reference
static A_STATIC: Option<&mut i32> = helper(); //~ ERROR it is undefined behavior to use this value
const fn helper2() -> Option<&'static mut i32> { unsafe { //~^ encountered mutable reference
// Undefined behaviour (dangling pointer), who doesn't love tests like this.
Some(&mut *(&mut 42 as *mut i32))
} }
const B: Option<&mut i32> = helper2(); //~ ERROR encountered dangling pointer in final value of constant
fn main() {} fn main() {}