Test that reborrowing contents of an `&'a mut &'b mut` pointer can only

be done for at most lifetime `'a`

Fixes #8624
This commit is contained in:
Niko Matsakis 2013-11-16 17:30:45 -05:00
parent bc4164d4c0
commit 09e12fa553
7 changed files with 270 additions and 97 deletions

View File

@ -233,7 +233,7 @@ the lifetime of the value being borrowed. This pass is also
responsible for inserting root annotations to keep managed values
alive and for dynamically freezing `@mut` boxes.
3. `RESTRICTIONS(LV, ACTIONS) = RS`: This pass checks and computes the
3. `RESTRICTIONS(LV, LT, ACTIONS) = RS`: This pass checks and computes the
restrictions to maintain memory safety. These are the restrictions
that will go into the final loan. We'll discuss in more detail below.
@ -451,7 +451,7 @@ the scope `LT`.
The final rules govern the computation of *restrictions*, meaning that
we compute the set of actions that will be illegal for the life of the
loan. The predicate is written `RESTRICTIONS(LV, ACTIONS) =
loan. The predicate is written `RESTRICTIONS(LV, LT, ACTIONS) =
RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from
occuring on `LV`, the restrictions `RESTRICTION*` must be respected
for the lifetime of the loan".
@ -459,9 +459,9 @@ for the lifetime of the loan".
Note that there is an initial set of restrictions: these restrictions
are computed based on the kind of borrow:
&mut LV => RESTRICTIONS(LV, MUTATE|CLAIM|FREEZE)
&LV => RESTRICTIONS(LV, MUTATE|CLAIM)
&const LV => RESTRICTIONS(LV, [])
&mut LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM|FREEZE)
&LV => RESTRICTIONS(LV, LT, MUTATE|CLAIM)
&const LV => RESTRICTIONS(LV, LT, [])
The reasoning here is that a mutable borrow must be the only writer,
therefore it prevents other writes (`MUTATE`), mutable borrows
@ -474,7 +474,7 @@ moved out from under it, so no actions are forbidden.
The simplest case is a borrow of a local variable `X`:
RESTRICTIONS(X, ACTIONS) = (X, ACTIONS) // R-Variable
RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable
In such cases we just record the actions that are not permitted.
@ -483,8 +483,8 @@ In such cases we just record the actions that are not permitted.
Restricting a field is the same as restricting the owner of that
field:
RESTRICTIONS(LV.f, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
RESTRICTIONS(LV, ACTIONS) = RS
RESTRICTIONS(LV.f, LT, ACTIONS) = RS, (LV.f, ACTIONS) // R-Field
RESTRICTIONS(LV, LT, ACTIONS) = RS
The reasoning here is as follows. If the field must not be mutated,
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
that we always add `MUTATE` and `CLAIM` to the restriction set imposed
on `LV`:
RESTRICTIONS(*LV, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Send-Pointer
TYPE(LV) = ~Ty
RESTRICTIONS(LV, ACTIONS|MUTATE|CLAIM) = RS
RESTRICTIONS(LV, LT, ACTIONS|MUTATE|CLAIM) = RS
### Restrictions for loans of immutable managed/borrowed pointees
@ -519,7 +519,7 @@ restricting that path. Therefore, the rule for `&Ty` and `@Ty`
pointers always returns an empty set of restrictions, and it only
permits restricting `MUTATE` and `CLAIM` actions:
RESTRICTIONS(*LV, ACTIONS) = [] // R-Deref-Imm-Borrowed
RESTRICTIONS(*LV, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
TYPE(LV) = &Ty or @Ty
ACTIONS subset of [MUTATE, CLAIM]
@ -546,7 +546,7 @@ Because moves from a `&const` or `@const` lvalue are never legal, it
is not necessary to add any restrictions at all to the final
result.
RESTRICTIONS(*LV, []) = [] // R-Deref-Freeze-Borrowed
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Freeze-Borrowed
TYPE(LV) = &const Ty or @const Ty
### Restrictions for loans of mutable borrowed pointees
@ -581,83 +581,149 @@ an `&mut` pointee from being mutated, claimed, or frozen, as occurs
whenever the `&mut` pointee `*LV` is reborrowed as mutable or
immutable:
RESTRICTIONS(*LV, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed-1
TYPE(LV) = &mut Ty
RESTRICTIONS(LV, MUTATE|CLAIM|ALIAS) = RS
RESTRICTIONS(*LV, LT, ACTIONS) = RS, (*LV, ACTIONS) // R-Deref-Mut-Borrowed-1
TYPE(LV) = &LT' mut Ty
LT <= LT' // (1)
RESTRICTIONS(LV, LT, MUTATE|CLAIM|ALIAS) = RS // (2)
The main interesting part of the rule is the final line, which
requires that the `&mut` *pointer* `LV` be restricted from being
mutated, claimed, or aliased. The goal of these restrictions is to
ensure that, not considering the pointer that will result from this
borrow, `LV` remains the *sole pointer with mutable access* to `*LV`.
There are two interesting parts to this rule:
Restrictions against mutations and claims are necessary because if the
pointer in `LV` were to be somehow copied or moved to a different
location, then the restriction issued for `*LV` would not apply to the
new location. Note that because `&mut` values are non-copyable, a
simple attempt to move the base pointer will fail due to the
(implicit) restriction against moves:
1. The lifetime of the loan (`LT`) cannot exceed the lifetime of the
`&mut` pointer (`LT'`). The reason for this is that the `&mut`
pointer is guaranteed to be the only legal way to mutate its
pointee -- but only for the lifetime `LT'`. After that lifetime,
the loan on the pointee expires and hence the data may be modified
by its owner again. This implies that we are only able to guarantee that
the pointee will not be modified or aliased for a maximum of `LT'`.
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs
fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0`
let t1 = t0; //~ ERROR cannot move out of `t0`
*t1 = 22;
}
Here is a concrete example of a bug this rule prevents:
However, the additional restrictions against mutation mean that even a
clever attempt to use a swap to circumvent the type system will
encounter an error:
// Test region-reborrow-from-shorter-mut-ref.rs:
fn copy_pointer<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T {
&mut **p // ERROR due to clause (1)
}
fn main() {
let mut x = 1;
let mut y = &mut x; // <-'b-----------------------------+
// +-'a--------------------+ |
// v v |
let z = copy_borrowed_ptr(&mut y); // y is lent |
*y += 1; // Here y==z, so both should not be usable... |
*z += 1; // ...and yet they would be, but for clause 1. |
} <---------------------------------------------------------+
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
*t1 = 22;
}
2. The final line recursively requires that the `&mut` *pointer* `LV`
be restricted from being mutated, claimed, or aliased (not just the
pointee). The goal of these restrictions is to ensure that, not
considering the pointer that will result from this borrow, `LV`
remains the *sole pointer with mutable access* to `*LV`.
The restriction against *aliasing* (and, in turn, freezing) is
necessary because, if an alias were of `LV` were to be produced, then
`LV` would no longer be the sole path to access the `&mut`
pointee. Since we are only issuing restrictions against `*LV`, these
other aliases would be unrestricted, and the result would be
unsound. For example:
Restrictions against claims are necessary because if the pointer in
`LV` were to be somehow copied or moved to a different location,
then the restriction issued for `*LV` would not apply to the new
location. Note that because `&mut` values are non-copyable, a
simple attempt to move the base pointer will fail due to the
(implicit) restriction against moves:
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs
fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0`
let t1 = t0; //~ ERROR cannot move out of `t0`
*t1 = 22;
}
However, the additional restrictions against claims mean that even
a clever attempt to use a swap to circumvent the type system will
encounter an error:
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
*t1 = 22;
}
The restriction against *aliasing* (and, in turn, freezing) is
necessary because, if an alias were of `LV` were to be produced,
then `LV` would no longer be the sole path to access the `&mut`
pointee. Since we are only issuing restrictions against `*LV`,
these other aliases would be unrestricted, and the result would be
unsound. For example:
// src/test/compile-fail/borrowck-alias-mut-base-ptr.rs
fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0`
let q: &const &mut int = &const t0; //~ ERROR cannot borrow `t0`
**q = 22; // (*)
**q = 22;
}
Note that the current rules also report an error at the assignment in
`(*)`, because we only permit `&mut` poiners to be assigned if they
are located in a non-aliasable location. However, I do not believe
this restriction is strictly necessary. It was added, I believe, to
discourage `&mut` from being placed in aliasable locations in the
first place. One (desirable) side-effect of restricting aliasing on
`LV` is that borrowing an `&mut` pointee found inside an aliasable
pointee yields an error:
The current rules could use some correction:
// src/test/compile-fail/borrowck-borrow-mut-base-ptr-in-aliasable-loc:
fn foo(t0: & &mut int) {
let t1 = t0;
let p: &int = &**t0; //~ ERROR cannot borrow an `&mut` in a `&` pointer
**t1 = 22; // (*)
}
1. Issue #10520. Now that the swap operator has been removed, I do not
believe the restriction against mutating `LV` is needed, and in
fact it prevents some useful patterns. For example, the following
function will fail to compile:
Here at the line `(*)` you will also see the error I referred to
above, which I do not believe is strictly necessary.
fn mut_shift_ref<'a,T>(x: &mut &'a mut [T]) -> &'a mut T {
// `mut_split` will restrict mutation against *x:
let (head, tail) = (*x).mut_split(1);
// Hence mutating `*x` yields an error here:
*x = tail;
&mut head[0]
}
Note that this function -- which adjusts the slice `*x` in place so
that it no longer contains the head element and then returns a
pointer to that element separately -- is perfectly valid. It is
currently implemented using unsafe code. I believe that now that
the swap operator is removed from the language, we could liberalize
the rules and make this function be accepted normally. The idea
would be to have the assignment to `*x` kill the loans of `*x` and
its subpaths -- after all, those subpaths are no longer accessible
through `*x`, since it has been overwritten with a new value. Thus
those subpaths are only accessible through prior existing borrows
of `*x`, if any. The danger of the *swap* operator was that it
allowed `*x` to be mutated without making the subpaths of `*x`
inaccessible: worse, they became accessible through a new path (I
suppose that we could support swap, too, if needed, by moving the
loans over to the new path).
Note: the `swap()` function doesn't pose the same danger as the
swap operator because it requires taking `&mut` refs to invoke it.
2. Issue #9629. The current rules correctly prohibit `&mut` pointees
from being assigned unless they are in a unique location. However,
we *also* prohibit `&mut` pointees from being frozen. This prevents
compositional patterns, like this one:
struct BorrowedMap<'a> {
map: &'a mut HashMap
}
If we have a pointer `x:&BorrowedMap`, we can't freeze `x.map`,
and hence can't call `find` etc on it. But that's silly, since
fact that the `&mut` exists in frozen data implies that it
will not be mutable by anyone. For example, this program nets an
error:
fn main() {
let a = &mut 2;
let b = &a;
*a += 1; // ERROR: cannot assign to `*a` because it is borrowed
}
(Naturally `&mut` reborrows from an `&&mut` pointee should be illegal.)
The second rule for `&mut` handles the case where we are not adding
any restrictions (beyond the default of "no move"):
RESTRICTIONS(*LV, []) = [] // R-Deref-Mut-Borrowed-2
RESTRICTIONS(*LV, LT, []) = [] // R-Deref-Mut-Borrowed-2
TYPE(LV) = &mut Ty
Moving from an `&mut` pointee is never legal, so no special
restrictions are needed.
restrictions are needed. This rule is used for `&const` borrows.
### Restrictions for loans of mutable managed pointees
@ -665,7 +731,7 @@ With `@mut` pointees, we don't make any static guarantees. But as a
convenience, we still register a restriction against `*LV`, because
that way if we *can* find a simple static error, we will:
RESTRICTIONS(*LV, ACTIONS) = [*LV, ACTIONS] // R-Deref-Managed-Borrowed
RESTRICTIONS(*LV, LT, ACTIONS) = [*LV, ACTIONS] // R-Deref-Managed-Borrowed
TYPE(LV) = @mut Ty
# Moves and initialization

View File

@ -8,9 +8,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! This module implements the check that the lifetime of a borrow
//! does not exceed the lifetime of the value being borrowed.
/*!
* This module implements the check that the lifetime of a borrow
* does not exceed the lifetime of the value being borrowed.
*/
use middle::borrowck::*;
use mc = middle::mem_categorization;
@ -20,13 +21,15 @@ use syntax::ast;
use syntax::codemap::Span;
use util::ppaux::{note_and_explain_region};
type R = Result<(),()>;
pub fn guarantee_lifetime(bccx: &BorrowckCtxt,
item_scope_id: ast::NodeId,
root_scope_id: ast::NodeId,
span: Span,
cmt: mc::cmt,
loan_region: ty::Region,
loan_mutbl: LoanMutability) {
loan_mutbl: LoanMutability) -> R {
debug!("guarantee_lifetime(cmt={}, loan_region={})",
cmt.repr(bccx.tcx), loan_region.repr(bccx.tcx));
let ctxt = GuaranteeLifetimeContext {bccx: bccx,
@ -36,7 +39,7 @@ pub fn guarantee_lifetime(bccx: &BorrowckCtxt,
loan_mutbl: loan_mutbl,
cmt_original: cmt,
root_scope_id: root_scope_id};
ctxt.check(cmt, None);
ctxt.check(cmt, None)
}
///////////////////////////////////////////////////////////////////////////
@ -63,7 +66,7 @@ impl<'self> GuaranteeLifetimeContext<'self> {
self.bccx.tcx
}
fn check(&self, cmt: mc::cmt, discr_scope: Option<ast::NodeId>) {
fn check(&self, cmt: mc::cmt, discr_scope: Option<ast::NodeId>) -> R {
//! Main routine. Walks down `cmt` until we find the "guarantor".
match cmt.cat {
@ -83,6 +86,7 @@ impl<'self> GuaranteeLifetimeContext<'self> {
}
mc::cat_static_item => {
Ok(())
}
mc::cat_deref(base, derefs, mc::gc_ptr(ptr_mutbl)) => {
@ -99,10 +103,11 @@ impl<'self> GuaranteeLifetimeContext<'self> {
if !omit_root {
// L-Deref-Managed-Imm-Compiler-Root
// L-Deref-Managed-Mut-Compiler-Root
self.check_root(cmt, base, derefs, ptr_mutbl, discr_scope);
self.check_root(cmt, base, derefs, ptr_mutbl, discr_scope)
} else {
debug!("omitting root, base={}, base_scope={:?}",
base.repr(self.tcx()), base_scope);
Ok(())
}
}
@ -120,7 +125,7 @@ impl<'self> GuaranteeLifetimeContext<'self> {
// for one arm. Therefore, we insert a cat_discr(),
// basically a special kind of category that says "if this
// value must be dynamically rooted, root it for the scope
// `match_id`.
// `match_id`".
//
// As an example, consider this scenario:
//
@ -188,7 +193,7 @@ impl<'self> GuaranteeLifetimeContext<'self> {
cmt_base: mc::cmt,
derefs: uint,
ptr_mutbl: ast::Mutability,
discr_scope: Option<ast::NodeId>) {
discr_scope: Option<ast::NodeId>) -> R {
debug!("check_root(cmt_deref={}, cmt_base={}, derefs={:?}, ptr_mutbl={:?}, \
discr_scope={:?})",
cmt_deref.repr(self.tcx()),
@ -201,9 +206,8 @@ impl<'self> GuaranteeLifetimeContext<'self> {
// that we can root the value, dynamically.
let root_region = ty::ReScope(self.root_scope_id);
if !self.bccx.is_subregion_of(self.loan_region, root_region) {
self.report_error(
err_out_of_root_scope(root_region, self.loan_region));
return;
return Err(self.report_error(
err_out_of_root_scope(root_region, self.loan_region)));
}
// Extract the scope id that indicates how long the rooting is required
@ -278,13 +282,16 @@ impl<'self> GuaranteeLifetimeContext<'self> {
self.bccx.root_map.insert(rm_key, root_info);
debug!("root_key: {:?} root_info: {:?}", rm_key, root_info);
Ok(())
}
fn check_scope(&self, max_scope: ty::Region) {
fn check_scope(&self, max_scope: ty::Region) -> R {
//! Reports an error if `loan_region` is larger than `valid_scope`
if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
self.report_error(err_out_of_scope(max_scope, self.loan_region));
Err(self.report_error(err_out_of_scope(max_scope, self.loan_region)))
} else {
Ok(())
}
}

View File

@ -449,17 +449,22 @@ impl<'self> GatherLoanCtxt<'self> {
// Check that the lifetime of the borrow does not exceed
// the lifetime of the data being borrowed.
lifetime::guarantee_lifetime(self.bccx, self.item_ub, root_ub,
borrow_span, cmt, loan_region, req_mutbl);
if lifetime::guarantee_lifetime(self.bccx, self.item_ub, root_ub,
borrow_span, cmt, loan_region,
req_mutbl).is_err() {
return; // reported an error, no sense in reporting more.
}
// Check that we don't allow mutable borrows of non-mutable data.
check_mutability(self.bccx, borrow_span, cmt, req_mutbl);
if check_mutability(self.bccx, borrow_span, cmt, req_mutbl).is_err() {
return; // reported an error, no sense in reporting more.
}
// Compute the restrictions that are required to enforce the
// loan is safe.
let restr = restrictions::compute_restrictions(
self.bccx, borrow_span,
cmt, self.restriction_set(req_mutbl));
cmt, loan_region, self.restriction_set(req_mutbl));
// Create the loan record (if needed).
let loan = match restr {
@ -556,25 +561,29 @@ impl<'self> GatherLoanCtxt<'self> {
fn check_mutability(bccx: &BorrowckCtxt,
borrow_span: Span,
cmt: mc::cmt,
req_mutbl: LoanMutability) {
req_mutbl: LoanMutability) -> Result<(),()> {
//! Implements the M-* rules in doc.rs.
match req_mutbl {
ConstMutability => {
// Data of any mutability can be lent as const.
Ok(())
}
ImmutableMutability => {
// both imm and mut data can be lent as imm;
// for mutable data, this is a freeze
Ok(())
}
MutableMutability => {
// Only mutable data can be lent as mutable.
if !cmt.mutbl.is_mutable() {
bccx.report(BckError {span: borrow_span,
cmt: cmt,
code: err_mutbl(req_mutbl)});
Err(bccx.report(BckError {span: borrow_span,
cmt: cmt,
code: err_mutbl(req_mutbl)}))
} else {
Ok(())
}
}
}

View File

@ -8,8 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Computes the restrictions that result from a borrow.
/*!
* Computes the restrictions that result from a borrow.
*/
use std::vec;
use middle::borrowck::*;
@ -26,11 +27,13 @@ pub enum RestrictionResult {
pub fn compute_restrictions(bccx: &BorrowckCtxt,
span: Span,
cmt: mc::cmt,
loan_region: ty::Region,
restr: RestrictionSet) -> RestrictionResult {
let ctxt = RestrictionsContext {
bccx: bccx,
span: span,
cmt_original: cmt
cmt_original: cmt,
loan_region: loan_region,
};
ctxt.restrict(cmt, restr)
@ -42,7 +45,8 @@ pub fn compute_restrictions(bccx: &BorrowckCtxt,
struct RestrictionsContext<'self> {
bccx: &'self BorrowckCtxt,
span: Span,
cmt_original: mc::cmt
cmt_original: mc::cmt,
loan_region: ty::Region,
}
impl<'self> RestrictionsContext<'self> {
@ -169,12 +173,22 @@ impl<'self> RestrictionsContext<'self> {
}
}
mc::cat_deref(cmt_base, _, pk @ mc::region_ptr(MutMutable, _)) => {
mc::cat_deref(cmt_base, _, pk @ mc::region_ptr(MutMutable, lt)) => {
// Because an `&mut` pointer does not inherit its
// mutability, we can only prevent mutation or prevent
// freezing if it is not aliased. Therefore, in such
// cases we restrict aliasing on `cmt_base`.
if restrictions != RESTR_EMPTY {
if !self.bccx.is_subregion_of(self.loan_region, lt) {
self.bccx.report(
BckError {
span: self.span,
cmt: cmt_base,
code: err_mut_pointer_too_short(
self.loan_region, lt, restrictions)});
return Safe;
}
// R-Deref-Mut-Borrowed-1
let result = self.restrict(
cmt_base,

View File

@ -443,7 +443,8 @@ pub enum bckerr_code {
err_mutbl(LoanMutability),
err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope
err_out_of_scope(ty::Region, ty::Region), // superscope, subscope
err_freeze_aliasable_const
err_freeze_aliasable_const,
err_mut_pointer_too_short(ty::Region, ty::Region, RestrictionSet), // loan, ptr
}
// Combination of an error code and the categorization of the expression
@ -669,6 +670,22 @@ impl BorrowckCtxt {
// supposed to be going away.
format!("unsafe borrow of aliasable, const value")
}
err_mut_pointer_too_short(_, _, r) => {
let descr = match opt_loan_path(err.cmt) {
Some(lp) => format!("`{}`", self.loan_path_to_str(lp)),
None => ~"`&mut` pointer"
};
let tag = if r.intersects(RESTR_ALIAS) {
"its contents are unique"
} else {
"its contents are not otherwise mutable"
};
format!("lifetime of {} is too short to guarantee {} \
so they can be safely reborrowed",
descr, tag)
}
}
}
@ -742,7 +759,24 @@ impl BorrowckCtxt {
"...but borrowed value is only valid for ",
super_scope,
"");
}
}
err_mut_pointer_too_short(loan_scope, ptr_scope, _) => {
let descr = match opt_loan_path(err.cmt) {
Some(lp) => format!("`{}`", self.loan_path_to_str(lp)),
None => ~"`&mut` pointer"
};
note_and_explain_region(
self.tcx,
format!("{} would have to be valid for ", descr),
loan_scope,
"...");
note_and_explain_region(
self.tcx,
format!("...but {} is only valid for ", descr),
ptr_scope,
"");
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Issue #8624. Test for reborrowing with 3 levels, not just two.
fn copy_borrowed_ptr<'a, 'b, 'c>(p: &'a mut &'b mut &'c mut int) -> &'b mut int {
&mut ***p //~ ERROR cannot infer an appropriate lifetime
}
fn main() {
}

View File

@ -0,0 +1,25 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Issue #8624. Tests that reborrowing the contents of an `&'b mut`
// pointer which is backed by another `&'a mut` can only be done
// for `'a` (which must be a sublifetime of `'b`).
fn copy_borrowed_ptr<'a, 'b>(p: &'a mut &'b mut int) -> &'b mut int {
&mut **p //~ ERROR lifetime of `p` is too short
}
fn main() {
let mut x = 1;
let mut y = &mut x;
let z = copy_borrowed_ptr(&mut y);
*y += 1;
*z += 1;
}