Skip to content

Commit cfac9b6

Browse files
committed
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
1 parent c5f2c1d commit cfac9b6

File tree

10 files changed

+278
-45
lines changed

10 files changed

+278
-45
lines changed

src/rustc/middle/borrowck.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Borrowck results in two maps.
149149
"];
150150

151151
import syntax::ast;
152-
import syntax::ast::{m_mutbl, m_imm, m_const};
152+
import syntax::ast::{mutability, m_mutbl, m_imm, m_const};
153153
import syntax::visit;
154154
import syntax::ast_util;
155155
import syntax::ast_map;
@@ -254,7 +254,8 @@ enum ptr_kind {uniq_ptr, gc_ptr, region_ptr, unsafe_ptr}
254254
// I am coining the term "components" to mean "pieces of a data
255255
// structure accessible without a dereference":
256256
enum comp_kind {comp_tuple, comp_res, comp_variant,
257-
comp_field(str), comp_index(ty::t)}
257+
comp_field(str, ast::mutability),
258+
comp_index(ty::t, ast::mutability)}
258259

259260
// We pun on *T to mean both actual deref of a ptr as well
260261
// as accessing of components:
@@ -422,8 +423,8 @@ impl to_str_methods for borrowck_ctxt {
422423

423424
fn comp_to_repr(comp: comp_kind) -> str {
424425
alt comp {
425-
comp_field(fld) { fld }
426-
comp_index(_) { "[]" }
426+
comp_field(fld, _) { fld }
427+
comp_index(*) { "[]" }
427428
comp_tuple { "()" }
428429
comp_res { "<res>" }
429430
comp_variant { "<enum>" }
@@ -480,11 +481,11 @@ impl to_str_methods for borrowck_ctxt {
480481
cat_deref(_, _, pk) { #fmt["dereference of %s %s pointer",
481482
mut_str, self.pk_to_sigil(pk)] }
482483
cat_stack_upvar(_) { mut_str + " upvar" }
483-
cat_comp(_, comp_field(_)) { mut_str + " field" }
484+
cat_comp(_, comp_field(*)) { mut_str + " field" }
484485
cat_comp(_, comp_tuple) { "tuple content" }
485486
cat_comp(_, comp_res) { "resource content" }
486487
cat_comp(_, comp_variant) { "enum content" }
487-
cat_comp(_, comp_index(t)) {
488+
cat_comp(_, comp_index(t, _)) {
488489
alt ty::get(t).struct {
489490
ty::ty_vec(*) | ty::ty_evec(*) {
490491
mut_str + " vec content"
@@ -522,3 +523,14 @@ impl to_str_methods for borrowck_ctxt {
522523
}
523524
}
524525
}
526+
527+
// The inherent mutability of a component is its default mutability
528+
// assuming it is embedded in an immutable context. In general, the
529+
// mutability can be "overridden" if the component is embedded in a
530+
// mutable structure.
531+
fn inherent_mutability(ck: comp_kind) -> mutability {
532+
alt ck {
533+
comp_tuple | comp_res | comp_variant {m_imm}
534+
comp_field(_, m) | comp_index(_, m) {m}
535+
}
536+
}

src/rustc/middle/borrowck/categorization.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,53 @@ then an index to jump forward to the relevant item.
3030
"];
3131

3232
export public_methods;
33+
export opt_deref_kind;
3334

3435
// Categorizes a derefable type. Note that we include vectors and strings as
3536
// derefable (we model an index as the combination of a deref and then a
3637
// pointer adjustment).
37-
fn deref_kind(tcx: ty::ctxt, t: ty::t) -> deref_kind {
38+
fn opt_deref_kind(t: ty::t) -> option<deref_kind> {
3839
alt ty::get(t).struct {
3940
ty::ty_uniq(*) | ty::ty_vec(*) | ty::ty_str |
4041
ty::ty_evec(_, ty::vstore_uniq) |
4142
ty::ty_estr(ty::vstore_uniq) {
42-
deref_ptr(uniq_ptr)
43+
some(deref_ptr(uniq_ptr))
4344
}
4445

4546
ty::ty_rptr(*) |
4647
ty::ty_evec(_, ty::vstore_slice(_)) |
4748
ty::ty_estr(ty::vstore_slice(_)) {
48-
deref_ptr(region_ptr)
49+
some(deref_ptr(region_ptr))
4950
}
5051

5152
ty::ty_box(*) |
5253
ty::ty_evec(_, ty::vstore_box) |
5354
ty::ty_estr(ty::vstore_box) {
54-
deref_ptr(gc_ptr)
55+
some(deref_ptr(gc_ptr))
5556
}
5657

5758
ty::ty_ptr(*) {
58-
deref_ptr(unsafe_ptr)
59+
some(deref_ptr(unsafe_ptr))
5960
}
6061

6162
ty::ty_enum(*) {
62-
deref_comp(comp_variant)
63+
some(deref_comp(comp_variant))
6364
}
6465

6566
ty::ty_res(*) {
66-
deref_comp(comp_res)
67+
some(deref_comp(comp_res))
6768
}
6869

6970
_ {
71+
none
72+
}
73+
}
74+
}
75+
76+
fn deref_kind(tcx: ty::ctxt, t: ty::t) -> deref_kind {
77+
alt opt_deref_kind(t) {
78+
some(k) {k}
79+
none {
7080
tcx.sess.bug(
7181
#fmt["deref_cat() invoked on non-derefable type %s",
7282
ty_to_str(tcx, t)]);
@@ -281,11 +291,12 @@ impl public_methods for borrowck_ctxt {
281291
m_imm { base_cmt.mutbl } // imm: as mutable as the container
282292
m_mutbl | m_const { f_mutbl }
283293
};
294+
let f_comp = comp_field(f_name, f_mutbl);
284295
let lp = base_cmt.lp.map { |lp|
285-
@lp_comp(lp, comp_field(f_name))
296+
@lp_comp(lp, f_comp)
286297
};
287298
@{id: node.id(), span: node.span(),
288-
cat: cat_comp(base_cmt, comp_field(f_name)), lp:lp,
299+
cat: cat_comp(base_cmt, f_comp), lp:lp,
289300
mutbl: m, ty: self.tcx.ty(node)}
290301
}
291302

@@ -347,8 +358,8 @@ impl public_methods for borrowck_ctxt {
347358
let deref_lp = base_cmt.lp.map { |lp| @lp_deref(lp, ptr) };
348359
let deref_cmt = @{id:expr.id, span:expr.span,
349360
cat:cat_deref(base_cmt, 0u, ptr), lp:deref_lp,
350-
mutbl:mt.mutbl, ty:mt.ty};
351-
let comp = comp_index(base_cmt.ty);
361+
mutbl:m_imm, ty:mt.ty};
362+
let comp = comp_index(base_cmt.ty, mt.mutbl);
352363
let index_lp = deref_lp.map { |lp| @lp_comp(lp, comp) };
353364
@{id:expr.id, span:expr.span,
354365
cat:cat_comp(deref_cmt, comp), lp:index_lp,

src/rustc/middle/borrowck/check_loans.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ impl methods for check_loan_ctxt {
262262

263263
fn is_self_field(cmt: cmt) -> bool {
264264
alt cmt.cat {
265-
cat_comp(cmt_base, comp_field(_)) {
265+
cat_comp(cmt_base, comp_field(*)) {
266266
alt cmt_base.cat {
267267
cat_special(sk_self) { true }
268268
_ { false }
@@ -314,31 +314,54 @@ impl methods for check_loan_ctxt {
314314
// which will be checked for compat separately in
315315
// check_for_conflicting_loans()
316316
if at != at_mutbl_ref {
317-
let lp = alt cmt.lp {
318-
none { ret; }
319-
some(lp) { lp }
320-
};
321-
for self.walk_loans_of(ex.id, lp) { |loan|
322-
alt loan.mutbl {
323-
m_mutbl | m_const { /*ok*/ }
324-
m_imm {
325-
self.bccx.span_err(
326-
ex.span,
327-
#fmt["%s prohibited due to outstanding loan",
328-
at.ing_form(self.bccx.cmt_to_str(cmt))]);
329-
self.bccx.span_note(
330-
loan.cmt.span,
331-
#fmt["loan of %s granted here",
332-
self.bccx.cmt_to_str(loan.cmt)]);
333-
ret;
334-
}
335-
}
317+
for cmt.lp.each { |lp|
318+
self.check_for_loan_conflicting_with_assignment(
319+
at, ex, cmt, lp);
336320
}
337321
}
338322

339323
self.bccx.add_to_mutbl_map(cmt);
340324
}
341325

326+
fn check_for_loan_conflicting_with_assignment(
327+
at: assignment_type,
328+
ex: @ast::expr,
329+
cmt: cmt,
330+
lp: @loan_path) {
331+
332+
for self.walk_loans_of(ex.id, lp) { |loan|
333+
alt loan.mutbl {
334+
m_mutbl | m_const { /*ok*/ }
335+
m_imm {
336+
self.bccx.span_err(
337+
ex.span,
338+
#fmt["%s prohibited due to outstanding loan",
339+
at.ing_form(self.bccx.cmt_to_str(cmt))]);
340+
self.bccx.span_note(
341+
loan.cmt.span,
342+
#fmt["loan of %s granted here",
343+
self.bccx.cmt_to_str(loan.cmt)]);
344+
ret;
345+
}
346+
}
347+
}
348+
349+
// Subtle: if the mutability of the component being assigned
350+
// is inherited from the thing that the component is embedded
351+
// within, then we have to check whether that thing has been
352+
// loaned out as immutable! An example:
353+
// let mut x = {f: some(3)};
354+
// let y = &x; // x loaned out as immutable
355+
// x.f = none; // changes type of y.f, which appears to be imm
356+
alt *lp {
357+
lp_comp(lp_base, ck) if inherent_mutability(ck) != m_mutbl {
358+
self.check_for_loan_conflicting_with_assignment(
359+
at, ex, cmt, lp_base);
360+
}
361+
lp_comp(*) | lp_local(*) | lp_arg(*) | lp_deref(*) {}
362+
}
363+
}
364+
342365
fn report_purity_error(pc: purity_cause, sp: span, msg: str) {
343366
alt pc {
344367
pc_pure_fn {

src/rustc/middle/borrowck/gather_loans.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// their associated scopes. In phase two, checking loans, we will then make
77
// sure that all of these loans are honored.
88

9-
import categorization::public_methods;
9+
import categorization::{public_methods, opt_deref_kind};
1010
import loan::public_methods;
1111
import preserve::public_methods;
1212

@@ -30,6 +30,8 @@ fn req_loans_in_expr(ex: @ast::expr,
3030
let bccx = self.bccx;
3131
let tcx = bccx.tcx;
3232

33+
#debug["req_loans_in_expr(ex=%s)", pprust::expr_to_str(ex)];
34+
3335
// If this expression is borrowed, have to ensure it remains valid:
3436
for tcx.borrowings.find(ex.id).each { |borrow|
3537
let cmt = self.bccx.cat_borrow_of_expr(ex);
@@ -64,7 +66,39 @@ fn req_loans_in_expr(ex: @ast::expr,
6466
let arg_cmt = self.bccx.cat_expr(arg);
6567
self.guarantee_valid(arg_cmt, m_imm, scope_r);
6668
}
67-
ast::by_move | ast::by_copy | ast::by_val {}
69+
ast::by_val {
70+
// Rust's by-val does not actually give ownership to
71+
// the callee. This means that if a pointer type is
72+
// passed, it is effectively a borrow, and so the
73+
// caller must guarantee that the data remains valid.
74+
//
75+
// Subtle: we only guarantee that the pointer is valid
76+
// and const. Technically, we ought to pass in the
77+
// mutability that the caller expects (e.g., if the
78+
// formal argument has type @mut, we should guarantee
79+
// validity and mutability, not validity and const).
80+
// However, the type system already guarantees that
81+
// the caller's mutability is compatible with the
82+
// callee, so this is not necessary. (Note that with
83+
// actual borrows, typeck is more liberal and allows
84+
// the pointer to be borrowed as immutable even if it
85+
// is mutable in the caller's frame, thus effectively
86+
// passing the buck onto us to enforce this)
87+
88+
alt opt_deref_kind(arg_ty.ty) {
89+
some(deref_ptr(region_ptr)) {
90+
/* region pointers are (by induction) guaranteed */
91+
}
92+
none {
93+
/* not a pointer, no worries */
94+
}
95+
some(_) {
96+
let arg_cmt = self.bccx.cat_borrow_of_expr(arg);
97+
self.guarantee_valid(arg_cmt, m_const, scope_r);
98+
}
99+
}
100+
}
101+
ast::by_move | ast::by_copy {}
68102
}
69103
}
70104
}
@@ -78,6 +112,24 @@ fn req_loans_in_expr(ex: @ast::expr,
78112
}
79113
}
80114

115+
ast::expr_field(rcvr, _, _) |
116+
ast::expr_binary(_, rcvr, _) |
117+
ast::expr_unary(_, rcvr) if self.bccx.method_map.contains_key(ex.id) {
118+
// Receivers in method calls are always passed by ref.
119+
//
120+
// FIXME--this scope is both too large and too small. We make
121+
// the scope the enclosing block, which surely includes any
122+
// immediate call (a.b()) but which is too big. OTOH, in the
123+
// case of a naked field `a.b`, the value is copied
124+
// anyhow. This is probably best fixed if we address the
125+
// syntactic ambiguity.
126+
127+
// let scope_r = ty::re_scope(ex.id);
128+
let scope_r = ty::re_scope(self.tcx().region_map.get(ex.id));
129+
let rcvr_cmt = self.bccx.cat_expr(rcvr);
130+
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);
131+
}
132+
81133
_ { /*ok*/ }
82134
}
83135

src/rustc/middle/borrowck/loan.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ impl loan_methods for loan_ctxt {
5555
cat_discr(base, _) {
5656
self.loan(base, req_mutbl)
5757
}
58-
cat_comp(cmt_base, comp_field(_)) |
59-
cat_comp(cmt_base, comp_index(_)) |
58+
cat_comp(cmt_base, comp_field(*)) |
59+
cat_comp(cmt_base, comp_index(*)) |
6060
cat_comp(cmt_base, comp_tuple) |
6161
cat_comp(cmt_base, comp_res) {
6262
// For most components, the type of the embedded data is

src/rustc/middle/borrowck/preserve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ impl public_methods for borrowck_ctxt {
2929
// This is basically a deref of a region ptr.
3030
ok(())
3131
}
32-
cat_comp(cmt_base, comp_field(_)) |
33-
cat_comp(cmt_base, comp_index(_)) |
32+
cat_comp(cmt_base, comp_field(*)) |
33+
cat_comp(cmt_base, comp_index(*)) |
3434
cat_comp(cmt_base, comp_tuple) |
3535
cat_comp(cmt_base, comp_res) {
3636
// Most embedded components: if the base is stable, the

src/rustc/util/ppaux.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@ fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> str {
2727
}
2828
ast_map::node_expr(expr) {
2929
alt expr.node {
30-
ast::expr_call(_, _, _) {
30+
ast::expr_call(*) {
3131
#fmt("<call at %s>",
3232
codemap::span_to_str(expr.span, cx.sess.codemap))
3333
}
34-
ast::expr_alt(_, _, _) {
34+
ast::expr_alt(*) {
3535
#fmt("<alt at %s>",
3636
codemap::span_to_str(expr.span, cx.sess.codemap))
3737
}
38+
ast::expr_field(*) | ast::expr_unary(*) | ast::expr_binary(*) {
39+
#fmt("<method at %s>",
40+
codemap::span_to_str(expr.span, cx.sess.codemap))
41+
}
3842
_ { cx.sess.bug(
3943
#fmt["re_scope refers to %s",
4044
ast_map::node_id_to_str(cx.items, node_id)]) }
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// xfail-fast (compile-flags unsupported on windows)
2+
// compile-flags:--borrowck=err
3+
4+
type point = { x: int, y: int };
5+
6+
fn a() {
7+
let mut p = [mut 1];
8+
9+
// Create an immutable pointer into p's contents:
10+
let _q: &int = &p[0]; //! NOTE loan of mutable vec content granted here
11+
12+
p[0] = 5; //! ERROR assigning to mutable vec content prohibited due to outstanding loan
13+
}
14+
15+
fn borrow(_x: [int]/&, _f: fn()) {}
16+
17+
fn b() {
18+
// here we alias the mutable vector into an imm slice and try to
19+
// modify the original:
20+
21+
let mut p = [mut 1];
22+
23+
borrow(p) {|| //! NOTE loan of mutable vec content granted here
24+
p[0] = 5; //! ERROR assigning to mutable vec content prohibited due to outstanding loan
25+
}
26+
}
27+
28+
fn c() {
29+
// Legal because the scope of the borrow does not include the
30+
// modification:
31+
let mut p = [mut 1];
32+
borrow(p, {||});
33+
p[0] = 5;
34+
}
35+
36+
fn main() {
37+
}
38+

0 commit comments

Comments
 (0)