提交 cfac9b68 编写于 作者: N Niko Matsakis

improve borrowck to handle some frankly rather tricky cases

- receivers of method calls are also borrowed
- by-val arguments are also borrowed (needs tests)
- assignment to components can interfere with loans
上级 c5f2c1d6
......@@ -149,7 +149,7 @@
"];
import syntax::ast;
import syntax::ast::{m_mutbl, m_imm, m_const};
import syntax::ast::{mutability, m_mutbl, m_imm, m_const};
import syntax::visit;
import syntax::ast_util;
import syntax::ast_map;
......@@ -254,7 +254,8 @@ enum ptr_kind {uniq_ptr, gc_ptr, region_ptr, unsafe_ptr}
// I am coining the term "components" to mean "pieces of a data
// structure accessible without a dereference":
enum comp_kind {comp_tuple, comp_res, comp_variant,
comp_field(str), comp_index(ty::t)}
comp_field(str, ast::mutability),
comp_index(ty::t, ast::mutability)}
// We pun on *T to mean both actual deref of a ptr as well
// as accessing of components:
......@@ -422,8 +423,8 @@ fn ptr_sigil(ptr: ptr_kind) -> str {
fn comp_to_repr(comp: comp_kind) -> str {
alt comp {
comp_field(fld) { fld }
comp_index(_) { "[]" }
comp_field(fld, _) { fld }
comp_index(*) { "[]" }
comp_tuple { "()" }
comp_res { "<res>" }
comp_variant { "<enum>" }
......@@ -480,11 +481,11 @@ fn cmt_to_str(cmt: cmt) -> str {
cat_deref(_, _, pk) { #fmt["dereference of %s %s pointer",
mut_str, self.pk_to_sigil(pk)] }
cat_stack_upvar(_) { mut_str + " upvar" }
cat_comp(_, comp_field(_)) { mut_str + " field" }
cat_comp(_, comp_field(*)) { mut_str + " field" }
cat_comp(_, comp_tuple) { "tuple content" }
cat_comp(_, comp_res) { "resource content" }
cat_comp(_, comp_variant) { "enum content" }
cat_comp(_, comp_index(t)) {
cat_comp(_, comp_index(t, _)) {
alt ty::get(t).struct {
ty::ty_vec(*) | ty::ty_evec(*) {
mut_str + " vec content"
......@@ -522,3 +523,14 @@ fn bckerr_code_to_str(code: bckerr_code) -> str {
}
}
}
// The inherent mutability of a component is its default mutability
// assuming it is embedded in an immutable context. In general, the
// mutability can be "overridden" if the component is embedded in a
// mutable structure.
fn inherent_mutability(ck: comp_kind) -> mutability {
alt ck {
comp_tuple | comp_res | comp_variant {m_imm}
comp_field(_, m) | comp_index(_, m) {m}
}
}
\ No newline at end of file
......@@ -30,43 +30,53 @@
"];
export public_methods;
export opt_deref_kind;
// Categorizes a derefable type. Note that we include vectors and strings as
// derefable (we model an index as the combination of a deref and then a
// pointer adjustment).
fn deref_kind(tcx: ty::ctxt, t: ty::t) -> deref_kind {
fn opt_deref_kind(t: ty::t) -> option<deref_kind> {
alt ty::get(t).struct {
ty::ty_uniq(*) | ty::ty_vec(*) | ty::ty_str |
ty::ty_evec(_, ty::vstore_uniq) |
ty::ty_estr(ty::vstore_uniq) {
deref_ptr(uniq_ptr)
some(deref_ptr(uniq_ptr))
}
ty::ty_rptr(*) |
ty::ty_evec(_, ty::vstore_slice(_)) |
ty::ty_estr(ty::vstore_slice(_)) {
deref_ptr(region_ptr)
some(deref_ptr(region_ptr))
}
ty::ty_box(*) |
ty::ty_evec(_, ty::vstore_box) |
ty::ty_estr(ty::vstore_box) {
deref_ptr(gc_ptr)
some(deref_ptr(gc_ptr))
}
ty::ty_ptr(*) {
deref_ptr(unsafe_ptr)
some(deref_ptr(unsafe_ptr))
}
ty::ty_enum(*) {
deref_comp(comp_variant)
some(deref_comp(comp_variant))
}
ty::ty_res(*) {
deref_comp(comp_res)
some(deref_comp(comp_res))
}
_ {
none
}
}
}
fn deref_kind(tcx: ty::ctxt, t: ty::t) -> deref_kind {
alt opt_deref_kind(t) {
some(k) {k}
none {
tcx.sess.bug(
#fmt["deref_cat() invoked on non-derefable type %s",
ty_to_str(tcx, t)]);
......@@ -281,11 +291,12 @@ fn cat_field<N:ast_node>(node: N, base_cmt: cmt, f_name: str) -> cmt {
m_imm { base_cmt.mutbl } // imm: as mutable as the container
m_mutbl | m_const { f_mutbl }
};
let f_comp = comp_field(f_name, f_mutbl);
let lp = base_cmt.lp.map { |lp|
@lp_comp(lp, comp_field(f_name))
@lp_comp(lp, f_comp)
};
@{id: node.id(), span: node.span(),
cat: cat_comp(base_cmt, comp_field(f_name)), lp:lp,
cat: cat_comp(base_cmt, f_comp), lp:lp,
mutbl: m, ty: self.tcx.ty(node)}
}
......@@ -347,8 +358,8 @@ fn cat_index(expr: @ast::expr, base: @ast::expr) -> cmt {
let deref_lp = base_cmt.lp.map { |lp| @lp_deref(lp, ptr) };
let deref_cmt = @{id:expr.id, span:expr.span,
cat:cat_deref(base_cmt, 0u, ptr), lp:deref_lp,
mutbl:mt.mutbl, ty:mt.ty};
let comp = comp_index(base_cmt.ty);
mutbl:m_imm, ty:mt.ty};
let comp = comp_index(base_cmt.ty, mt.mutbl);
let index_lp = deref_lp.map { |lp| @lp_comp(lp, comp) };
@{id:expr.id, span:expr.span,
cat:cat_comp(deref_cmt, comp), lp:index_lp,
......
......@@ -262,7 +262,7 @@ fn is_local_variable(cmt: cmt) -> bool {
fn is_self_field(cmt: cmt) -> bool {
alt cmt.cat {
cat_comp(cmt_base, comp_field(_)) {
cat_comp(cmt_base, comp_field(*)) {
alt cmt_base.cat {
cat_special(sk_self) { true }
_ { false }
......@@ -314,31 +314,54 @@ fn check_assignment(at: assignment_type, ex: @ast::expr) {
// which will be checked for compat separately in
// check_for_conflicting_loans()
if at != at_mutbl_ref {
let lp = alt cmt.lp {
none { ret; }
some(lp) { lp }
};
for self.walk_loans_of(ex.id, lp) { |loan|
alt loan.mutbl {
m_mutbl | m_const { /*ok*/ }
m_imm {
self.bccx.span_err(
ex.span,
#fmt["%s prohibited due to outstanding loan",
at.ing_form(self.bccx.cmt_to_str(cmt))]);
self.bccx.span_note(
loan.cmt.span,
#fmt["loan of %s granted here",
self.bccx.cmt_to_str(loan.cmt)]);
ret;
}
}
for cmt.lp.each { |lp|
self.check_for_loan_conflicting_with_assignment(
at, ex, cmt, lp);
}
}
self.bccx.add_to_mutbl_map(cmt);
}
fn check_for_loan_conflicting_with_assignment(
at: assignment_type,
ex: @ast::expr,
cmt: cmt,
lp: @loan_path) {
for self.walk_loans_of(ex.id, lp) { |loan|
alt loan.mutbl {
m_mutbl | m_const { /*ok*/ }
m_imm {
self.bccx.span_err(
ex.span,
#fmt["%s prohibited due to outstanding loan",
at.ing_form(self.bccx.cmt_to_str(cmt))]);
self.bccx.span_note(
loan.cmt.span,
#fmt["loan of %s granted here",
self.bccx.cmt_to_str(loan.cmt)]);
ret;
}
}
}
// Subtle: if the mutability of the component being assigned
// is inherited from the thing that the component is embedded
// within, then we have to check whether that thing has been
// loaned out as immutable! An example:
// let mut x = {f: some(3)};
// let y = &x; // x loaned out as immutable
// x.f = none; // changes type of y.f, which appears to be imm
alt *lp {
lp_comp(lp_base, ck) if inherent_mutability(ck) != m_mutbl {
self.check_for_loan_conflicting_with_assignment(
at, ex, cmt, lp_base);
}
lp_comp(*) | lp_local(*) | lp_arg(*) | lp_deref(*) {}
}
}
fn report_purity_error(pc: purity_cause, sp: span, msg: str) {
alt pc {
pc_pure_fn {
......
......@@ -6,7 +6,7 @@
// their associated scopes. In phase two, checking loans, we will then make
// sure that all of these loans are honored.
import categorization::public_methods;
import categorization::{public_methods, opt_deref_kind};
import loan::public_methods;
import preserve::public_methods;
......@@ -30,6 +30,8 @@ fn req_loans_in_expr(ex: @ast::expr,
let bccx = self.bccx;
let tcx = bccx.tcx;
#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);
......@@ -64,7 +66,39 @@ fn req_loans_in_expr(ex: @ast::expr,
let arg_cmt = self.bccx.cat_expr(arg);
self.guarantee_valid(arg_cmt, m_imm, scope_r);
}
ast::by_move | ast::by_copy | ast::by_val {}
ast::by_val {
// Rust's by-val does not actually give ownership to
// the callee. This means that if a pointer type is
// passed, it is effectively a borrow, and so the
// caller must guarantee that the data remains valid.
//
// Subtle: we only guarantee that the pointer is valid
// and const. Technically, we ought to pass in the
// mutability that the caller expects (e.g., if the
// formal argument has type @mut, we should guarantee
// validity and mutability, not validity and const).
// However, the type system already guarantees that
// the caller's mutability is compatible with the
// callee, so this is not necessary. (Note that with
// actual borrows, typeck is more liberal and allows
// the pointer to be borrowed as immutable even if it
// is mutable in the caller's frame, thus effectively
// passing the buck onto us to enforce this)
alt opt_deref_kind(arg_ty.ty) {
some(deref_ptr(region_ptr)) {
/* region pointers are (by induction) guaranteed */
}
none {
/* not a pointer, no worries */
}
some(_) {
let arg_cmt = self.bccx.cat_borrow_of_expr(arg);
self.guarantee_valid(arg_cmt, m_const, scope_r);
}
}
}
ast::by_move | ast::by_copy {}
}
}
}
......@@ -78,6 +112,24 @@ fn req_loans_in_expr(ex: @ast::expr,
}
}
ast::expr_field(rcvr, _, _) |
ast::expr_binary(_, rcvr, _) |
ast::expr_unary(_, rcvr) if self.bccx.method_map.contains_key(ex.id) {
// Receivers in method calls are always passed by ref.
//
// FIXME--this scope is both too large and too small. We make
// the scope the enclosing block, which surely includes any
// immediate call (a.b()) but which is too big. OTOH, in the
// case of a naked field `a.b`, the value is copied
// anyhow. This is probably best fixed if we address the
// syntactic ambiguity.
// let scope_r = ty::re_scope(ex.id);
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);
}
_ { /*ok*/ }
}
......
......@@ -55,8 +55,8 @@ fn loan(cmt: cmt, req_mutbl: ast::mutability) {
cat_discr(base, _) {
self.loan(base, req_mutbl)
}
cat_comp(cmt_base, comp_field(_)) |
cat_comp(cmt_base, comp_index(_)) |
cat_comp(cmt_base, comp_field(*)) |
cat_comp(cmt_base, comp_index(*)) |
cat_comp(cmt_base, comp_tuple) |
cat_comp(cmt_base, comp_res) {
// For most components, the type of the embedded data is
......
......@@ -29,8 +29,8 @@ fn preserve(cmt: cmt, opt_scope_id: option<ast::node_id>) -> bckres<()> {
// This is basically a deref of a region ptr.
ok(())
}
cat_comp(cmt_base, comp_field(_)) |
cat_comp(cmt_base, comp_index(_)) |
cat_comp(cmt_base, comp_field(*)) |
cat_comp(cmt_base, comp_index(*)) |
cat_comp(cmt_base, comp_tuple) |
cat_comp(cmt_base, comp_res) {
// Most embedded components: if the base is stable, the
......
......@@ -27,14 +27,18 @@ fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> str {
}
ast_map::node_expr(expr) {
alt expr.node {
ast::expr_call(_, _, _) {
ast::expr_call(*) {
#fmt("<call at %s>",
codemap::span_to_str(expr.span, cx.sess.codemap))
}
ast::expr_alt(_, _, _) {
ast::expr_alt(*) {
#fmt("<alt at %s>",
codemap::span_to_str(expr.span, cx.sess.codemap))
}
ast::expr_field(*) | ast::expr_unary(*) | ast::expr_binary(*) {
#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)]) }
......
// xfail-fast (compile-flags unsupported on windows)
// compile-flags:--borrowck=err
type point = { x: int, y: int };
fn a() {
let mut p = [mut 1];
// Create an immutable pointer into p's contents:
let _q: &int = &p[0]; //! NOTE loan of mutable vec content granted here
p[0] = 5; //! ERROR assigning to mutable vec content prohibited due to outstanding loan
}
fn borrow(_x: [int]/&, _f: fn()) {}
fn b() {
// here we alias the mutable vector into an imm slice and try to
// modify the original:
let mut p = [mut 1];
borrow(p) {|| //! NOTE loan of mutable vec content granted here
p[0] = 5; //! ERROR assigning to mutable vec content prohibited due to outstanding loan
}
}
fn c() {
// Legal because the scope of the borrow does not include the
// modification:
let mut p = [mut 1];
borrow(p, {||});
p[0] = 5;
}
fn main() {
}
// xfail-fast (compile-flags unsupported on windows)
// compile-flags:--borrowck=err
type point = { x: int, y: int };
fn a() {
let mut p = {x: 3, y: 4};
let _q = &p; //! NOTE loan of mutable local variable granted here
// This assignment is illegal because the field x is not
// inherently mutable; since `p` was made immutable, `p.x` is now
// immutable. Otherwise the type of &_q.x (&int) would be wrong.
p.x = 5; //! ERROR assigning to mutable field prohibited due to outstanding loan
}
fn b() {
let mut p = {x: 3, mut y: 4};
let _q = &p;
// This assignment is legal because `y` is inherently mutable (and
// hence &_q.y is &mut int).
p.y = 5;
}
fn c() {
// this is sort of the opposite. We take a loan to the interior of `p`
// and then try to overwrite `p` as a whole.
let mut p = {x: 3, mut y: 4};
let _q = &p.y; //! NOTE loan of mutable local variable granted here
p = {x: 5, mut y: 7};//! ERROR assigning to mutable local variable prohibited due to outstanding loan
copy p;
}
fn d() {
// just for completeness's sake, the easy case, where we take the
// address of a subcomponent and then modify that subcomponent:
let mut p = {x: 3, mut y: 4};
let _q = &p.y; //! NOTE loan of mutable field granted here
p.y = 5; //! ERROR assigning to mutable field prohibited due to outstanding loan
copy p;
}
fn main() {
}
// xfail-fast (compile-flags unsupported on windows)
// compile-flags:--borrowck=err
type point = { x: int, y: int };
impl foo for point {
fn impurem() {
}
pure fn purem() {
}
}
fn a() {
let mut p = {x: 3, y: 4};
p.purem();
p.impurem();
}
fn a2() {
let mut p = {x: 3, y: 4};
p.purem();
p.impurem();
p.x = p.y;
}
fn b() {
let mut p = {x: 3, y: 4};
&mut p; //! NOTE prior loan as mutable granted here
//!^ NOTE prior loan as mutable granted here
p.purem(); //! ERROR loan of mutable local variable as immutable conflicts with prior loan
p.impurem(); //! ERROR loan of mutable local variable as immutable conflicts with prior loan
}
fn c() {
let q = @mut {x: 3, y: 4};
(*q).purem();
(*q).impurem(); //! ERROR illegal borrow unless pure: creating immutable alias to aliasable, mutable memory
//!^ NOTE impure due to access to impure function
}
fn main() {
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册