Skip to content

Commit 09e12fa

Browse files
committed
Test that reborrowing contents of an &'a mut &'b mut pointer can only
be done for at most lifetime `'a` Fixes #8624
1 parent bc4164d commit 09e12fa

File tree

7 files changed

+278
-105
lines changed

7 files changed

+278
-105
lines changed

src/librustc/middle/borrowck/doc.rs

Lines changed: 142 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ the lifetime of the value being borrowed. This pass is also
233233
responsible for inserting root annotations to keep managed values
234234
alive and for dynamically freezing `@mut` boxes.
235235
236-
3. `RESTRICTIONS(LV, ACTIONS) = RS`: This pass checks and computes the
236+
3. `RESTRICTIONS(LV, LT, ACTIONS) = RS`: This pass checks and computes the
237237
restrictions to maintain memory safety. These are the restrictions
238238
that will go into the final loan. We'll discuss in more detail below.
239239
@@ -451,17 +451,17 @@ the scope `LT`.
451451
452452
The final rules govern the computation of *restrictions*, meaning that
453453
we compute the set of actions that will be illegal for the life of the
454-
loan. The predicate is written `RESTRICTIONS(LV, ACTIONS) =
454+
loan. The predicate is written `RESTRICTIONS(LV, LT, ACTIONS) =
455455
RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from
456456
occuring on `LV`, the restrictions `RESTRICTION*` must be respected
457457
for the lifetime of the loan".
458458
459459
Note that there is an initial set of restrictions: these restrictions
460460
are computed based on the kind of borrow:
461461
462-
&mut LV => RESTRICTIONS(LV, MUTATE|CLAIM|FREEZE)
463-
&LV => RESTRICTIONS(LV, MUTATE|CLAIM)
464-
&const LV => RESTRICTIONS(LV, [])
462+
&mut LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM|FREEZE)
463+
&LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM)
464+
&const LV => RESTRICTIONS(LV, LT, [])
465465
466466
The reasoning here is that a mutable borrow must be the only writer,
467467
therefore it prevents other writes (`MUTATE`), mutable borrows
@@ -474,7 +474,7 @@ moved out from under it, so no actions are forbidden.
474474
475475
The simplest case is a borrow of a local variable `X`:
476476
477-
RESTRICTIONS(X, ACTIONS) = (X, ACTIONS) // R-Variable
477+
RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable
478478
479479
In such cases we just record the actions that are not permitted.
480480
@@ -483,8 +483,8 @@ In such cases we just record the actions that are not permitted.
483483
Restricting a field is the same as restricting the owner of that
484484
field:
485485
486-
RESTRICTIONS(LV.f, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
487-
RESTRICTIONS(LV, ACTIONS) = RS
486+
RESTRICTIONS(LV.f, LT, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
487+
RESTRICTIONS(LV, LT, ACTIONS) = RS
488488
489489
The reasoning here is as follows. If the field must not be mutated,
490490
then you must not mutate the owner of the field either, since that
@@ -504,9 +504,9 @@ must prevent the owned pointer `LV` from being mutated, which means
504504
that we always add `MUTATE` and `CLAIM` to the restriction set imposed
505505
on `LV`:
506506
507-
RESTRICTIONS(*LV, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
507+
RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
508508
TYPE(LV) = ~Ty
509-
RESTRICTIONS(LV, ACTIONS|MUTATE|CLAIM) = RS
509+
RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS
510510
511511
### Restrictions for loans of immutable managed/borrowed pointees
512512
@@ -519,7 +519,7 @@ restricting that path. Therefore, the rule for `&Ty` and `@Ty`
519519
pointers always returns an empty set of restrictions, and it only
520520
permits restricting `MUTATE` and `CLAIM` actions:
521521
522-
RESTRICTIONS(*LV, ACTIONS) = [] // R-Deref-Imm-Borrowed
522+
RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
523523
TYPE(LV) = &Ty or @Ty
524524
ACTIONS subset of [MUTATE, CLAIM]
525525
@@ -546,7 +546,7 @@ Because moves from a `&const` or `@const` lvalue are never legal, it
546546
is not necessary to add any restrictions at all to the final
547547
result.
548548
549-
RESTRICTIONS(*LV, []) = [] // R-Deref-Freeze-Borrowed
549+
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed
550550
TYPE(LV) = &const Ty or @const Ty
551551
552552
### Restrictions for loans of mutable borrowed pointees
@@ -581,91 +581,157 @@ an `&mut` pointee from being mutated, claimed, or frozen, as occurs
581581
whenever the `&mut` pointee `*LV` is reborrowed as mutable or
582582
immutable:
583583
584-
RESTRICTIONS(*LV, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed-1
585-
TYPE(LV) = &mut Ty
586-
RESTRICTIONS(LV, MUTATE|CLAIM|ALIAS) = RS
587-
588-
The main interesting part of the rule is the final line, which
589-
requires that the `&mut` *pointer* `LV` be restricted from being
590-
mutated, claimed, or aliased. The goal of these restrictions is to
591-
ensure that, not considering the pointer that will result from this
592-
borrow, `LV` remains the *sole pointer with mutable access* to `*LV`.
593-
594-
Restrictions against mutations and claims are necessary because if the
595-
pointer in `LV` were to be somehow copied or moved to a different
596-
location, then the restriction issued for `*LV` would not apply to the
597-
new location. Note that because `&mut` values are non-copyable, a
598-
simple attempt to move the base pointer will fail due to the
599-
(implicit) restriction against moves:
600-
601-
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs
602-
fn foo(t0: &mut int) {
603-
let p: &int = &*t0; // Freezes `*t0`
604-
let t1 = t0; //~ ERROR cannot move out of `t0`
605-
*t1 = 22;
606-
}
607-
608-
However, the additional restrictions against mutation mean that even a
609-
clever attempt to use a swap to circumvent the type system will
610-
encounter an error:
611-
612-
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
613-
fn foo<'a>(mut t0: &'a mut int,
614-
mut t1: &'a mut int) {
615-
let p: &int = &*t0; // Freezes `*t0`
616-
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
617-
*t1 = 22;
618-
}
619-
620-
The restriction against *aliasing* (and, in turn, freezing) is
621-
necessary because, if an alias were of `LV` were to be produced, then
622-
`LV` would no longer be the sole path to access the `&mut`
623-
pointee. Since we are only issuing restrictions against `*LV`, these
624-
other aliases would be unrestricted, and the result would be
625-
unsound. For example:
584+
RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed-1
585+
TYPE(LV) = &LT' mut Ty
586+
LT <= LT' // (1)
587+
RESTRICTIONS(LV, LT, MUTATE|CLAIM|ALIAS) = RS // (2)
588+
589+
There are two interesting parts to this rule:
590+
591+
1. The lifetime of the loan (`LT`) cannot exceed the lifetime of the
592+
`&mut` pointer (`LT'`). The reason for this is that the `&mut`
593+
pointer is guaranteed to be the only legal way to mutate its
594+
pointee -- but only for the lifetime `LT'`. After that lifetime,
595+
the loan on the pointee expires and hence the data may be modified
596+
by its owner again. This implies that we are only able to guarantee that
597+
the pointee will not be modified or aliased for a maximum of `LT'`.
598+
599+
Here is a concrete example of a bug this rule prevents:
600+
601+
// Test region-reborrow-from-shorter-mut-ref.rs:
602+
fn copy_pointer<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T {
603+
&mut **p // ERROR due to clause (1)
604+
}
605+
fn main() {
606+
let mut x = 1;
607+
let mut y = &mut x; // <-'b-----------------------------+
608+
// +-'a--------------------+ |
609+
// v v |
610+
let z = copy_borrowed_ptr(&mut y); // y is lent |
611+
*y += 1; // Here y==z, so both should not be usable... |
612+
*z += 1; // ...and yet they would be, but for clause 1. |
613+
} <---------------------------------------------------------+
614+
615+
2. The final line recursively requires that the `&mut` *pointer* `LV`
616+
be restricted from being mutated, claimed, or aliased (not just the
617+
pointee). The goal of these restrictions is to ensure that, not
618+
considering the pointer that will result from this borrow, `LV`
619+
remains the *sole pointer with mutable access* to `*LV`.
620+
621+
Restrictions against claims are necessary because if the pointer in
622+
`LV` were to be somehow copied or moved to a different location,
623+
then the restriction issued for `*LV` would not apply to the new
624+
location. Note that because `&mut` values are non-copyable, a
625+
simple attempt to move the base pointer will fail due to the
626+
(implicit) restriction against moves:
627+
628+
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs
629+
fn foo(t0: &mut int) {
630+
let p: &int = &*t0; // Freezes `*t0`
631+
let t1 = t0; //~ ERROR cannot move out of `t0`
632+
*t1 = 22;
633+
}
634+
635+
However, the additional restrictions against claims mean that even
636+
a clever attempt to use a swap to circumvent the type system will
637+
encounter an error:
638+
639+
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
640+
fn foo<'a>(mut t0: &'a mut int,
641+
mut t1: &'a mut int) {
642+
let p: &int = &*t0; // Freezes `*t0`
643+
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
644+
*t1 = 22;
645+
}
646+
647+
The restriction against *aliasing* (and, in turn, freezing) is
648+
necessary because, if an alias were of `LV` were to be produced,
649+
then `LV` would no longer be the sole path to access the `&mut`
650+
pointee. Since we are only issuing restrictions against `*LV`,
651+
these other aliases would be unrestricted, and the result would be
652+
unsound. For example:
626653
627654
// src/test/compile-fail/borrowck-alias-mut-base-ptr.rs
628655
fn foo(t0: &mut int) {
629656
let p: &int = &*t0; // Freezes `*t0`
630657
let q: &const &mut int = &const t0; //~ ERROR cannot borrow `t0`
631-
**q = 22; // (*)
632-
}
633-
634-
Note that the current rules also report an error at the assignment in
635-
`(*)`, because we only permit `&mut` poiners to be assigned if they
636-
are located in a non-aliasable location. However, I do not believe
637-
this restriction is strictly necessary. It was added, I believe, to
638-
discourage `&mut` from being placed in aliasable locations in the
639-
first place. One (desirable) side-effect of restricting aliasing on
640-
`LV` is that borrowing an `&mut` pointee found inside an aliasable
641-
pointee yields an error:
642-
643-
// src/test/compile-fail/borrowck-borrow-mut-base-ptr-in-aliasable-loc:
644-
fn foo(t0: & &mut int) {
645-
let t1 = t0;
646-
let p: &int = &**t0; //~ ERROR cannot borrow an `&mut` in a `&` pointer
647-
**t1 = 22; // (*)
658+
**q = 22;
648659
}
649660
650-
Here at the line `(*)` you will also see the error I referred to
651-
above, which I do not believe is strictly necessary.
661+
The current rules could use some correction:
662+
663+
1. Issue #10520. Now that the swap operator has been removed, I do not
664+
believe the restriction against mutating `LV` is needed, and in
665+
fact it prevents some useful patterns. For example, the following
666+
function will fail to compile:
667+
668+
fn mut_shift_ref<'a,T>(x: &mut &'a mut [T]) -> &'a mut T {
669+
// `mut_split` will restrict mutation against *x:
670+
let (head, tail) = (*x).mut_split(1);
671+
672+
// Hence mutating `*x` yields an error here:
673+
*x = tail;
674+
&mut head[0]
675+
}
676+
677+
Note that this function -- which adjusts the slice `*x` in place so
678+
that it no longer contains the head element and then returns a
679+
pointer to that element separately -- is perfectly valid. It is
680+
currently implemented using unsafe code. I believe that now that
681+
the swap operator is removed from the language, we could liberalize
682+
the rules and make this function be accepted normally. The idea
683+
would be to have the assignment to `*x` kill the loans of `*x` and
684+
its subpaths -- after all, those subpaths are no longer accessible
685+
through `*x`, since it has been overwritten with a new value. Thus
686+
those subpaths are only accessible through prior existing borrows
687+
of `*x`, if any. The danger of the *swap* operator was that it
688+
allowed `*x` to be mutated without making the subpaths of `*x`
689+
inaccessible: worse, they became accessible through a new path (I
690+
suppose that we could support swap, too, if needed, by moving the
691+
loans over to the new path).
692+
693+
Note: the `swap()` function doesn't pose the same danger as the
694+
swap operator because it requires taking `&mut` refs to invoke it.
695+
696+
2. Issue #9629. The current rules correctly prohibit `&mut` pointees
697+
from being assigned unless they are in a unique location. However,
698+
we *also* prohibit `&mut` pointees from being frozen. This prevents
699+
compositional patterns, like this one:
700+
701+
struct BorrowedMap<'a> {
702+
map: &'a mut HashMap
703+
}
704+
705+
If we have a pointer `x:&BorrowedMap`, we can't freeze `x.map`,
706+
and hence can't call `find` etc on it. But that's silly, since
707+
fact that the `&mut` exists in frozen data implies that it
708+
will not be mutable by anyone. For example, this program nets an
709+
error:
710+
711+
fn main() {
712+
let a = &mut 2;
713+
let b = &a;
714+
*a += 1; // ERROR: cannot assign to `*a` because it is borrowed
715+
}
716+
717+
(Naturally `&mut` reborrows from an `&&mut` pointee should be illegal.)
652718
653719
The second rule for `&mut` handles the case where we are not adding
654720
any restrictions (beyond the default of "no move"):
655721
656-
RESTRICTIONS(*LV, []) = [] // R-Deref-Mut-Borrowed-2
722+
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Mut-Borrowed-2
657723
TYPE(LV) = &mut Ty
658724
659725
Moving from an `&mut` pointee is never legal, so no special
660-
restrictions are needed.
726+
restrictions are needed. This rule is used for `&const` borrows.
661727
662728
### Restrictions for loans of mutable managed pointees
663729
664730
With `@mut` pointees, we don't make any static guarantees. But as a
665731
convenience, we still register a restriction against `*LV`, because
666732
that way if we *can* find a simple static error, we will:
667733
668-
RESTRICTIONS(*LV, ACTIONS) = [*LV, ACTIONS] // R-Deref-Managed-Borrowed
734+
RESTRICTIONS(*LV, LT, ACTIONS) = [*LV, ACTIONS] // R-Deref-Managed-Borrowed
669735
TYPE(LV) = @mut Ty
670736
671737
# Moves and initialization

0 commit comments

Comments
 (0)