@@ -344,7 +344,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
344
344
visitor. visit_ty ( ty) ;
345
345
}
346
346
347
- fn check_mut_borrow ( & mut self , local : Local , kind : hir:: BorrowKind ) {
347
+ fn check_mut_borrow ( & mut self , place : & Place < ' _ > , kind : hir:: BorrowKind ) {
348
348
match self . const_kind ( ) {
349
349
// In a const fn all borrows are transient or point to the places given via
350
350
// references in the arguments (so we already checked them with
@@ -355,10 +355,19 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
355
355
// to mutable memory.
356
356
hir:: ConstContext :: ConstFn => self . check_op ( ops:: TransientMutBorrow ( kind) ) ,
357
357
_ => {
358
+ // For indirect places, we are not creating a new permanent borrow, it's just as
359
+ // transient as the already existing one. For reborrowing references this is handled
360
+ // at the top of `visit_rvalue`, but for raw pointers we handle it here.
361
+ // Pointers/references to `static mut` and cases where the `*` is not the first
362
+ // projection also end up here.
358
363
// Locals with StorageDead do not live beyond the evaluation and can
359
364
// thus safely be borrowed without being able to be leaked to the final
360
365
// value of the constant.
361
- if self . local_has_storage_dead ( local) {
366
+ // Note: This is only sound if every local that has a `StorageDead` has a
367
+ // `StorageDead` in every control flow path leading to a `return` terminator.
368
+ // The good news is that interning will detect if any unexpected mutable
369
+ // pointer slips through.
370
+ if place. is_indirect ( ) || self . local_has_storage_dead ( place. local ) {
362
371
self . check_op ( ops:: TransientMutBorrow ( kind) ) ;
363
372
} else {
364
373
self . check_op ( ops:: MutBorrow ( kind) ) ;
@@ -390,6 +399,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
390
399
trace ! ( "visit_rvalue: rvalue={:?} location={:?}" , rvalue, location) ;
391
400
392
401
// Special-case reborrows to be more like a copy of a reference.
402
+ // FIXME: this does not actually handle all reborrows. It only detects cases where `*` is the outermost
403
+ // projection of the borrowed place, it skips deref'ing raw pointers and it skips `static`.
404
+ // All those cases are handled below with shared/mutable borrows.
405
+ // Once `const_mut_refs` is stable, we should be able to entirely remove this special case.
406
+ // (`const_refs_to_cell` is not needed, we already allow all borrows of indirect places anyway.)
393
407
match * rvalue {
394
408
Rvalue :: Ref ( _, kind, place) => {
395
409
if let Some ( reborrowed_place_ref) = place_as_reborrow ( self . tcx , self . body , place) {
@@ -460,7 +474,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
460
474
461
475
if !is_allowed {
462
476
self . check_mut_borrow (
463
- place. local ,
477
+ place,
464
478
if matches ! ( rvalue, Rvalue :: Ref ( ..) ) {
465
479
hir:: BorrowKind :: Ref
466
480
} else {
@@ -478,7 +492,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
478
492
place. as_ref ( ) ,
479
493
) ;
480
494
481
- if borrowed_place_has_mut_interior {
495
+ // If the place is indirect, this is basically a reborrow. We have a reborrow
496
+ // special case above, but for raw pointers and pointers/references to `static` and
497
+ // when the `*` is not the first projection, `place_as_reborrow` does not recognize
498
+ // them as such, so we end up here. This should probably be considered a
499
+ // `TransientCellBorrow` (we consider the equivalent mutable case a
500
+ // `TransientMutBorrow`), but such reborrows got accidentally stabilized already and
501
+ // it is too much of a breaking change to take back.
502
+ if borrowed_place_has_mut_interior && !place. is_indirect ( ) {
482
503
match self . const_kind ( ) {
483
504
// In a const fn all borrows are transient or point to the places given via
484
505
// references in the arguments (so we already checked them with
@@ -495,6 +516,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
495
516
// final value.
496
517
// Note: This is only sound if every local that has a `StorageDead` has a
497
518
// `StorageDead` in every control flow path leading to a `return` terminator.
519
+ // The good news is that interning will detect if any unexpected mutable
520
+ // pointer slips through.
498
521
if self . local_has_storage_dead ( place. local ) {
499
522
self . check_op ( ops:: TransientCellBorrow ) ;
500
523
} else {
@@ -948,6 +971,12 @@ fn place_as_reborrow<'tcx>(
948
971
) -> Option < PlaceRef < ' tcx > > {
949
972
match place. as_ref ( ) . last_projection ( ) {
950
973
Some ( ( place_base, ProjectionElem :: Deref ) ) => {
974
+ // FIXME: why do statics and raw pointers get excluded here? This makes
975
+ // some code involving mutable pointers unstable, but it is unclear
976
+ // why that code is treated differently from mutable references.
977
+ // Once TransientMutBorrow and TransientCellBorrow are stable,
978
+ // this can probably be cleaned up without any behavioral changes.
979
+
951
980
// A borrow of a `static` also looks like `&(*_1)` in the MIR, but `_1` is a `const`
952
981
// that points to the allocation for the static. Don't treat these as reborrows.
953
982
if body. local_decls [ place_base. local ] . is_ref_to_static ( ) {
0 commit comments