@@ -320,7 +320,10 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
320320 self . check_op_spanned ( ops:: StaticAccess , span)
321321 }
322322
323- fn check_mut_borrow ( & mut self , place : & Place < ' _ > , kind : hir:: BorrowKind ) {
323+ /// Returns whether this place can possibly escape the evaluation of the current const/static
324+ /// initializer. The check assumes that all already existing pointers and references point to
325+ /// non-escaping places.
326+ fn place_may_escape ( & mut self , place : & Place < ' _ > ) -> bool {
324327 let is_transient = match self . const_kind ( ) {
325328 // In a const fn all borrows are transient or point to the places given via
326329 // references in the arguments (so we already checked them with
@@ -341,14 +344,16 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
341344 // value of the constant.
342345 // Note: This is only sound if every local that has a `StorageDead` has a
343346 // `StorageDead` in every control flow path leading to a `return` terminator.
344- // The good news is that interning will detect if any unexpected mutable
345- // pointer slips through.
347+ // If anything slips through, there's no safety net -- safe code can create
348+ // references to variants of `!Freeze` enums as long as that variant is `Freeze`, so
349+ // interning can't protect us here. (There *is* a safety net for mutable references
350+ // though, interning will ICE if we miss something here.)
346351 place. is_indirect ( ) || self . local_is_transient ( place. local )
347352 }
348353 } ;
349- if !is_transient {
350- self . check_op ( ops :: EscapingMutBorrow ( kind ) ) ;
351- }
354+ // Transient places cannot possibly escape because the place doesn't exist any more at the
355+ // end of evaluation.
356+ !is_transient
352357 }
353358}
354359
@@ -406,15 +411,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
406411 let is_allowed =
407412 self . const_kind ( ) == hir:: ConstContext :: Static ( hir:: Mutability :: Mut ) ;
408413
409- if !is_allowed {
410- self . check_mut_borrow (
411- place,
412- if matches ! ( rvalue, Rvalue :: Ref ( ..) ) {
413- hir:: BorrowKind :: Ref
414- } else {
415- hir:: BorrowKind :: Raw
416- } ,
417- ) ;
414+ if !is_allowed && self . place_may_escape ( place) {
415+ self . check_op ( ops:: EscapingMutBorrow ( if matches ! ( rvalue, Rvalue :: Ref ( ..) ) {
416+ hir:: BorrowKind :: Ref
417+ } else {
418+ hir:: BorrowKind :: Raw
419+ } ) ) ;
418420 }
419421 }
420422
@@ -426,51 +428,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
426428 place. as_ref ( ) ,
427429 ) ;
428430
429- // If the place is indirect, this is basically a reborrow. We have a reborrow
430- // special case above, but for raw pointers and pointers/references to `static` and
431- // when the `*` is not the first projection, `place_as_reborrow` does not recognize
432- // them as such, so we end up here. This should probably be considered a
433- // `TransientCellBorrow` (we consider the equivalent mutable case a
434- // `TransientMutBorrow`), but such reborrows got accidentally stabilized already and
435- // it is too much of a breaking change to take back.
436- // However, we only want to consider places that are obtained by dereferencing
437- // a *shared* reference. Mutable references to interior mutable data are stable,
438- // and we don't want `&*&mut interior_mut` to be accepted.
439- let is_indirect = place. iter_projections ( ) . any ( |( base, proj) | {
440- matches ! ( proj, ProjectionElem :: Deref )
441- && matches ! (
442- base. ty( self . body, self . tcx) . ty. kind( ) ,
443- ty:: Ref ( _, _, Mutability :: Not ) | ty:: RawPtr ( _, Mutability :: Not )
444- )
445- } ) ;
446-
447- if borrowed_place_has_mut_interior && !is_indirect {
448- match self . const_kind ( ) {
449- // In a const fn all borrows are transient or point to the places given via
450- // references in the arguments (so we already checked them with
451- // TransientCellBorrow/CellBorrow as appropriate).
452- // The borrow checker guarantees that no new non-transient borrows are created.
453- // NOTE: Once we have heap allocations during CTFE we need to figure out
454- // how to prevent `const fn` to create long-lived allocations that point
455- // to (interior) mutable memory.
456- hir:: ConstContext :: ConstFn => self . check_op ( ops:: TransientCellBorrow ) ,
457- _ => {
458- // Locals with StorageDead are definitely not part of the final constant value, and
459- // it is thus inherently safe to permit such locals to have their
460- // address taken as we can't end up with a reference to them in the
461- // final value.
462- // Note: This is only sound if every local that has a `StorageDead` has a
463- // `StorageDead` in every control flow path leading to a `return` terminator.
464- // If anything slips through, there's no safety net -- safe code can create
465- // references to variants of `!Freeze` enums as long as that variant is `Freeze`,
466- // so interning can't protect us here.
467- if self . local_is_transient ( place. local ) {
468- self . check_op ( ops:: TransientCellBorrow ) ;
469- } else {
470- self . check_op ( ops:: CellBorrow ) ;
471- }
472- }
473- }
431+ if borrowed_place_has_mut_interior && self . place_may_escape ( place) {
432+ self . check_op ( ops:: EscapingCellBorrow ) ;
474433 }
475434 }
476435
0 commit comments