Fix #2979: inference for lifetimes of & expressions

What we now do is to create a region variable for each &
expression (and also each borrow).  The lifetime of this
variable will be checked by borrowck to ensure it is not greater
than the lifetime of the underlying data.  This both leads to
shorter lifetimes in some cases but also longer in others,
such as taking the address to the interior of unique boxes
tht are rooted in region pointers (e.g., returning a pointer
to the interior of a sendable map).

This may lead to issue #2977 if the rvalue is not POD, because
we may drop the data in trans sooner than borrowck expects us
to.  Need to work out precisely where that fix ought to occur.
This commit is contained in:
Niko Matsakis 2012-07-26 08:51:57 -07:00
parent 6ef13e76e9
commit 5d32d03b89
34 changed files with 717 additions and 320 deletions

View file

@ -436,21 +436,6 @@ impl of tr for method_origin {
}
}
// ______________________________________________________________________
// Encoding and decoding of borrow
trait read_borrow_helper {
fn read_borrow(xcx: extended_decode_ctxt) -> ty::borrow;
}
impl helper of read_borrow_helper for ebml::ebml_deserializer {
fn read_borrow(xcx: extended_decode_ctxt) -> ty::borrow {
let borrow = ty::deserialize_borrow(self);
{scope_id: xcx.tr_id(borrow.scope_id),
mutbl: borrow.mutbl}
}
}
// ______________________________________________________________________
// Encoding and decoding vtable_res
@ -766,11 +751,13 @@ fn encode_side_tables_for_id(ecx: @e::encode_ctxt,
}
}
do option::iter(tcx.borrowings.find(id)) |borrow| {
do option::iter(tcx.borrowings.find(id)) |_borrow| {
do ebml_w.tag(c::tag_table_borrowings) {
ebml_w.id(id);
do ebml_w.tag(c::tag_table_val) {
ty::serialize_borrow(ebml_w, borrow)
// N.B. We don't actually serialize borrows as, in
// trans, we only care whether a value is borrowed or
// not.
}
}
}
@ -890,7 +877,10 @@ fn decode_side_tables(xcx: extended_decode_ctxt,
dcx.maps.vtable_map.insert(id,
val_dsr.read_vtable_res(xcx));
} else if tag == (c::tag_table_borrowings as uint) {
let borrow = val_dsr.read_borrow(xcx);
// N.B.: we don't actually *serialize* borrows because, in
// trans, the only thing we care about is whether a value was
// borrowed or not.
let borrow = {region: ty::re_static, mutbl: ast::m_imm};
dcx.tcx.borrowings.insert(id, borrow);
} else {
xcx.dcx.tcx.sess.bug(

View file

@ -321,9 +321,10 @@ type binding_map = std::map::hashmap<ast::node_id, ast::mutability>;
enum bckerr_code {
err_mut_uniq,
err_mut_variant,
err_preserve_gc,
err_mutbl(ast::mutability,
ast::mutability)
err_root_not_permitted,
err_mutbl(ast::mutability, ast::mutability),
err_out_of_root_scope(ty::region, ty::region), // superscope, subscope
err_out_of_scope(ty::region, ty::region) // superscope, subscope
}
// Combination of an error code and the categorization of the expression
@ -346,7 +347,7 @@ enum categorization {
}
// different kinds of pointers:
enum ptr_kind {uniq_ptr, gc_ptr, region_ptr, unsafe_ptr}
enum ptr_kind {uniq_ptr, gc_ptr, region_ptr(ty::region), unsafe_ptr}
// I am coining the term "components" to mean "pieces of a data
// structure accessible without a dereference":
@ -391,10 +392,15 @@ enum loan_path {
lp_comp(@loan_path, comp_kind)
}
// a complete record of a loan that was granted
/// a complete record of a loan that was granted
type loan = {lp: @loan_path, cmt: cmt, mutbl: ast::mutability};
// maps computed by `gather_loans` that are then used by `check_loans`
/// maps computed by `gather_loans` that are then used by `check_loans`
///
/// - `req_loan_map`: map from each block/expr to the required loans needed
/// for the duration of that block/expr
/// - `pure_map`: map from block/expr that must be pure to the error message
/// that should be reported if they are not pure
type req_maps = {
req_loan_map: hashmap<ast::node_id, @dvec<@dvec<loan>>>,
pure_map: hashmap<ast::node_id, bckerr>
@ -519,7 +525,7 @@ impl to_str_methods for borrowck_ctxt {
alt ptr {
uniq_ptr { ~"~" }
gc_ptr { ~"@" }
region_ptr { ~"&" }
region_ptr(_) { ~"&" }
unsafe_ptr { ~"*" }
}
}
@ -561,15 +567,6 @@ impl to_str_methods for borrowck_ctxt {
ty_to_str(self.tcx, cmt.ty)]
}
fn pk_to_sigil(pk: ptr_kind) -> ~str {
alt pk {
uniq_ptr {~"~"}
gc_ptr {~"@"}
region_ptr {~"&"}
unsafe_ptr {~"*"}
}
}
fn cmt_to_str(cmt: cmt) -> ~str {
let mut_str = self.mut_to_str(cmt.mutbl);
alt cmt.cat {
@ -584,7 +581,7 @@ impl to_str_methods for borrowck_ctxt {
cat_binding(_) { ~"pattern binding" }
cat_arg(_) { ~"argument" }
cat_deref(_, _, pk) { #fmt["dereference of %s %s pointer",
mut_str, self.pk_to_sigil(pk)] }
mut_str, self.ptr_sigil(pk)] }
cat_stack_upvar(_) {
~"captured outer " + mut_str + ~" variable in a stack closure"
}
@ -622,12 +619,30 @@ impl to_str_methods for borrowck_ctxt {
err_mut_variant {
~"enum variant in aliasable, mutable location"
}
err_preserve_gc {
~"GC'd value would have to be preserved for longer \
than the scope of the function"
err_root_not_permitted {
// note: I don't expect users to ever see this error
// message, reasons are discussed in attempt_root() in
// preserve.rs.
~"rooting is not permitted"
}
err_out_of_root_scope(super_scope, sub_scope) {
#fmt["managed value would have to be rooted for lifetime %s, \
but can only be rooted for lifetime %s",
self.region_to_str(sub_scope),
self.region_to_str(super_scope)]
}
err_out_of_scope(super_scope, sub_scope) {
#fmt["borrowed pointer has lifetime %s, \
but the borrowed value only has lifetime %s",
self.region_to_str(sub_scope),
self.region_to_str(super_scope)]
}
}
}
fn region_to_str(r: ty::region) -> ~str {
region_to_str(self.tcx, r)
}
}
// The inherent mutability of a component is its default mutability

View file

@ -50,10 +50,10 @@ fn opt_deref_kind(t: ty::t) -> option<deref_kind> {
some(deref_ptr(uniq_ptr))
}
ty::ty_rptr(*) |
ty::ty_evec(_, ty::vstore_slice(_)) |
ty::ty_estr(ty::vstore_slice(_)) {
some(deref_ptr(region_ptr))
ty::ty_rptr(r, _) |
ty::ty_evec(_, ty::vstore_slice(r)) |
ty::ty_estr(ty::vstore_slice(r)) {
some(deref_ptr(region_ptr(r)))
}
ty::ty_box(*) |
@ -343,7 +343,7 @@ impl public_methods for borrowck_ctxt {
// not loanable.
alt ptr {
uniq_ptr => {some(@lp_deref(l, ptr))}
gc_ptr | region_ptr | unsafe_ptr => {none}
gc_ptr | region_ptr(_) | unsafe_ptr => {none}
}
};
@ -353,7 +353,7 @@ impl public_methods for borrowck_ctxt {
uniq_ptr => {
self.inherited_mutability(base_cmt.mutbl, mt.mutbl)
}
gc_ptr | region_ptr | unsafe_ptr => {
gc_ptr | region_ptr(_) | unsafe_ptr => {
mt.mutbl
}
};
@ -402,7 +402,7 @@ impl public_methods for borrowck_ctxt {
uniq_ptr => {
self.inherited_mutability(base_cmt.mutbl, mt.mutbl)
}
gc_ptr | region_ptr | unsafe_ptr => {
gc_ptr | region_ptr(_) | unsafe_ptr => {
mt.mutbl
}
};

View file

@ -8,35 +8,94 @@
import categorization::{public_methods, opt_deref_kind};
import loan::public_methods;
import preserve::public_methods;
import preserve::{public_methods, preserve_condition, pc_ok, pc_if_pure};
export gather_loans;
enum gather_loan_ctxt = @{bccx: borrowck_ctxt, req_maps: req_maps};
/// Context used while gathering loans:
///
/// - `bccx`: the the borrow check context
/// - `req_maps`: the maps computed by `gather_loans()`, see def'n of the
/// type `req_maps` for more info
/// - `item_ub`: the id of the block for the enclosing fn/method item
/// - `root_ub`: the id of the outermost block for which we can root
/// an `@T`. This is the id of the innermost enclosing
/// loop or function body.
///
/// The role of `root_ub` is to prevent us from having to accumulate
/// vectors of rooted items at runtime. Consider this case:
///
/// fn foo(...) -> int {
/// let mut ptr: &int;
/// while some_cond {
/// let x: @int = ...;
/// ptr = &*x;
/// }
/// *ptr
/// }
///
/// If we are not careful here, we would infer the scope of the borrow `&*x`
/// to be the body of the function `foo()` as a whole. We would then
/// have root each `@int` that is produced, which is an unbounded number.
/// No good. Instead what will happen is that `root_ub` will be set to the
/// body of the while loop and we will refuse to root the pointer `&*x`
/// because it would have to be rooted for a region greater than `root_ub`.
enum gather_loan_ctxt = @{bccx: borrowck_ctxt,
req_maps: req_maps,
mut item_ub: ast::node_id,
mut root_ub: ast::node_id};
fn gather_loans(bccx: borrowck_ctxt, crate: @ast::crate) -> req_maps {
let glcx = gather_loan_ctxt(@{bccx: bccx,
req_maps: {req_loan_map: int_hash(),
pure_map: int_hash()}});
let v = visit::mk_vt(@{visit_expr: req_loans_in_expr
pure_map: int_hash()},
mut item_ub: 0,
mut root_ub: 0});
let v = visit::mk_vt(@{visit_expr: req_loans_in_expr,
visit_fn: req_loans_in_fn,
with *visit::default_visitor()});
visit::visit_crate(*crate, glcx, v);
ret glcx.req_maps;
}
fn req_loans_in_fn(fk: visit::fn_kind,
decl: ast::fn_decl,
body: ast::blk,
sp: span,
id: ast::node_id,
&&self: gather_loan_ctxt,
v: visit::vt<gather_loan_ctxt>) {
// see explanation attached to the `root_ub` field:
let old_item_id = self.item_ub;
let old_root_ub = self.root_ub;
self.root_ub = body.node.id;
alt fk {
visit::fk_anon(*) | visit::fk_fn_block(*) {}
visit::fk_item_fn(*) | visit::fk_method(*) |
visit::fk_ctor(*) | visit::fk_dtor(*) {
self.item_ub = body.node.id;
}
}
visit::visit_fn(fk, decl, body, sp, id, self, v);
self.root_ub = old_root_ub;
self.item_ub = old_item_id;
}
fn req_loans_in_expr(ex: @ast::expr,
&&self: gather_loan_ctxt,
vt: visit::vt<gather_loan_ctxt>) {
let bccx = self.bccx;
let tcx = bccx.tcx;
let old_root_ub = self.root_ub;
#debug["req_loans_in_expr(ex=%s)", pprust::expr_to_str(ex)];
// If this expression is borrowed, have to ensure it remains valid:
for tcx.borrowings.find(ex.id).each |borrow| {
let cmt = self.bccx.cat_borrow_of_expr(ex);
let scope_r = ty::re_scope(borrow.scope_id);
self.guarantee_valid(cmt, borrow.mutbl, scope_r);
self.guarantee_valid(cmt, borrow.mutbl, borrow.region);
}
// Special checks for various kinds of expressions:
@ -51,6 +110,7 @@ fn req_loans_in_expr(ex: @ast::expr,
ty::ty_rptr(r, _) { r }
};
self.guarantee_valid(base_cmt, mutbl, scope_r);
visit::visit_expr(ex, self, vt);
}
ast::expr_call(f, args, _) {
@ -92,7 +152,7 @@ fn req_loans_in_expr(ex: @ast::expr,
// fine).
//
alt opt_deref_kind(arg_ty.ty) {
some(deref_ptr(region_ptr)) |
some(deref_ptr(region_ptr(_))) |
some(deref_ptr(unsafe_ptr)) {
/* region pointers are (by induction) guaranteed */
/* unsafe pointers are the user's problem */
@ -110,6 +170,7 @@ fn req_loans_in_expr(ex: @ast::expr,
ast::by_move | ast::by_copy {}
}
}
visit::visit_expr(ex, self, vt);
}
ast::expr_alt(ex_v, arms, _) {
@ -119,6 +180,7 @@ fn req_loans_in_expr(ex: @ast::expr,
self.gather_pat(cmt, pat, arm.body.node.id, ex.id);
}
}
visit::visit_expr(ex, self, vt);
}
ast::expr_index(rcvr, _) |
@ -136,6 +198,7 @@ fn req_loans_in_expr(ex: @ast::expr,
let scope_r = ty::re_scope(ex.id);
let rcvr_cmt = self.bccx.cat_expr(rcvr);
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);
visit::visit_expr(ex, self, vt);
}
ast::expr_field(rcvr, _, _)
@ -151,13 +214,34 @@ fn req_loans_in_expr(ex: @ast::expr,
let scope_r = ty::re_scope(self.tcx().region_map.get(ex.id));
let rcvr_cmt = self.bccx.cat_expr(rcvr);
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);
visit::visit_expr(ex, self, vt);
}
_ { /*ok*/ }
// see explanation attached to the `root_ub` field:
ast::expr_while(cond, body) {
// during the condition, can only root for the condition
self.root_ub = cond.id;
vt.visit_expr(cond, self, vt);
// during body, can only root for the body
self.root_ub = body.node.id;
vt.visit_block(body, self, vt);
}
// see explanation attached to the `root_ub` field:
ast::expr_loop(body) {
self.root_ub = body.node.id;
visit::visit_expr(ex, self, vt);
}
_ => {
visit::visit_expr(ex, self, vt);
}
}
// Check any contained expressions:
visit::visit_expr(ex, self, vt);
self.root_ub = old_root_ub;
}
impl methods for gather_loan_ctxt {
@ -192,65 +276,69 @@ impl methods for gather_loan_ctxt {
// it within that scope, the loan will be detected and an
// error will be reported.
some(_) {
alt scope_r {
ty::re_scope(scope_id) {
let loans = self.bccx.loan(cmt, req_mutbl);
self.add_loans(scope_id, loans);
alt self.bccx.loan(cmt, scope_r, req_mutbl) {
err(e) => { self.bccx.report(e); }
ok(loans) if loans.len() == 0 => {}
ok(loans) => {
alt scope_r {
ty::re_scope(scope_id) => {
self.add_loans(scope_id, loans);
if req_mutbl == m_imm && cmt.mutbl != m_imm {
self.bccx.loaned_paths_imm += 1;
if req_mutbl == m_imm && cmt.mutbl != m_imm {
self.bccx.loaned_paths_imm += 1;
if self.tcx().sess.borrowck_note_loan() {
self.bccx.span_note(
cmt.span,
#fmt["immutable loan required"]);
if self.tcx().sess.borrowck_note_loan() {
self.bccx.span_note(
cmt.span,
#fmt["immutable loan required"]);
}
} else {
self.bccx.loaned_paths_same += 1;
}
} else {
self.bccx.loaned_paths_same += 1;
}
_ => {
self.bccx.tcx.sess.span_bug(
cmt.span,
#fmt["loans required but scope is scope_region is %s",
region_to_str(self.tcx(), scope_r)]);
}
}
}
_ {
self.bccx.span_err(
cmt.span,
#fmt["cannot guarantee the stability \
of this expression for the entirety of \
its lifetime, %s",
region_to_str(self.tcx(), scope_r)]);
}
}
}
// The path is not loanable: in that case, we must try and preserve
// it dynamically (or see that it is preserved by virtue of being
// rooted in some immutable path)
// The path is not loanable: in that case, we must try and
// preserve it dynamically (or see that it is preserved by
// virtue of being rooted in some immutable path). We must
// also check that the mutability of the desired pointer
// matches with the actual mutability (but if an immutable
// pointer is desired, that is ok as long as we are pure)
none {
let opt_scope_id = alt scope_r {
ty::re_scope(scope_id) { some(scope_id) }
_ { none }
};
let result = {
do self.check_mutbl(req_mutbl, cmt).chain |_ok| {
self.bccx.preserve(cmt, opt_scope_id)
let result: bckres<preserve_condition> = {
do self.check_mutbl(req_mutbl, cmt).chain |pc1| {
do self.bccx.preserve(cmt, scope_r,
self.item_ub,
self.root_ub).chain |pc2| {
ok(pc1.combine(pc2))
}
}
};
alt result {
ok(()) {
ok(pc_ok) {
// we were able guarantee the validity of the ptr,
// perhaps by rooting or because it is immutably
// rooted. good.
self.bccx.stable_paths += 1;
}
err(e) {
// not able to guarantee the validity of the ptr.
// rather than report an error, presuming that the
// borrow is for a limited scope, we'll make one last
// ditch effort and require that the scope where the
// borrow occurs be pure.
alt opt_scope_id {
some(scope_id) {
self.req_maps.pure_map.insert(scope_id, e);
ok(pc_if_pure(e)) {
// we are only able to guarantee the validity if
// the scope is pure
alt scope_r {
ty::re_scope(pure_id) => {
// if the scope is some block/expr in the fn,
// then just require that this scope be pure
self.req_maps.pure_map.insert(pure_id, e);
self.bccx.req_pure_paths += 1;
if self.tcx().sess.borrowck_note_pure() {
@ -259,12 +347,17 @@ impl methods for gather_loan_ctxt {
#fmt["purity required"]);
}
}
none {
// otherwise, fine, I give up.
_ => {
// otherwise, we can't enforce purity for that
// scope, so give up and report an error
self.bccx.report(e);
}
}
}
err(e) => {
// we cannot guarantee the validity of this pointer
self.bccx.report(e);
}
}
}
}
@ -279,19 +372,25 @@ impl methods for gather_loan_ctxt {
// reqires an immutable pointer, but `f` lives in (aliased)
// mutable memory.
fn check_mutbl(req_mutbl: ast::mutability,
cmt: cmt) -> bckres<()> {
cmt: cmt) -> bckres<preserve_condition> {
alt (req_mutbl, cmt.mutbl) {
(m_const, _) |
(m_imm, m_imm) |
(m_mutbl, m_mutbl) {
ok(())
(m_mutbl, m_mutbl) => {
ok(pc_ok)
}
(_, m_const) |
(m_imm, m_mutbl) |
(m_mutbl, m_imm) {
err({cmt: cmt,
code: err_mutbl(req_mutbl, cmt.mutbl)})
(m_mutbl, m_imm) => {
let e = {cmt: cmt,
code: err_mutbl(req_mutbl, cmt.mutbl)};
if req_mutbl == m_imm {
// you can treat mutable things as imm if you are pure
ok(pc_if_pure(e))
} else {
err(e)
}
}
}
}

View file

@ -3,17 +3,29 @@
// of the scope S, presuming that the returned set of loans `Ls` are honored.
export public_methods;
import result::{result, ok, err};
impl public_methods for borrowck_ctxt {
fn loan(cmt: cmt, mutbl: ast::mutability) -> @dvec<loan> {
let lc = loan_ctxt_(@{bccx: self, loans: @dvec()});
lc.loan(cmt, mutbl);
ret lc.loans;
fn loan(cmt: cmt,
scope_region: ty::region,
mutbl: ast::mutability) -> bckres<@dvec<loan>> {
let lc = loan_ctxt_(@{bccx: self,
scope_region: scope_region,
loans: @dvec()});
alt lc.loan(cmt, mutbl) {
ok(()) => {ok(lc.loans)}
err(e) => {err(e)}
}
}
}
type loan_ctxt_ = {
bccx: borrowck_ctxt,
// the region scope for which we must preserve the memory
scope_region: ty::region,
// accumulated list of loans that will be required
loans: @dvec<loan>
};
@ -22,18 +34,30 @@ enum loan_ctxt {
}
impl loan_methods for loan_ctxt {
fn tcx() -> ty::ctxt { self.bccx.tcx }
fn ok_with_loan_of(cmt: cmt,
mutbl: ast::mutability) {
// Note: all cmt's that we deal with will have a non-none lp, because
// the entry point into this routine, `borrowck_ctxt::loan()`, rejects
// any cmt with a none-lp.
(*self.loans).push({lp:option::get(cmt.lp),
cmt:cmt,
mutbl:mutbl});
scope_ub: ty::region,
mutbl: ast::mutability) -> bckres<()> {
let region_map = self.tcx().region_map;
if region::subregion(region_map, scope_ub, self.scope_region) {
// Note: all cmt's that we deal with will have a non-none
// lp, because the entry point into this routine,
// `borrowck_ctxt::loan()`, rejects any cmt with a
// none-lp.
(*self.loans).push({lp: option::get(cmt.lp),
cmt: cmt,
mutbl: mutbl});
ok(())
} else {
// The loan being requested lives longer than the data
// being loaned out!
err({cmt:cmt, code:err_out_of_scope(scope_ub,
self.scope_region)})
}
}
fn loan(cmt: cmt, req_mutbl: ast::mutability) {
fn loan(cmt: cmt, req_mutbl: ast::mutability) -> bckres<()> {
#debug["loan(%s, %s)",
self.bccx.cmt_to_repr(cmt),
self.bccx.mut_to_str(req_mutbl)];
@ -53,8 +77,12 @@ impl loan_methods for loan_ctxt {
cmt.span,
~"rvalue with a non-none lp");
}
cat_local(_) | cat_arg(_) | cat_stack_upvar(_) {
self.ok_with_loan_of(cmt, req_mutbl)
cat_local(local_id) | cat_arg(local_id) {
let local_scope_id = self.tcx().region_map.get(local_id);
self.ok_with_loan_of(cmt, ty::re_scope(local_scope_id), req_mutbl)
}
cat_stack_upvar(cmt) {
self.loan(cmt, req_mutbl) // NDM correct?
}
cat_discr(base, _) {
self.loan(base, req_mutbl)
@ -88,7 +116,7 @@ impl loan_methods for loan_ctxt {
}
cat_deref(cmt1, _, unsafe_ptr) |
cat_deref(cmt1, _, gc_ptr) |
cat_deref(cmt1, _, region_ptr) {
cat_deref(cmt1, _, region_ptr(_)) {
// Aliased data is simply not lendable.
self.bccx.tcx.sess.span_bug(
cmt.span,
@ -102,14 +130,17 @@ impl loan_methods for loan_ctxt {
// Example: record fields.
fn loan_stable_comp(cmt: cmt,
cmt_base: cmt,
req_mutbl: ast::mutability) {
req_mutbl: ast::mutability) -> bckres<()> {
let base_mutbl = alt req_mutbl {
m_imm { m_imm }
m_const | m_mutbl { m_const }
};
self.loan(cmt_base, base_mutbl);
self.ok_with_loan_of(cmt, req_mutbl)
do self.loan(cmt_base, base_mutbl).chain |_ok| {
// can use static for the scope because the base
// determines the lifetime, ultimately
self.ok_with_loan_of(cmt, ty::re_static, req_mutbl)
}
}
// An "unstable deref" means a deref of a ptr/comp where, if the
@ -117,11 +148,13 @@ impl loan_methods for loan_ctxt {
// deref would be invalidated. Examples: interior of variants, uniques.
fn loan_unstable_deref(cmt: cmt,
cmt_base: cmt,
req_mutbl: ast::mutability) {
req_mutbl: ast::mutability) -> bckres<()> {
// Variant components: the base must be immutable, because
// if it is overwritten, the types of the embedded data
// could change.
self.loan(cmt_base, m_imm);
self.ok_with_loan_of(cmt, req_mutbl)
do self.loan(cmt_base, m_imm).chain |_ok| {
// can use static, as in loan_stable_comp()
self.ok_with_loan_of(cmt, ty::re_static, req_mutbl)
}
}
}

View file

@ -1,78 +1,146 @@
// ----------------------------------------------------------------------
// Preserve(Ex, S) holds if ToAddr(Ex) will remain valid for the entirety of
// the scope S.
export public_methods;
//
export public_methods, preserve_condition, pc_ok, pc_if_pure;
enum preserve_condition {
pc_ok,
pc_if_pure(bckerr)
}
impl public_methods for preserve_condition {
// combines two preservation conditions such that if either of
// them requires purity, the result requires purity
fn combine(pc: preserve_condition) -> preserve_condition {
alt self {
pc_ok => {pc}
pc_if_pure(e) => {self}
}
}
}
impl public_methods for borrowck_ctxt {
fn preserve(cmt: cmt, opt_scope_id: option<ast::node_id>) -> bckres<()> {
#debug["preserve(%s)", self.cmt_to_repr(cmt)];
fn preserve(cmt: cmt,
scope_region: ty::region,
item_ub: ast::node_id,
root_ub: ast::node_id)
-> bckres<preserve_condition> {
let ctxt = preserve_ctxt({bccx: self,
scope_region: scope_region,
item_ub: item_ub,
root_ub: root_ub,
root_managed_data: true});
(&ctxt).preserve(cmt)
}
}
enum preserve_ctxt = {
bccx: borrowck_ctxt,
// the region scope for which we must preserve the memory
scope_region: ty::region,
// the scope for the body of the enclosing fn/method item
item_ub: ast::node_id,
// the upper bound on how long we can root an @T pointer
root_ub: ast::node_id,
// if false, do not attempt to root managed data
root_managed_data: bool
};
impl private_methods for &preserve_ctxt {
fn tcx() -> ty::ctxt { self.bccx.tcx }
fn preserve(cmt: cmt) -> bckres<preserve_condition> {
#debug["preserve(cmt=%s, root_ub=%?, root_managed_data=%b)",
self.bccx.cmt_to_repr(cmt), self.root_ub,
self.root_managed_data];
let _i = indenter();
alt cmt.cat {
cat_rvalue | cat_special(_) {
ok(())
cat_special(sk_self) | cat_special(sk_heap_upvar) {
self.compare_scope(cmt, ty::re_scope(self.item_ub))
}
cat_special(sk_static_item) | cat_special(sk_method) {
ok(pc_ok)
}
cat_rvalue {
// when we borrow an rvalue, we can keep it rooted but only
// up to the root_ub point
// FIXME(#2977)--need to update trans!
self.compare_scope(cmt, ty::re_scope(self.root_ub))
}
cat_stack_upvar(cmt) {
self.preserve(cmt, opt_scope_id)
self.preserve(cmt)
}
cat_local(_) {
cat_local(local_id) {
// Normally, local variables are lendable, and so this
// case should never trigger. However, if we are
// preserving an expression like a.b where the field `b`
// has @ type, then it will recurse to ensure that the `a`
// is stable to try and avoid rooting the value `a.b`. In
// this case, opt_scope_id will be none.
if opt_scope_id.is_some() {
self.tcx.sess.span_bug(
// this case, root_managed_data will be false.
if self.root_managed_data {
self.tcx().sess.span_bug(
cmt.span,
~"preserve() called with local and \
non-none opt_scope_id");
~"preserve() called with local and !root_managed_data");
}
ok(())
let local_scope_id = self.tcx().region_map.get(local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
}
cat_binding(_) {
cat_binding(local_id) {
// Bindings are these kind of weird implicit pointers (cc
// #2329). We require (in gather_loans) that they be
// rooted in an immutable location.
ok(())
let local_scope_id = self.tcx().region_map.get(local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
}
cat_arg(_) {
cat_arg(local_id) {
// This can happen as not all args are lendable (e.g., &&
// modes). In that case, the caller guarantees stability.
// This is basically a deref of a region ptr.
ok(())
// modes). In that case, the caller guarantees stability
// for at least the scope of the fn. This is basically a
// deref of a region ptr.
let local_scope_id = self.tcx().region_map.get(local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
}
cat_comp(cmt_base, comp_field(*)) |
cat_comp(cmt_base, comp_index(*)) |
cat_comp(cmt_base, comp_tuple) {
// Most embedded components: if the base is stable, the
// type never changes.
self.preserve(cmt_base, opt_scope_id)
self.preserve(cmt_base)
}
cat_comp(cmt_base, comp_variant(enum_did)) {
if ty::enum_is_univariant(self.tcx, enum_did) {
self.preserve(cmt_base, opt_scope_id)
if ty::enum_is_univariant(self.tcx(), enum_did) {
self.preserve(cmt_base)
} else {
// If there are multiple variants: overwriting the
// base could cause the type of this memory to change,
// so require imm.
self.require_imm(cmt, cmt_base, opt_scope_id, err_mut_variant)
self.require_imm(cmt, cmt_base, err_mut_variant)
}
}
cat_deref(cmt_base, _, uniq_ptr) {
// Overwriting the base could cause this memory to be
// freed, so require imm.
self.require_imm(cmt, cmt_base, opt_scope_id, err_mut_uniq)
self.require_imm(cmt, cmt_base, err_mut_uniq)
}
cat_deref(_, _, region_ptr) {
// References are always "stable" by induction (when the
// reference of type &MT was created, the memory must have
// been stable)
ok(())
cat_deref(_, _, region_ptr(region)) {
// References are always "stable" for lifetime `region` by
// induction (when the reference of type &MT was created,
// the memory must have been stable).
self.compare_scope(cmt, region)
}
cat_deref(_, _, unsafe_ptr) {
// Unsafe pointers are the user's problem
ok(())
ok(pc_ok)
}
cat_deref(base, derefs, gc_ptr) {
// GC'd pointers of type @MT: if this pointer lives in
@ -80,13 +148,26 @@ impl public_methods for borrowck_ctxt {
// otherwise we have no guarantee the pointer will stay
// live, so we must root the pointer (i.e., inc the ref
// count) for the duration of the loan.
#debug["base.mutbl = %?", self.bccx.mut_to_str(base.mutbl)];
if base.mutbl == m_imm {
alt self.preserve(base, none) {
ok(()) {ok(())}
err(_) {self.attempt_root(cmt, opt_scope_id, base, derefs)}
let non_rooting_ctxt =
preserve_ctxt({root_managed_data: false with **self});
alt (&non_rooting_ctxt).preserve(base) {
ok(pc_ok) => {
ok(pc_ok)
}
ok(pc_if_pure(_)) {
#debug["must root @T, otherwise purity req'd"];
self.attempt_root(cmt, base, derefs)
}
err(e) => {
#debug["must root @T, err: %s",
self.bccx.bckerr_code_to_str(e.code)];
self.attempt_root(cmt, base, derefs)
}
}
} else {
self.attempt_root(cmt, opt_scope_id, base, derefs)
self.attempt_root(cmt, base, derefs)
}
}
cat_discr(base, alt_id) {
@ -144,43 +225,124 @@ impl public_methods for borrowck_ctxt {
// in the *arm* vs the *alt*.
// current scope must be the arm, which is always a child of alt:
assert self.tcx.region_map.get(opt_scope_id.get()) == alt_id;
assert {
alt check self.scope_region {
ty::re_scope(arm_id) => {
self.tcx().region_map.get(arm_id) == alt_id
}
_ => {false}
}
};
self.preserve(base, some(alt_id))
let alt_rooting_ctxt =
preserve_ctxt({scope_region: ty::re_scope(alt_id)
with **self});
(&alt_rooting_ctxt).preserve(base)
}
}
}
}
impl private_methods for borrowck_ctxt {
/// Reqiures that `cmt` (which is a deref or subcomponent of
/// `base`) be found in an immutable location (that is, `base`
/// must be immutable). Also requires that `base` itself is
/// preserved.
fn require_imm(cmt: cmt,
cmt_base: cmt,
opt_scope_id: option<ast::node_id>,
code: bckerr_code) -> bckres<()> {
code: bckerr_code) -> bckres<preserve_condition> {
// Variant contents and unique pointers: must be immutably
// rooted to a preserved address.
alt cmt_base.mutbl {
m_mutbl | m_const { err({cmt:cmt, code:code}) }
m_imm { self.preserve(cmt_base, opt_scope_id) }
alt self.preserve(cmt_base) {
// the base is preserved, but if we are not mutable then
// purity is required
ok(pc_ok) => {
alt cmt_base.mutbl {
m_mutbl | m_const => {
ok(pc_if_pure({cmt:cmt, code:code}))
}
m_imm => {
ok(pc_ok)
}
}
}
// the base requires purity too, that's fine
ok(pc_if_pure(e)) => {
ok(pc_if_pure(e))
}
// base is not stable, doesn't matter
err(e) => {
err(e)
}
}
}
fn attempt_root(cmt: cmt, opt_scope_id: option<ast::node_id>,
base: cmt, derefs: uint) -> bckres<()> {
alt opt_scope_id {
some(scope_id) {
#debug["Inserting root map entry for %s: \
node %d:%u -> scope %d",
self.cmt_to_repr(cmt), base.id,
derefs, scope_id];
/// Checks that the scope for which the value must be preserved
/// is a subscope of `scope_ub`; if so, success.
fn compare_scope(cmt: cmt,
scope_ub: ty::region) -> bckres<preserve_condition> {
let region_map = self.tcx().region_map;
if region::subregion(region_map, scope_ub, self.scope_region) {
ok(pc_ok)
} else {
err({cmt:cmt, code:err_out_of_scope(scope_ub,
self.scope_region)})
}
}
let rk = {id: base.id, derefs: derefs};
self.root_map.insert(rk, scope_id);
ok(())
/// Here, `cmt=*base` is always a deref of managed data (if
/// `derefs` != 0, then an auto-deref). This routine determines
/// whether it is safe to MAKE cmt stable by rooting the pointer
/// `base`. We can only do the dynamic root if the desired
/// lifetime `self.scope_region` is a subset of `self.root_ub`
/// scope; otherwise, it would either require that we hold the
/// value live for longer than the current fn or else potentially
/// require that an statically unbounded number of values be
/// rooted (if a loop exists).
fn attempt_root(cmt: cmt, base: cmt,
derefs: uint) -> bckres<preserve_condition> {
if !self.root_managed_data {
// normally, there is a root_ub; the only time that this
// is none is when a boxed value is stored in an immutable
// location. In that case, we will test to see if that
// immutable location itself can be preserved long enough
// in which case no rooting is necessary. But there it
// would be sort of pointless to avoid rooting the inner
// box by rooting an outer box, as it would just keep more
// memory live than necessary, so we set root_ub to none.
ret err({cmt:cmt, code:err_root_not_permitted});
}
let root_region = ty::re_scope(self.root_ub);
alt self.scope_region {
// we can only root values if the desired region is some concrete
// scope within the fn body
ty::re_scope(scope_id) => {
let region_map = self.tcx().region_map;
#debug["Considering root map entry for %s: \
node %d:%u -> scope_id %?, root_ub %?",
self.bccx.cmt_to_repr(cmt), base.id,
derefs, scope_id, self.root_ub];
if region::subregion(region_map, root_region, self.scope_region) {
#debug["Elected to root"];
let rk = {id: base.id, derefs: derefs};
self.bccx.root_map.insert(rk, scope_id);
ret ok(pc_ok);
} else {
#debug["Unable to root"];
ret err({cmt:cmt,
code:err_out_of_root_scope(root_region,
self.scope_region)});
}
}
none {
err({cmt:cmt, code:err_preserve_gc})
// we won't be able to root long enough
_ => {
ret err({cmt:cmt,
code:err_out_of_root_scope(root_region,
self.scope_region)});
}
}
}
}

View file

@ -63,24 +63,19 @@ class LanguageItems {
}
class LanguageItemCollector {
let items: LanguageItems;
let items: &LanguageItems;
let crate: @crate;
let session: session;
let item_refs: hashmap<~str,&mut option<def_id>>;
new(crate: @crate, session: session) {
new(crate: @crate, session: session, items: &self/LanguageItems) {
self.crate = crate;
self.session = session;
self.items = LanguageItems();
self.items = items;
self.item_refs = str_hash();
}
// XXX: Needed to work around an issue with constructors.
fn init() {
self.item_refs.insert(~"const", &mut self.items.const_trait);
self.item_refs.insert(~"copy", &mut self.items.copy_trait);
self.item_refs.insert(~"send", &mut self.items.send_trait);
@ -206,7 +201,6 @@ class LanguageItemCollector {
}
fn collect() {
self.init();
self.collect_local_language_items();
self.collect_external_language_items();
self.check_completeness();
@ -214,8 +208,9 @@ class LanguageItemCollector {
}
fn collect_language_items(crate: @crate, session: session) -> LanguageItems {
let collector = LanguageItemCollector(crate, session);
let items = LanguageItems();
let collector = LanguageItemCollector(crate, session, &items);
collector.collect();
copy collector.items
copy items
}

View file

@ -39,6 +39,15 @@ type ctxt = {
def_map: resolve3::DefMap,
region_map: region_map,
// Generally speaking, expressions are parented to their innermost
// enclosing block. But some kinds of expressions serve as
// parents: calls, methods, etc. In addition, some expressions
// serve as parents by virtue of where they appear. For example,
// the condition in a while loop is always a parent. In those
// cases, we add the node id of such an expression to this set so
// that when we visit it we can view it as a parent.
root_exprs: hashmap<ast::node_id, ()>,
// The parent scope is the innermost block, call, or alt
// expression during the execution of which the current expression
// will be evaluated. Generally speaking, the innermost parent
@ -87,6 +96,27 @@ fn scope_contains(region_map: region_map, superscope: ast::node_id,
ret true;
}
/// Determines whether one region is a subregion of another. This is
/// intended to run *after inference* and sadly the logic is somewhat
/// duplicated with the code in infer.rs.
fn subregion(region_map: region_map,
super_region: ty::region,
sub_region: ty::region) -> bool {
super_region == sub_region ||
alt (super_region, sub_region) {
(ty::re_static, _) => {true}
(ty::re_scope(super_scope), ty::re_scope(sub_scope)) |
(ty::re_free(super_scope, _), ty::re_scope(sub_scope)) => {
scope_contains(region_map, super_scope, sub_scope)
}
_ => {
false
}
}
}
/// Finds the nearest common ancestor (if any) of two scopes. That
/// is, finds the smallest scope which is greater than or equal to
/// both `scope_a` and `scope_b`.
@ -198,31 +228,37 @@ fn resolve_pat(pat: @ast::pat, cx: ctxt, visitor: visit::vt<ctxt>) {
fn resolve_expr(expr: @ast::expr, cx: ctxt, visitor: visit::vt<ctxt>) {
record_parent(cx, expr.id);
let mut new_cx = cx;
alt expr.node {
ast::expr_call(*) {
ast::expr_call(*) => {
#debug["node %d: %s", expr.id, pprust::expr_to_str(expr)];
let new_cx = {parent: some(expr.id) with cx};
visit::visit_expr(expr, new_cx, visitor);
new_cx.parent = some(expr.id);
}
ast::expr_alt(subexpr, _, _) {
ast::expr_alt(subexpr, _, _) => {
#debug["node %d: %s", expr.id, pprust::expr_to_str(expr)];
let new_cx = {parent: some(expr.id) with cx};
visit::visit_expr(expr, new_cx, visitor);
new_cx.parent = some(expr.id);
}
ast::expr_fn(_, _, _, cap_clause) |
ast::expr_fn_block(_, _, cap_clause) {
ast::expr_fn_block(_, _, cap_clause) => {
// although the capture items are not expressions per se, they
// do get "evaluated" in some sense as copies or moves of the
// relevant variables so we parent them like an expression
for (*cap_clause).each |cap_item| {
record_parent(cx, cap_item.id);
record_parent(new_cx, cap_item.id);
}
visit::visit_expr(expr, cx, visitor);
}
_ {
visit::visit_expr(expr, cx, visitor);
ast::expr_while(cond, _) => {
new_cx.root_exprs.insert(cond.id, ());
}
_ => {}
};
if new_cx.root_exprs.contains_key(expr.id) {
new_cx.parent = some(expr.id);
}
visit::visit_expr(expr, new_cx, visitor);
}
fn resolve_local(local: @ast::local, cx: ctxt, visitor: visit::vt<ctxt>) {
@ -269,6 +305,7 @@ fn resolve_crate(sess: session, def_map: resolve3::DefMap,
let cx: ctxt = {sess: sess,
def_map: def_map,
region_map: int_hash(),
root_exprs: int_hash(),
parent: none};
let visitor = visit::mk_vt(@{
visit_block: resolve_block,

View file

@ -213,9 +213,11 @@ enum ast_ty_to_ty_cache_entry {
atttce_resolved(t) /* resolved to a type, irrespective of region */
}
#[auto_serialize]
// N.B.: Borrows from inlined content are not accurately deserialized. This
// is because we don't need the details in trans, we only care if there is an
// entry in the table or not.
type borrow = {
scope_id: ast::node_id,
region: ty::region,
mutbl: ast::mutability
};

View file

@ -105,7 +105,6 @@ type fn_ctxt_ =
// use. In practice, this is the innermost loop or function
// body.
mut region_lb: ast::node_id,
mut region_ub: ast::node_id,
in_scope_regions: isr_alist,
@ -130,7 +129,6 @@ fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t,
infcx: infer::new_infer_ctxt(ccx.tcx),
locals: int_hash(),
mut region_lb: region_bnd,
mut region_ub: region_bnd,
in_scope_regions: @nil,
node_types: map::int_hash(),
node_type_substs: map::int_hash(),
@ -246,7 +244,6 @@ fn check_fn(ccx: @crate_ctxt,
infcx: infcx,
locals: locals,
mut region_lb: body.node.id,
mut region_ub: body.node.id,
in_scope_regions: isr,
node_types: node_types,
node_type_substs: node_type_substs,
@ -600,15 +597,13 @@ impl methods for @fn_ctxt {
fn mk_assignty(expr: @ast::expr, borrow_lb: ast::node_id,
sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
let anmnt = {expr_id: expr.id, borrow_lb: borrow_lb,
borrow_ub: self.region_ub};
let anmnt = {expr_id: expr.id, span: expr.span, borrow_lb: borrow_lb};
infer::mk_assignty(self.infcx, anmnt, sub, sup)
}
fn can_mk_assignty(expr: @ast::expr, borrow_lb: ast::node_id,
sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
let anmnt = {expr_id: expr.id, borrow_lb: borrow_lb,
borrow_ub: self.region_ub};
sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
let anmnt = {expr_id: expr.id, span: expr.span, borrow_lb: borrow_lb};
infer::can_mk_assignty(self.infcx, anmnt, sub, sup)
}
@ -637,13 +632,6 @@ impl methods for @fn_ctxt {
self.region_lb = old_region_lb;
ret v;
}
fn with_region_ub<R>(ub: ast::node_id, f: fn() -> R) -> R {
let old_region_ub = self.region_ub;
self.region_ub = ub;
let v <- f();
self.region_ub = old_region_ub;
ret v;
}
}
fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t {
@ -1376,7 +1364,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
bot = check_expr(fcx, oprnd, unpack_expected(fcx, expected, |ty|
alt ty { ty::ty_rptr(_, mt) { some(mt.ty) } _ { none } }
));
let region = region_of(fcx, oprnd);
//let region = region_of(fcx, oprnd);
let region = fcx.infcx.next_region_var_with_scope_lb(expr.id);
let tm = { ty: fcx.expr_ty(oprnd), mutbl: mutbl };
let oprnd_t = ty::mk_rptr(tcx, region, tm);
fcx.write_ty(id, oprnd_t);
@ -1446,15 +1435,11 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
}
ast::expr_while(cond, body) {
bot = check_expr_with(fcx, cond, ty::mk_bool(tcx));
do fcx.with_region_ub(body.node.id) {
check_block_no_value(fcx, body);
}
check_block_no_value(fcx, body);
fcx.write_ty(id, ty::mk_nil(tcx));
}
ast::expr_loop(body) {
do fcx.with_region_ub(body.node.id) {
check_block_no_value(fcx, body);
}
check_block_no_value(fcx, body);
fcx.write_ty(id, ty::mk_nil(tcx));
bot = !may_break(body);
}

View file

@ -276,13 +276,13 @@ fn convert_integral_ty_to_int_ty_set(tcx: ty::ctxt, t: ty::t)
}
// Extra information needed to perform an assignment that may borrow.
// The `expr_id` is the is of the expression whose type is being
// assigned, and `borrow_scope` is the region scope to use if the
// value should be borrowed.
// The `expr_id` and `span` are the id/span of the expression
// whose type is being assigned, and `borrow_scope` is the region
// scope to use if the value should be borrowed.
type assignment = {
expr_id: ast::node_id,
span: span,
borrow_lb: ast::node_id,
borrow_ub: ast::node_id,
};
type bound<T:copy> = option<T>;
@ -325,6 +325,7 @@ enum infer_ctxt = @{
region_var_counter: @mut uint,
borrowings: dvec<{expr_id: ast::node_id,
span: span,
scope: ty::region,
mutbl: ast::mutability}>
};
@ -422,16 +423,18 @@ fn resolve_region(cx: infer_ctxt, r: ty::region, modes: uint)
fn resolve_borrowings(cx: infer_ctxt) {
for cx.borrowings.each |item| {
alt resolve_region(cx, item.scope, resolve_all|force_all) {
ok(ty::re_scope(scope_id)) => {
#debug["borrowing for expr %d resolved to scope %d, mutbl %?",
item.expr_id, scope_id, item.mutbl];
ok(region) => {
#debug["borrowing for expr %d resolved to region %?, mutbl %?",
item.expr_id, region, item.mutbl];
cx.tcx.borrowings.insert(
item.expr_id, {scope_id: scope_id, mutbl: item.mutbl});
item.expr_id, {region: region, mutbl: item.mutbl});
}
r => {
cx.tcx.sess.bug(
#fmt["borrowing resolved to %?, not a valid scope", r]);
err(e) => {
let str = fixup_err_to_str(e);
cx.tcx.sess.span_err(
item.span,
#fmt["could not resolve lifetime for borrow: %s", str]);
}
}
}
@ -663,6 +666,11 @@ impl methods for infer_ctxt {
ret region_vid(id);
}
fn next_region_var_with_scope_lb(scope_id: ast::node_id) -> ty::region {
self.next_region_var({lb: some(ty::re_scope(scope_id)),
ub: none})
}
fn next_region_var(bnds: bounds<ty::region>) -> ty::region {
ty::re_var(self.next_region_var_id(bnds))
}
@ -1475,10 +1483,7 @@ impl assignment for infer_ctxt {
do self.sub_tys(a, nr_b).then {
// Create a fresh region variable `r_a` with the given
// borrow bounds:
let r_lb = ty::re_scope(anmnt.borrow_lb);
let r_ub = ty::re_scope(anmnt.borrow_ub);
let r_a = self.next_region_var({lb: some(r_lb),
ub: some(r_ub)});
let r_a = self.next_region_var_with_scope_lb(anmnt.borrow_lb);
#debug["anmnt=%?", anmnt];
do sub(self).contraregions(r_a, r_b).chain |_r| {
@ -1487,6 +1492,7 @@ impl assignment for infer_ctxt {
#debug["borrowing expression #%?, scope=%?, m=%?",
anmnt, r_a, m];
self.borrowings.push({expr_id: anmnt.expr_id,
span: anmnt.span,
scope: r_a,
mutbl: m});
uok()

View file

@ -62,9 +62,10 @@ fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> ~str {
#fmt("<method at %s>",
codemap::span_to_str(expr.span, cx.sess.codemap))
}
_ { cx.sess.bug(
#fmt["re_scope refers to %s",
ast_map::node_id_to_str(cx.items, node_id)]) }
_ {
#fmt("<expression at %s>",
codemap::span_to_str(expr.span, cx.sess.codemap))
}
}
}
none {