auto merge of #11585 : nikomatsakis/rust/issue-3511-rvalue-lifetimes, r=pcwalton

Major changes:

- Define temporary scopes in a syntax-based way that basically defaults
  to the innermost statement or conditional block, except for in
  a `let` initializer, where we default to the innermost block. Rules
  are documented in the code, but not in the manual (yet).
  See new test run-pass/cleanup-value-scopes.rs for examples.
- Refactors Datum to better define cleanup roles.
- Refactor cleanup scopes to not be tied to basic blocks, permitting
  us to have a very large number of scopes (one per AST node).
- Introduce nascent documentation in trans/doc.rs covering datums and
  cleanup in a more comprehensive way.

r? @pcwalton
This commit is contained in:
bors 2014-01-17 07:56:45 -08:00
commit 4098327b1f
72 changed files with 5064 additions and 3754 deletions

View file

@ -0,0 +1,22 @@
// Copyright 2014 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.
// Test lifetimes are linked properly when we take reference
// to interior.
struct Foo(int);
fn foo() -> &int {
let &Foo(ref x) = &Foo(3); //~ ERROR borrowed value does not live long enough
x
}
pub fn main() {
}

View file

@ -0,0 +1,31 @@
// Copyright 2014 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.
// Test that assignments to an `&mut` pointer which is found in a
// borrowed (but otherwise non-aliasable) location is illegal.
struct S<'a> {
pointer: &'a mut int
}
fn copy_borrowed_ptr<'a,'b>(p: &'a mut S<'b>) -> S<'b> {
S { pointer: &mut *p.pointer } //~ ERROR lifetime of `p` is too short to guarantee its contents can be safely reborrowed
}
fn main() {
let mut x = 1;
{
let mut y = S { pointer: &mut x };
let z = copy_borrowed_ptr(&mut y);
*y.pointer += 1;
*z.pointer += 1;
}
}

View file

@ -1,38 +0,0 @@
// 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.
// Tests that rvalue lifetimes is limited to the enclosing trans
// cleanup scope. It is unclear that this is the correct lifetime for
// rvalues, but that's what it is right now.
struct Counter {
value: uint
}
impl Counter {
fn new(v: uint) -> Counter {
Counter {value: v}
}
fn inc<'a>(&'a mut self) -> &'a mut Counter {
self.value += 1;
self
}
fn get(&self) -> uint {
self.value
}
}
pub fn main() {
let v = Counter::new(22).inc().inc().get();
//~^ ERROR borrowed value does not live long enough
assert_eq!(v, 24);;
}

View file

@ -0,0 +1,45 @@
// Copyright 2014 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.
// Test that the borrow checker prevents pointers to temporaries
// with statement lifetimes from escaping.
#[feature(macro_rules)];
use std::ops::Drop;
static mut FLAGS: u64 = 0;
struct Box<T> { f: T }
struct AddFlags { bits: u64 }
fn AddFlags(bits: u64) -> AddFlags {
AddFlags { bits: bits }
}
fn arg<'a>(x: &'a AddFlags) -> &'a AddFlags {
x
}
impl AddFlags {
fn get<'a>(&'a self) -> &'a AddFlags {
self
}
}
pub fn main() {
let _x = arg(&AddFlags(1)); //~ ERROR value does not live long enough
let _x = AddFlags(1).get(); //~ ERROR value does not live long enough
let _x = &*arg(&AddFlags(1)); //~ ERROR value does not live long enough
let ref _x = *arg(&AddFlags(1)); //~ ERROR value does not live long enough
let &ref _x = arg(&AddFlags(1)); //~ ERROR value does not live long enough
let _x = AddFlags(1).get(); //~ ERROR value does not live long enough
let Box { f: _x } = Box { f: AddFlags(1).get() }; //~ ERROR value does not live long enough
}

View file

@ -196,13 +196,13 @@ fn main() {
let Unit(ii) = Unit(51);
// univariant enum with ref binding
let Unit(ref jj) = Unit(52);
let &Unit(ref jj) = &Unit(52);
// tuple struct
let TupleStruct(kk, ll) = TupleStruct(53.0, 54);
let &TupleStruct(kk, ll) = &TupleStruct(53.0, 54);
// tuple struct with ref binding
let TupleStruct(mm, ref nn) = TupleStruct(55.0, 56);
let &TupleStruct(mm, ref nn) = &TupleStruct(55.0, 56);
zzz();
}

View file

@ -17,6 +17,15 @@ impl Counter {
Counter {value: v}
}
fn inc<'a>(&'a mut self) -> &'a mut Counter {
self.value += 1;
self
}
fn get(&self) -> uint {
self.value
}
fn get_and_inc(&mut self) -> uint {
let v = self.value;
self.value += 1;
@ -27,4 +36,7 @@ impl Counter {
pub fn main() {
let v = Counter::new(22).get_and_inc();
assert_eq!(v, 22);
let v = Counter::new(22).inc().inc().get();
assert_eq!(v, 24);;
}

View file

@ -0,0 +1,43 @@
// copyright 2014 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.
// Test that cleanup scope for temporaries created in a match
// arm is confined to the match arm itself.
use std::{os, run};
use std::io::process;
struct Test { x: int }
impl Test {
fn get_x(&self) -> Option<~int> {
Some(~self.x)
}
}
fn do_something(t: &Test) -> int {
// The cleanup scope for the result of `t.get_x()` should be the
// arm itself and not the match, otherwise we'll (potentially) get
// a crash trying to free an uninitialized stack slot.
match t {
&Test { x: 2 } if t.get_x().is_some() => {
t.x * 2
}
_ => { 22 }
}
}
pub fn main() {
let t = Test { x: 1 };
do_something(&t);
}

View file

@ -0,0 +1,70 @@
// Copyright 2014 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.
// Test that the lifetime of rvalues in for loops is extended
// to the for loop itself.
#[feature(macro_rules)];
use std::ops::Drop;
static mut FLAGS: u64 = 0;
struct Box<T> { f: T }
struct AddFlags { bits: u64 }
fn AddFlags(bits: u64) -> AddFlags {
AddFlags { bits: bits }
}
fn arg(exp: u64, _x: &AddFlags) {
check_flags(exp);
}
fn pass<T>(v: T) -> T {
v
}
fn check_flags(exp: u64) {
unsafe {
let x = FLAGS;
FLAGS = 0;
println!("flags {}, expected {}", x, exp);
assert_eq!(x, exp);
}
}
impl AddFlags {
fn check_flags<'a>(&'a self, exp: u64) -> &'a AddFlags {
check_flags(exp);
self
}
fn bits(&self) -> u64 {
self.bits
}
}
impl Drop for AddFlags {
fn drop(&mut self) {
unsafe {
FLAGS = FLAGS + self.bits;
}
}
}
pub fn main() {
// The array containing [AddFlags] should not be dropped until
// after the for loop:
for x in [AddFlags(1)].iter() {
check_flags(0);
}
check_flags(1);
}

View file

@ -0,0 +1,137 @@
// Copyright 2014 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.
// Test that destructors for rvalue temporaries run either at end of
// statement or end of block, as appropriate given the temporary
// lifetime rules.
#[feature(macro_rules)];
use std::ops::Drop;
static mut FLAGS: u64 = 0;
struct Box<T> { f: T }
struct AddFlags { bits: u64 }
fn AddFlags(bits: u64) -> AddFlags {
AddFlags { bits: bits }
}
fn arg(exp: u64, _x: &AddFlags) {
check_flags(exp);
}
fn pass<T>(v: T) -> T {
v
}
fn check_flags(exp: u64) {
unsafe {
let x = FLAGS;
FLAGS = 0;
println!("flags {}, expected {}", x, exp);
assert_eq!(x, exp);
}
}
impl AddFlags {
fn check_flags<'a>(&'a self, exp: u64) -> &'a AddFlags {
check_flags(exp);
self
}
fn bits(&self) -> u64 {
self.bits
}
}
impl Drop for AddFlags {
fn drop(&mut self) {
unsafe {
FLAGS = FLAGS + self.bits;
}
}
}
macro_rules! end_of_block(
($pat:pat, $expr:expr) => (
{
println!("end_of_block({})", stringify!({let $pat = $expr;}));
{
// Destructor here does not run until exit from the block.
let $pat = $expr;
check_flags(0);
}
check_flags(1);
}
)
)
macro_rules! end_of_stmt(
($pat:pat, $expr:expr) => (
{
println!("end_of_stmt({})", stringify!($expr));
{
// Destructor here run after `let` statement
// terminates.
let $pat = $expr;
check_flags(1);
}
check_flags(0);
}
)
)
pub fn main() {
// In all these cases, we trip over the rules designed to cover
// the case where we are taking addr of rvalue and storing that
// addr into a stack slot, either via `let ref` or via a `&` in
// the initializer.
end_of_block!(_x, AddFlags(1));
end_of_block!(_x, &AddFlags(1));
end_of_block!(_x, & &AddFlags(1));
end_of_block!(_x, Box { f: AddFlags(1) });
end_of_block!(_x, Box { f: &AddFlags(1) });
end_of_block!(_x, Box { f: &AddFlags(1) });
end_of_block!(_x, pass(AddFlags(1)));
end_of_block!(ref _x, AddFlags(1));
end_of_block!(AddFlags { bits: ref _x }, AddFlags(1));
end_of_block!(&AddFlags { bits }, &AddFlags(1));
end_of_block!((_, ref _y), (AddFlags(1), 22));
end_of_block!(~ref _x, ~AddFlags(1));
end_of_block!(~_x, ~AddFlags(1));
end_of_block!(_, { { check_flags(0); &AddFlags(1) } });
end_of_block!(_, &((Box { f: AddFlags(1) }).f));
end_of_block!(_, &(([AddFlags(1)])[0]));
end_of_block!(_, &((&~[AddFlags(1)])[0]));
// LHS does not create a ref binding, so temporary lives as long
// as statement, and we do not move the AddFlags out:
end_of_stmt!(_, AddFlags(1));
end_of_stmt!((_, _), (AddFlags(1), 22));
// `&` operator appears inside an arg to a function,
// so it is not prolonged:
end_of_stmt!(ref _x, arg(0, &AddFlags(1)));
// autoref occurs inside receiver, so temp lifetime is not
// prolonged:
end_of_stmt!(ref _x, AddFlags(1).check_flags(0).bits());
// No reference is created on LHS, thus RHS is moved into
// a temporary that lives just as long as the statement.
end_of_stmt!(AddFlags { bits }, AddFlags(1));
}

View file

@ -0,0 +1,39 @@
// Test cleanup of rvalue temporary that occurs while `~` construction
// is in progress. This scenario revealed a rather terrible bug. The
// ingredients are:
//
// 1. Partial cleanup of `~` is in scope,
// 2. cleanup of return value from `get_bar()` is in scope,
// 3. do_it() fails.
//
// This led to a bug because `the top-most frame that was to be
// cleaned (which happens to be the partial cleanup of `~`) required
// multiple basic blocks, which led to us dropping part of the cleanup
// from the top-most frame.
//
// It's unclear how likely such a bug is to recur, but it seems like a
// scenario worth testing.
use std::task;
enum Conzabble {
Bickwick(Foo)
}
struct Foo { field: ~uint }
fn do_it(x: &[uint]) -> Foo {
fail!()
}
fn get_bar(x: uint) -> ~[uint] { ~[x * 2] }
pub fn fails() {
let x = 2;
let mut y = ~[];
y.push(~Bickwick(do_it(get_bar(x))));
}
pub fn main() {
task::try(fails);
}

View file

@ -0,0 +1,30 @@
// copyright 2014 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.
// Test that cleanups for the RHS of shorcircuiting operators work.
use std::{os, run};
use std::io::process;
pub fn main() {
let args = os::args();
// Here, the rvalue `~"signal"` requires cleanup. Older versions
// of the code had a problem that the cleanup scope for this
// expression was the end of the `if`, and as the `~"signal"`
// expression was never evaluated, we wound up trying to clean
// uninitialized memory.
if args.len() >= 2 && args[1] == ~"signal" {
// Raise a segfault.
unsafe { *(0 as *mut int) = 0; }
}
}

View file

@ -8,20 +8,22 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[feature(managed_boxes)];
use std::cast::transmute;
mod rusti {
extern "rust-intrinsic" {
pub fn init<T>() -> T;
pub fn move_val_init<T>(dst: &mut T, src: T);
pub fn move_val<T>(dst: &mut T, src: T);
}
}
pub fn main() {
unsafe {
let x = @1;
let mut y = @2;
rusti::move_val(&mut y, x);
let x = ~1;
let mut y = rusti::init();
let mut z: *uint = transmute(&x);
rusti::move_val_init(&mut y, x);
assert_eq!(*y, 1);
assert_eq!(*z, 0); // `x` is nulled out, not directly visible
}
}

View file

@ -30,10 +30,10 @@ pub fn main () {
let config = process::ProcessConfig {
program : args[0].as_slice(),
args : [~"child"],
args : &[~"child"],
env : None,
cwd : None,
io : []
io : &[]
};
let mut p = process::Process::new(config).unwrap();

View file

@ -28,10 +28,10 @@ struct Thing2<'a> {
pub fn main() {
let _t1_fixed = Thing1 {
baz: [],
baz: &[],
bar: ~32,
};
let _t1_uniq = Thing1 {
Thing1 {
baz: ~[],
bar: ~32,
};
@ -40,10 +40,10 @@ pub fn main() {
bar: ~32,
};
let _t2_fixed = Thing2 {
baz: [],
baz: &[],
bar: 32,
};
let _t2_uniq = Thing2 {
Thing2 {
baz: ~[],
bar: 32,
};

View file

@ -15,6 +15,7 @@ struct Triple { x: int, y: int, z: int }
fn test(x: bool, foo: @Triple) -> int {
let bar = foo;
let mut y: @Triple;
y = bar;
if x { y = bar; } else { y = @Triple{x: 4, y: 5, z: 6}; }
return y.y;
}

View file

@ -0,0 +1,19 @@
// Copyright 2014 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.
// Test lifetimes are linked properly when we take reference
// to interior.
struct Foo(int);
pub fn main() {
// Here the lifetime of the `&` should be at least the
// block, since a ref binding is created to the interior.
let &Foo(ref _x) = &Foo(3);
}

View file

@ -0,0 +1,24 @@
// Copyright 2014 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.
// Test the uninit() construct returning various empty types.
use std::vec;
use std::unstable::intrinsics;
#[deriving(Clone)]
struct Foo;
pub fn main() {
unsafe {
let _x: Foo = intrinsics::uninit();
let _x: [Foo, ..2] = intrinsics::uninit();
}
}