1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // ----------------------------------------------------------------------
14 // Phase 2 of check: we walk down the tree and check that:
15 // 1. assignments are always made to mutable locations;
16 // 2. loans made in overlapping scopes do not conflict
17 // 3. assignments do not affect things loaned out as immutable
18 // 4. moves do not affect things loaned out in any way
21 use std::hashmap::HashSet;
22 use mc = middle::mem_categorization;
23 use middle::borrowck::*;
26 use syntax::ast::{m_mutbl, m_imm, m_const};
29 use syntax::codemap::span;
31 use syntax::visit::Visitor;
32 use util::ppaux::Repr;
35 struct CheckLoanCtxt<'self> {
37 dfcx_loans: &'self LoanDataFlow,
38 move_data: @move_data::FlowedMoveData,
39 all_loans: &'self [Loan],
40 reported: @mut HashSet<ast::NodeId>,
43 struct CheckLoanVisitor;
45 impl<'self> Visitor<CheckLoanCtxt<'self>> for CheckLoanVisitor {
46 fn visit_expr<'a>(&mut self, ex:@ast::expr, e:CheckLoanCtxt<'a>) {
47 check_loans_in_expr(self, ex, e);
49 fn visit_local(&mut self, l:@ast::Local, e:CheckLoanCtxt) {
50 check_loans_in_local(self, l, e);
52 fn visit_block(&mut self, b:&ast::Block, e:CheckLoanCtxt) {
53 check_loans_in_block(self, b, e);
55 fn visit_pat(&mut self, p:@ast::pat, e:CheckLoanCtxt) {
56 check_loans_in_pat(self, p, e);
58 fn visit_fn(&mut self, fk:&visit::fn_kind, fd:&ast::fn_decl,
59 b:&ast::Block, s:span, n:ast::NodeId, e:CheckLoanCtxt) {
60 check_loans_in_fn(self, fk, fd, b, s, n, e);
64 pub fn check_loans(bccx: @BorrowckCtxt,
65 dfcx_loans: &LoanDataFlow,
66 move_data: move_data::FlowedMoveData,
69 debug!("check_loans(body id=%?)", body.id);
71 let clcx = CheckLoanCtxt {
73 dfcx_loans: dfcx_loans,
74 move_data: @move_data,
76 reported: @mut HashSet::new(),
79 let mut vt = CheckLoanVisitor;
80 vt.visit_block(body, clcx);
85 MoveWhileBorrowed(/*loan*/@LoanPath, /*loan*/span)
88 impl<'self> CheckLoanCtxt<'self> {
89 pub fn tcx(&self) -> ty::ctxt { self.bccx.tcx }
91 pub fn each_issued_loan(&self,
92 scope_id: ast::NodeId,
93 op: &fn(&Loan) -> bool)
95 //! Iterates over each loan that has been issued
96 //! on entrance to `scope_id`, regardless of whether it is
97 //! actually *in scope* at that point. Sometimes loans
98 //! are issued for future scopes and thus they may have been
99 //! *issued* but not yet be in effect.
101 do self.dfcx_loans.each_bit_on_entry_frozen(scope_id) |loan_index| {
102 let loan = &self.all_loans[loan_index];
107 pub fn each_in_scope_loan(&self,
108 scope_id: ast::NodeId,
109 op: &fn(&Loan) -> bool)
111 //! Like `each_issued_loan()`, but only considers loans that are
112 //! currently in scope.
114 let region_maps = self.tcx().region_maps;
115 do self.each_issued_loan(scope_id) |loan| {
116 if region_maps.is_subscope_of(scope_id, loan.kill_scope) {
124 pub fn each_in_scope_restriction(&self,
125 scope_id: ast::NodeId,
126 loan_path: @LoanPath,
127 op: &fn(&Loan, &Restriction) -> bool)
129 //! Iterates through all the in-scope restrictions for the
130 //! given `loan_path`
132 do self.each_in_scope_loan(scope_id) |loan| {
134 for restr in loan.restrictions.iter() {
135 if restr.loan_path == loan_path {
136 if !op(loan, restr) {
146 pub fn loans_generated_by(&self, scope_id: ast::NodeId) -> ~[uint] {
147 //! Returns a vector of the loans that are generated as
148 //! we encounter `scope_id`.
150 let mut result = ~[];
151 do self.dfcx_loans.each_gen_bit_frozen(scope_id) |loan_index| {
152 result.push(loan_index);
158 pub fn check_for_conflicting_loans(&self, scope_id: ast::NodeId) {
159 //! Checks to see whether any of the loans that are issued
160 //! by `scope_id` conflict with loans that have already been
161 //! issued when we enter `scope_id` (for example, we do not
162 //! permit two `&mut` borrows of the same variable).
164 debug!("check_for_conflicting_loans(scope_id=%?)", scope_id);
166 let new_loan_indices = self.loans_generated_by(scope_id);
167 debug!("new_loan_indices = %?", new_loan_indices);
169 do self.each_issued_loan(scope_id) |issued_loan| {
170 for &new_loan_index in new_loan_indices.iter() {
171 let new_loan = &self.all_loans[new_loan_index];
172 self.report_error_if_loans_conflict(issued_loan, new_loan);
177 for (i, &x) in new_loan_indices.iter().enumerate() {
178 let old_loan = &self.all_loans[x];
179 for &y in new_loan_indices.slice_from(i+1).iter() {
180 let new_loan = &self.all_loans[y];
181 self.report_error_if_loans_conflict(old_loan, new_loan);
186 pub fn report_error_if_loans_conflict(&self,
189 //! Checks whether `old_loan` and `new_loan` can safely be issued
192 debug!("report_error_if_loans_conflict(old_loan=%s, new_loan=%s)",
193 old_loan.repr(self.tcx()),
194 new_loan.repr(self.tcx()));
196 // Should only be called for loans that are in scope at the same time.
197 let region_maps = self.tcx().region_maps;
198 assert!(region_maps.scopes_intersect(old_loan.kill_scope,
199 new_loan.kill_scope));
201 self.report_error_if_loan_conflicts_with_restriction(
202 old_loan, new_loan, old_loan, new_loan) &&
203 self.report_error_if_loan_conflicts_with_restriction(
204 new_loan, old_loan, old_loan, new_loan);
207 pub fn report_error_if_loan_conflicts_with_restriction(&self,
213 //! Checks whether the restrictions introduced by `loan1` would
214 //! prohibit `loan2`. Returns false if an error is reported.
216 debug!("report_error_if_loan_conflicts_with_restriction(\
217 loan1=%s, loan2=%s)",
218 loan1.repr(self.tcx()),
219 loan2.repr(self.tcx()));
221 // Restrictions that would cause the new loan to be illegal:
222 let illegal_if = match loan2.mutbl {
223 m_mutbl => RESTR_ALIAS | RESTR_FREEZE | RESTR_CLAIM,
224 m_imm => RESTR_ALIAS | RESTR_FREEZE,
225 m_const => RESTR_ALIAS,
227 debug!("illegal_if=%?", illegal_if);
229 for restr in loan1.restrictions.iter() {
230 if !restr.set.intersects(illegal_if) { loop; }
231 if restr.loan_path != loan2.loan_path { loop; }
233 match (new_loan.mutbl, old_loan.mutbl) {
234 (m_mutbl, m_mutbl) => {
237 fmt!("cannot borrow `%s` as mutable \
238 more than once at a time",
239 self.bccx.loan_path_to_str(new_loan.loan_path)));
242 fmt!("second borrow of `%s` as mutable occurs here",
243 self.bccx.loan_path_to_str(new_loan.loan_path)));
250 fmt!("cannot borrow `%s` as %s because \
251 it is also borrowed as %s",
252 self.bccx.loan_path_to_str(new_loan.loan_path),
253 self.bccx.mut_to_str(new_loan.mutbl),
254 self.bccx.mut_to_str(old_loan.mutbl)));
257 fmt!("second borrow of `%s` occurs here",
258 self.bccx.loan_path_to_str(new_loan.loan_path)));
267 pub fn is_local_variable(&self, cmt: mc::cmt) -> bool {
269 mc::cat_local(_) => true,
274 pub fn check_if_path_is_moved(&self,
277 use_kind: MovedValueUseKind,
280 * Reports an error if `expr` (which should be a path)
281 * is using a moved/uninitialized value
284 debug!("check_if_path_is_moved(id=%?, use_kind=%?, lp=%s)",
285 id, use_kind, lp.repr(self.bccx.tcx));
286 do self.move_data.each_move_of(id, lp) |move, moved_lp| {
287 self.bccx.report_use_of_moved_value(
297 pub fn check_assignment(&self, expr: @ast::expr) {
298 // We don't use cat_expr() here because we don't want to treat
299 // auto-ref'd parameters in overloaded operators as rvalues.
300 let cmt = match self.bccx.tcx.adjustments.find(&expr.id) {
301 None => self.bccx.cat_expr_unadjusted(expr),
302 Some(&adj) => self.bccx.cat_expr_autoderefd(expr, adj)
305 debug!("check_assignment(cmt=%s)", cmt.repr(self.tcx()));
307 // Mutable values can be assigned, as long as they obey loans
308 // and aliasing restrictions:
309 if cmt.mutbl.is_mutable() {
310 if check_for_aliasable_mutable_writes(self, expr, cmt) {
311 if check_for_assignment_to_restricted_or_frozen_location(
314 // Safe, but record for lint pass later:
315 mark_variable_as_used_mut(self, cmt);
321 // For immutable local variables, assignments are legal
322 // if they cannot already have been assigned
323 if self.is_local_variable(cmt) {
324 assert!(cmt.mutbl.is_immutable()); // no "const" locals
325 let lp = opt_loan_path(cmt).unwrap();
326 do self.move_data.each_assignment_of(expr.id, lp) |assign| {
327 self.bccx.report_reassigned_immutable_variable(
336 // Otherwise, just a plain error.
339 fmt!("cannot assign to %s %s",
340 cmt.mutbl.to_user_str(),
341 self.bccx.cmt_to_str(cmt)));
344 fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
346 //! If the mutability of the `cmt` being written is inherited
347 //! from a local variable, liveness will
348 //! not have been able to detect that this variable's mutability
349 //! is important, so we must add the variable to the
350 //! `used_mut_nodes` table here.
354 debug!("mark_writes_through_upvars_as_used_mut(cmt=%s)",
355 cmt.repr(this.tcx()));
359 mc::cat_self(id) => {
360 this.tcx().used_mut_nodes.insert(id);
364 mc::cat_stack_upvar(b) => {
369 mc::cat_static_item |
370 mc::cat_copied_upvar(*) |
371 mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
372 mc::cat_deref(_, _, mc::gc_ptr(*)) |
373 mc::cat_deref(_, _, mc::region_ptr(*)) => {
374 assert_eq!(cmt.mutbl, mc::McDeclared);
378 mc::cat_discr(b, _) |
379 mc::cat_deref(b, _, mc::uniq_ptr) => {
380 assert_eq!(cmt.mutbl, mc::McInherited);
384 mc::cat_downcast(b) |
385 mc::cat_interior(b, _) => {
386 if cmt.mutbl == mc::McInherited {
389 return; // field declared as mutable or some such
396 fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
398 cmt: mc::cmt) -> bool {
399 //! Safety checks related to writes to aliasable, mutable locations
401 let guarantor = cmt.guarantor();
402 debug!("check_for_aliasable_mutable_writes(cmt=%s, guarantor=%s)",
403 cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
404 match guarantor.cat {
405 mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) => {
406 // Statically prohibit writes to `&mut` when aliasable
408 check_for_aliasability_violation(this, expr, b);
411 mc::cat_deref(_, deref_count, mc::gc_ptr(ast::m_mutbl)) => {
412 // Dynamically check writes to `@mut`
414 let key = root_map_key {
418 debug!("Inserting write guard at %?", key);
419 this.bccx.write_guard_map.insert(key);
425 return true; // no errors reported
428 fn check_for_aliasability_violation(this: &CheckLoanCtxt,
430 cmt: mc::cmt) -> bool {
435 mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) |
436 mc::cat_downcast(b) |
437 mc::cat_stack_upvar(b) |
438 mc::cat_deref(b, _, mc::uniq_ptr) |
439 mc::cat_interior(b, _) |
440 mc::cat_discr(b, _) => {
441 // Aliasability depends on base cmt
445 mc::cat_copied_upvar(_) |
450 mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
451 mc::cat_static_item(*) |
452 mc::cat_deref(_, _, mc::gc_ptr(_)) |
453 mc::cat_deref(_, _, mc::region_ptr(m_const, _)) |
454 mc::cat_deref(_, _, mc::region_ptr(m_imm, _)) => {
455 // Aliasability is independent of base cmt
456 match cmt.freely_aliasable() {
461 this.bccx.report_aliasability_violation(
473 fn check_for_assignment_to_restricted_or_frozen_location(
474 this: &CheckLoanCtxt,
476 cmt: mc::cmt) -> bool
478 //! Check for assignments that violate the terms of an
479 //! outstanding loan.
481 let loan_path = match opt_loan_path(cmt) {
483 None => { return true; /* no loan path, can't be any loans */ }
486 // Start by searching for an assignment to a *restricted*
487 // location. Here is one example of the kind of error caught
490 // let mut v = ~[1, 2, 3];
494 // In this case, creating `p` triggers a RESTR_MUTATE
495 // restriction on the path `v`.
497 // Here is a second, more subtle example:
499 // let mut v = ~[1, 2, 3];
500 // let p = &const v[0];
503 // v = ~[4, 5, 3]; // Error
505 // In this case, `p` is pointing to `v[0]`, and it is a
506 // `const` pointer in any case. So the first two
507 // assignments are legal (and would be permitted by this
508 // check). However, the final assignment (which is
509 // logically equivalent) is forbidden, because it would
510 // cause the existing `v` array to be freed, thus
511 // invalidating `p`. In the code, this error results
512 // because `gather_loans::restrictions` adds a
513 // `RESTR_MUTATE` restriction whenever the contents of an
514 // owned pointer are borrowed, and hence while `v[*]` is not
515 // restricted from being written, `v` is.
516 let cont = do this.each_in_scope_restriction(expr.id, loan_path)
519 if restr.set.intersects(RESTR_MUTATE) {
520 this.report_illegal_mutation(expr, loan_path, loan);
527 if !cont { return false }
529 // The previous code handled assignments to paths that
530 // have been restricted. This covers paths that have been
531 // directly lent out and their base paths, but does not
532 // cover random extensions of those paths. For example,
533 // the following program is not declared illegal by the
536 // let mut v = ~[1, 2, 3];
538 // v[0] = 4; // declared error by loop below, not code above
540 // The reason that this passes the previous check whereas
541 // an assignment like `v = ~[4]` fails is because the assignment
542 // here is to `v[*]`, and the existing restrictions were issued
543 // for `v`, not `v[*]`.
545 // So in this loop, we walk back up the loan path so long
546 // as the mutability of the path is dependent on a super
547 // path, and check that the super path was not lent out as
548 // mutable or immutable (a const loan is ok).
550 // Mutability of a path can be dependent on the super path
551 // in two ways. First, it might be inherited mutability.
552 // Second, the pointee of an `&mut` pointer can only be
553 // mutated if it is found in an unaliased location, so we
554 // have to check that the owner location is not borrowed.
556 // Note that we are *not* checking for any and all
557 // restrictions. We are only interested in the pointers
558 // that the user created, whereas we add restrictions for
559 // all kinds of paths that are not directly aliased. If we checked
560 // for all restrictions, and not just loans, then the following
561 // valid program would be considered illegal:
563 // let mut v = ~[1, 2, 3];
564 // let p = &const v[0];
567 // Here the restriction that `v` not be mutated would be misapplied
568 // to block the subpath `v[1]`.
569 let full_loan_path = loan_path;
570 let mut loan_path = loan_path;
573 // Peel back one layer if, for `loan_path` to be
574 // mutable, `lp_base` must be mutable. This occurs
575 // with inherited mutability and with `&mut`
577 LpExtend(lp_base, mc::McInherited, _) |
578 LpExtend(lp_base, _, LpDeref(mc::region_ptr(ast::m_mutbl, _))) => {
582 // Otherwise stop iterating
583 LpExtend(_, mc::McDeclared, _) |
584 LpExtend(_, mc::McImmutable, _) |
585 LpExtend(_, mc::McReadOnly, _) |
591 // Check for a non-const loan of `loan_path`
592 let cont = do this.each_in_scope_loan(expr.id) |loan| {
593 if loan.loan_path == loan_path && loan.mutbl != m_const {
594 this.report_illegal_mutation(expr, full_loan_path, loan);
601 if !cont { return false }
606 pub fn report_illegal_mutation(&self,
608 loan_path: &LoanPath,
612 fmt!("cannot assign to `%s` because it is borrowed",
613 self.bccx.loan_path_to_str(loan_path)));
616 fmt!("borrow of `%s` occurs here",
617 self.bccx.loan_path_to_str(loan_path)));
620 fn check_move_out_from_expr(&self, expr: @ast::expr) {
622 ast::expr_fn_block(*) => {
623 // moves due to capture clauses are checked
624 // in `check_loans_in_fn`, so that we can
625 // give a better error message
628 self.check_move_out_from_id(expr.id, expr.span)
633 fn check_move_out_from_id(&self, id: ast::NodeId, span: span) {
634 do self.move_data.each_path_moved_by(id) |_, move_path| {
635 match self.analyze_move_out_from(id, move_path) {
637 MoveWhileBorrowed(loan_path, loan_span) => {
640 fmt!("cannot move out of `%s` \
641 because it is borrowed",
642 self.bccx.loan_path_to_str(move_path)));
645 fmt!("borrow of `%s` occurs here",
646 self.bccx.loan_path_to_str(loan_path)));
653 pub fn analyze_move_out_from(&self,
654 expr_id: ast::NodeId,
655 move_path: @LoanPath) -> MoveError {
656 debug!("analyze_move_out_from(expr_id=%?, move_path=%s)",
657 expr_id, move_path.repr(self.tcx()));
659 // FIXME(#4384) inadequare if/when we permit `move a.b`
661 let mut ret = MoveOk;
663 // check for a conflicting loan:
664 do self.each_in_scope_restriction(expr_id, move_path) |loan, _| {
665 // Any restriction prevents moves.
666 ret = MoveWhileBorrowed(loan.loan_path, loan.span);
673 pub fn check_call(&self,
675 _callee: Option<@ast::expr>,
676 _callee_id: ast::NodeId,
678 _args: &[@ast::expr]) {
679 // NB: This call to check for conflicting loans is not truly
680 // necessary, because the callee_id never issues new loans.
681 // However, I added it for consistency and lest the system
682 // should change in the future.
684 // FIXME(#6268) nested method calls
685 // self.check_for_conflicting_loans(callee_id);
689 fn check_loans_in_fn<'a>(visitor: &mut CheckLoanVisitor,
695 this: CheckLoanCtxt<'a>) {
697 visit::fk_item_fn(*) |
698 visit::fk_method(*) => {
699 // Don't process nested items.
704 visit::fk_fn_block(*) => {
705 check_captured_variables(this, id, sp);
709 visit::walk_fn(visitor, fk, decl, body, sp, id, this);
711 fn check_captured_variables(this: CheckLoanCtxt,
712 closure_id: ast::NodeId,
714 let cap_vars = this.bccx.capture_map.get(&closure_id);
715 for cap_var in cap_vars.iter() {
716 let var_id = ast_util::def_id_of_def(cap_var.def).node;
717 let var_path = @LpVar(var_id);
718 this.check_if_path_is_moved(closure_id, span,
719 MovedInCapture, var_path);
721 moves::CapRef | moves::CapCopy => {}
723 check_by_move_capture(this, closure_id, cap_var, var_path);
729 fn check_by_move_capture(this: CheckLoanCtxt,
730 closure_id: ast::NodeId,
731 cap_var: &moves::CaptureVar,
732 move_path: @LoanPath) {
733 let move_err = this.analyze_move_out_from(closure_id, move_path);
736 MoveWhileBorrowed(loan_path, loan_span) => {
739 fmt!("cannot move `%s` into closure \
740 because it is borrowed",
741 this.bccx.loan_path_to_str(move_path)));
744 fmt!("borrow of `%s` occurs here",
745 this.bccx.loan_path_to_str(loan_path)));
752 fn check_loans_in_local<'a>(vt: &mut CheckLoanVisitor,
754 this: CheckLoanCtxt<'a>) {
755 visit::walk_local(vt, local, this);
758 fn check_loans_in_expr<'a>(vt: &mut CheckLoanVisitor,
760 this: CheckLoanCtxt<'a>) {
761 visit::walk_expr(vt, expr, this);
763 debug!("check_loans_in_expr(expr=%s)",
764 expr.repr(this.tcx()));
766 this.check_for_conflicting_loans(expr.id);
767 this.check_move_out_from_expr(expr);
771 ast::expr_path(*) => {
772 if !this.move_data.is_assignee(expr.id) {
773 let cmt = this.bccx.cat_expr_unadjusted(expr);
774 debug!("path cmt=%s", cmt.repr(this.tcx()));
775 let r = opt_loan_path(cmt);
776 for &lp in r.iter() {
777 this.check_if_path_is_moved(expr.id, expr.span, MovedInUse, lp);
781 ast::expr_assign(dest, _) |
782 ast::expr_assign_op(_, _, dest, _) => {
783 this.check_assignment(dest);
785 ast::expr_call(f, ref args, _) => {
786 this.check_call(expr, Some(f), f.id, f.span, *args);
788 ast::expr_method_call(callee_id, _, _, _, ref args, _) => {
789 this.check_call(expr, None, callee_id, expr.span, *args);
791 ast::expr_index(callee_id, _, rval) |
792 ast::expr_binary(callee_id, _, _, rval)
793 if this.bccx.method_map.contains_key(&expr.id) => {
794 this.check_call(expr,
800 ast::expr_unary(callee_id, _, _) | ast::expr_index(callee_id, _, _)
801 if this.bccx.method_map.contains_key(&expr.id) => {
802 this.check_call(expr,
812 fn check_loans_in_pat<'a>(vt: &mut CheckLoanVisitor,
814 this: CheckLoanCtxt<'a>)
816 this.check_for_conflicting_loans(pat.id);
817 this.check_move_out_from_id(pat.id, pat.span);
818 visit::walk_pat(vt, pat, this);
821 fn check_loans_in_block<'a>(vt: &mut CheckLoanVisitor,
823 this: CheckLoanCtxt<'a>)
825 visit::walk_block(vt, blk, this);
826 this.check_for_conflicting_loans(blk.id);