1 // Copyright 2012-2013 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.
4 //
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.
10
11 // ----------------------------------------------------------------------
12 // Checking loans
13 //
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
19
20
21 use middle::borrowck::*;
22 use euv = middle::expr_use_visitor;
23 use middle::freevars;
24 use mc = middle::mem_categorization;
25 use middle::ty;
26 use middle::typeck::MethodCall;
27 use syntax::ast;
28 use syntax::ast_util;
29 use syntax::codemap::Span;
30 use syntax::visit::Visitor;
31 use syntax::visit;
32 use util::ppaux::Repr;
33
34 use std::rc::Rc;
35
36 struct CheckLoanCtxt<'a> {
37 bccx: &'a BorrowckCtxt<'a>,
38 dfcx_loans: &'a LoanDataFlow<'a>,
39 move_data: move_data::FlowedMoveData<'a>,
40 all_loans: &'a [Loan],
41 }
42
43 impl<'a> Visitor<()> for CheckLoanCtxt<'a> {
44
45 fn visit_expr(&mut self, ex: &ast::Expr, _: ()) {
46 check_loans_in_expr(self, ex);
47 }
48 fn visit_local(&mut self, l: &ast::Local, _: ()) {
49 check_loans_in_local(self, l);
50 }
51 fn visit_block(&mut self, b: &ast::Block, _: ()) {
52 check_loans_in_block(self, b);
53 }
54 fn visit_pat(&mut self, p: &ast::Pat, _: ()) {
55 check_loans_in_pat(self, p);
56 }
57 fn visit_fn(&mut self, _fk: &visit::FnKind, _fd: &ast::FnDecl,
58 _b: &ast::Block, _s: Span, _n: ast::NodeId, _: ()) {
59 // Don't process nested items or closures here,
60 // the outer loop will take care of it.
61 return;
62 }
63
64 // FIXME(#10894) should continue recursing
65 fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {}
66 }
67
68 pub fn check_loans(bccx: &BorrowckCtxt,
69 dfcx_loans: &LoanDataFlow,
70 move_data: move_data::FlowedMoveData,
71 all_loans: &[Loan],
72 body: &ast::Block) {
73 debug!("check_loans(body id={:?})", body.id);
74
75 let mut clcx = CheckLoanCtxt {
76 bccx: bccx,
77 dfcx_loans: dfcx_loans,
78 move_data: move_data,
79 all_loans: all_loans,
80 };
81
82 clcx.visit_block(body, ());
83 }
84
85 #[deriving(Eq)]
86 enum MoveError {
87 MoveOk,
88 MoveWhileBorrowed(/*loan*/Rc<LoanPath>, /*loan*/Span)
89 }
90
91 impl<'a> CheckLoanCtxt<'a> {
92 pub fn tcx(&self) -> &'a ty::ctxt { self.bccx.tcx }
93
94 pub fn each_issued_loan(&self, scope_id: ast::NodeId, op: |&Loan| -> bool)
95 -> bool {
96 //! Iterates over each loan that has been issued
97 //! on entrance to `scope_id`, regardless of whether it is
98 //! actually *in scope* at that point. Sometimes loans
99 //! are issued for future scopes and thus they may have been
100 //! *issued* but not yet be in effect.
101
102 self.dfcx_loans.each_bit_on_entry_frozen(scope_id, |loan_index| {
103 let loan = &self.all_loans[loan_index];
104 op(loan)
105 })
106 }
107
108 pub fn each_in_scope_loan(&self,
109 scope_id: ast::NodeId,
110 op: |&Loan| -> bool)
111 -> bool {
112 //! Like `each_issued_loan()`, but only considers loans that are
113 //! currently in scope.
114
115 let tcx = self.tcx();
116 self.each_issued_loan(scope_id, |loan| {
117 if tcx.region_maps.is_subscope_of(scope_id, loan.kill_scope) {
118 op(loan)
119 } else {
120 true
121 }
122 })
123 }
124
125 pub fn each_in_scope_restriction(&self,
126 scope_id: ast::NodeId,
127 loan_path: &LoanPath,
128 op: |&Loan, &Restriction| -> bool)
129 -> bool {
130 //! Iterates through all the in-scope restrictions for the
131 //! given `loan_path`
132
133 self.each_in_scope_loan(scope_id, |loan| {
134 debug!("each_in_scope_restriction found loan: {:?}",
135 loan.repr(self.tcx()));
136
137 let mut ret = true;
138 for restr in loan.restrictions.iter() {
139 if *restr.loan_path == *loan_path {
140 if !op(loan, restr) {
141 ret = false;
142 break;
143 }
144 }
145 }
146 ret
147 })
148 }
149
150 pub fn loans_generated_by(&self, scope_id: ast::NodeId) -> Vec<uint> {
151 //! Returns a vector of the loans that are generated as
152 //! we encounter `scope_id`.
153
154 let mut result = Vec::new();
155 self.dfcx_loans.each_gen_bit_frozen(scope_id, |loan_index| {
156 result.push(loan_index);
157 true
158 });
159 return result;
160 }
161
162 pub fn check_for_conflicting_loans(&self, scope_id: ast::NodeId) {
163 //! Checks to see whether any of the loans that are issued
164 //! by `scope_id` conflict with loans that have already been
165 //! issued when we enter `scope_id` (for example, we do not
166 //! permit two `&mut` borrows of the same variable).
167
168 debug!("check_for_conflicting_loans(scope_id={:?})", scope_id);
169
170 let new_loan_indices = self.loans_generated_by(scope_id);
171 debug!("new_loan_indices = {:?}", new_loan_indices);
172
173 self.each_issued_loan(scope_id, |issued_loan| {
174 for &new_loan_index in new_loan_indices.iter() {
175 let new_loan = &self.all_loans[new_loan_index];
176 self.report_error_if_loans_conflict(issued_loan, new_loan);
177 }
178 true
179 });
180
181 for (i, &x) in new_loan_indices.iter().enumerate() {
182 let old_loan = &self.all_loans[x];
183 for &y in new_loan_indices.slice_from(i+1).iter() {
184 let new_loan = &self.all_loans[y];
185 self.report_error_if_loans_conflict(old_loan, new_loan);
186 }
187 }
188 }
189
190 pub fn report_error_if_loans_conflict(&self,
191 old_loan: &Loan,
192 new_loan: &Loan) {
193 //! Checks whether `old_loan` and `new_loan` can safely be issued
194 //! simultaneously.
195
196 debug!("report_error_if_loans_conflict(old_loan={}, new_loan={})",
197 old_loan.repr(self.tcx()),
198 new_loan.repr(self.tcx()));
199
200 // Should only be called for loans that are in scope at the same time.
201 assert!(self.tcx().region_maps.scopes_intersect(old_loan.kill_scope,
202 new_loan.kill_scope));
203
204 self.report_error_if_loan_conflicts_with_restriction(
205 old_loan, new_loan, old_loan, new_loan) &&
206 self.report_error_if_loan_conflicts_with_restriction(
207 new_loan, old_loan, old_loan, new_loan);
208 }
209
210 pub fn report_error_if_loan_conflicts_with_restriction(&self,
211 loan1: &Loan,
212 loan2: &Loan,
213 old_loan: &Loan,
214 new_loan: &Loan)
215 -> bool {
216 //! Checks whether the restrictions introduced by `loan1` would
217 //! prohibit `loan2`. Returns false if an error is reported.
218
219 debug!("report_error_if_loan_conflicts_with_restriction(\
220 loan1={}, loan2={})",
221 loan1.repr(self.tcx()),
222 loan2.repr(self.tcx()));
223
224 // Restrictions that would cause the new loan to be illegal:
225 let illegal_if = match loan2.kind {
226 // Look for restrictions against mutation. These are
227 // generated by all other borrows.
228 ty::MutBorrow => RESTR_MUTATE,
229
230 // Look for restrictions against freezing (immutable borrows).
231 // These are generated by `&mut` borrows.
232 ty::ImmBorrow => RESTR_FREEZE,
233
234 // No matter how the data is borrowed (as `&`, as `&mut`,
235 // or as `&unique imm`) it will always generate a
236 // restriction against mutating the data. So look for those.
237 ty::UniqueImmBorrow => RESTR_MUTATE,
238 };
239 debug!("illegal_if={:?}", illegal_if);
240
241 for restr in loan1.restrictions.iter() {
242 if !restr.set.intersects(illegal_if) { continue; }
243 if restr.loan_path != loan2.loan_path { continue; }
244
245 let old_pronoun = if new_loan.loan_path == old_loan.loan_path {
246 "it".to_owned()
247 } else {
248 format!("`{}`",
249 self.bccx.loan_path_to_str(&*old_loan.loan_path))
250 };
251
252 match (new_loan.kind, old_loan.kind) {
253 (ty::MutBorrow, ty::MutBorrow) => {
254 self.bccx.span_err(
255 new_loan.span,
256 format!("cannot borrow `{}` as mutable \
257 more than once at a time",
258 self.bccx.loan_path_to_str(&*new_loan.loan_path)));
259 }
260
261 (ty::UniqueImmBorrow, _) => {
262 self.bccx.span_err(
263 new_loan.span,
264 format!("closure requires unique access to `{}` \
265 but {} is already borrowed",
266 self.bccx.loan_path_to_str(&*new_loan.loan_path),
267 old_pronoun));
268 }
269
270 (_, ty::UniqueImmBorrow) => {
271 self.bccx.span_err(
272 new_loan.span,
273 format!("cannot borrow `{}` as {} because \
274 previous closure requires unique access",
275 self.bccx.loan_path_to_str(&*new_loan.loan_path),
276 new_loan.kind.to_user_str()));
277 }
278
279 (_, _) => {
280 self.bccx.span_err(
281 new_loan.span,
282 format!("cannot borrow `{}` as {} because \
283 {} is also borrowed as {}",
284 self.bccx.loan_path_to_str(&*new_loan.loan_path),
285 new_loan.kind.to_user_str(),
286 old_pronoun,
287 old_loan.kind.to_user_str()));
288 }
289 }
290
291 match new_loan.cause {
292 euv::ClosureCapture(span) => {
293 self.bccx.span_note(
294 span,
295 format!("borrow occurs due to use of `{}` in closure",
296 self.bccx.loan_path_to_str(&*new_loan.loan_path)));
297 }
298 _ => { }
299 }
300
301 let rule_summary = match old_loan.kind {
302 ty::MutBorrow => {
303 format!("the mutable borrow prevents subsequent \
304 moves, borrows, or modification of `{0}` \
305 until the borrow ends",
306 self.bccx.loan_path_to_str(&*old_loan.loan_path))
307 }
308
309 ty::ImmBorrow => {
310 format!("the immutable borrow prevents subsequent \
311 moves or mutable borrows of `{0}` \
312 until the borrow ends",
313 self.bccx.loan_path_to_str(&*old_loan.loan_path))
314 }
315
316 ty::UniqueImmBorrow => {
317 format!("the unique capture prevents subsequent \
318 moves or borrows of `{0}` \
319 until the borrow ends",
320 self.bccx.loan_path_to_str(&*old_loan.loan_path))
321 }
322 };
323
324 let borrow_summary = match old_loan.cause {
325 euv::ClosureCapture(_) => {
326 format!("previous borrow of `{}` occurs here due to \
327 use in closure",
328 self.bccx.loan_path_to_str(&*old_loan.loan_path))
329 }
330
331 euv::OverloadedOperator(..) |
332 euv::AddrOf(..) |
333 euv::AutoRef(..) |
334 euv::ClosureInvocation(..) |
335 euv::RefBinding(..) => {
336 format!("previous borrow of `{}` occurs here",
337 self.bccx.loan_path_to_str(&*old_loan.loan_path))
338 }
339 };
340
341 self.bccx.span_note(
342 old_loan.span,
343 format!("{}; {}", borrow_summary, rule_summary));
344
345 let old_loan_span = self.tcx().map.span(old_loan.kill_scope);
346 self.bccx.span_end_note(old_loan_span,
347 "previous borrow ends here");
348
349 return false;
350 }
351
352 true
353 }
354
355 pub fn is_local_variable(&self, cmt: mc::cmt) -> bool {
356 match cmt.cat {
357 mc::cat_local(_) => true,
358 _ => false
359 }
360 }
361
362 pub fn check_if_path_is_moved(&self,
363 id: ast::NodeId,
364 span: Span,
365 use_kind: MovedValueUseKind,
366 lp: &Rc<LoanPath>) {
367 /*!
368 * Reports an error if `expr` (which should be a path)
369 * is using a moved/uninitialized value
370 */
371
372 debug!("check_if_path_is_moved(id={:?}, use_kind={:?}, lp={})",
373 id, use_kind, lp.repr(self.bccx.tcx));
374 self.move_data.each_move_of(id, lp, |move, moved_lp| {
375 self.bccx.report_use_of_moved_value(
376 span,
377 use_kind,
378 &**lp,
379 move,
380 moved_lp);
381 false
382 });
383 }
384
385 pub fn check_assignment(&self, expr: &ast::Expr) {
386 // We don't use cat_expr() here because we don't want to treat
387 // auto-ref'd parameters in overloaded operators as rvalues.
388 let cmt = match self.bccx.tcx.adjustments.borrow().find(&expr.id) {
389 None => self.bccx.cat_expr_unadjusted(expr),
390 Some(adj) => self.bccx.cat_expr_autoderefd(expr, adj)
391 };
392
393 debug!("check_assignment(cmt={})", cmt.repr(self.tcx()));
394
395 // Mutable values can be assigned, as long as they obey loans
396 // and aliasing restrictions:
397 if cmt.mutbl.is_mutable() {
398 if check_for_aliasable_mutable_writes(self, expr, cmt.clone()) {
399 if check_for_assignment_to_restricted_or_frozen_location(
400 self, expr, cmt.clone()) {
401 // Safe, but record for lint pass later:
402 mark_variable_as_used_mut(self, cmt);
403 }
404 }
405 return;
406 }
407
408 // For immutable local variables, assignments are legal
409 // if they cannot already have been assigned
410 if self.is_local_variable(cmt.clone()) {
411 assert!(cmt.mutbl.is_immutable()); // no "const" locals
412 let lp = opt_loan_path(&cmt).unwrap();
413 self.move_data.each_assignment_of(expr.id, &lp, |assign| {
414 self.bccx.report_reassigned_immutable_variable(
415 expr.span,
416 &*lp,
417 assign);
418 false
419 });
420 return;
421 }
422
423 // Otherwise, just a plain error.
424 match opt_loan_path(&cmt) {
425 Some(lp) => {
426 self.bccx.span_err(
427 expr.span,
428 format!("cannot assign to {} {} `{}`",
429 cmt.mutbl.to_user_str(),
430 self.bccx.cmt_to_str(&*cmt),
431 self.bccx.loan_path_to_str(&*lp)));
432 }
433 None => {
434 self.bccx.span_err(
435 expr.span,
436 format!("cannot assign to {} {}",
437 cmt.mutbl.to_user_str(),
438 self.bccx.cmt_to_str(&*cmt)));
439 }
440 }
441 return;
442
443 fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
444 cmt: mc::cmt) {
445 //! If the mutability of the `cmt` being written is inherited
446 //! from a local variable, liveness will
447 //! not have been able to detect that this variable's mutability
448 //! is important, so we must add the variable to the
449 //! `used_mut_nodes` table here.
450
451 let mut cmt = cmt;
452 loop {
453 debug!("mark_writes_through_upvars_as_used_mut(cmt={})",
454 cmt.repr(this.tcx()));
455 match cmt.cat.clone() {
456 mc::cat_local(id) | mc::cat_arg(id) => {
457 this.tcx().used_mut_nodes.borrow_mut().insert(id);
458 return;
459 }
460
461 mc::cat_upvar(..) => {
462 return;
463 }
464
465 mc::cat_deref(_, _, mc::GcPtr) => {
466 assert_eq!(cmt.mutbl, mc::McImmutable);
467 return;
468 }
469
470 mc::cat_rvalue(..) |
471 mc::cat_static_item |
472 mc::cat_copied_upvar(..) |
473 mc::cat_deref(_, _, mc::UnsafePtr(..)) |
474 mc::cat_deref(_, _, mc::BorrowedPtr(..)) => {
475 assert_eq!(cmt.mutbl, mc::McDeclared);
476 return;
477 }
478
479 mc::cat_discr(b, _) |
480 mc::cat_deref(b, _, mc::OwnedPtr) => {
481 assert_eq!(cmt.mutbl, mc::McInherited);
482 cmt = b;
483 }
484
485 mc::cat_downcast(b) |
486 mc::cat_interior(b, _) => {
487 assert_eq!(cmt.mutbl, mc::McInherited);
488 cmt = b;
489 }
490 }
491 }
492 }
493
494 fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
495 expr: &ast::Expr,
496 cmt: mc::cmt) -> bool {
497 //! Safety checks related to writes to aliasable, mutable locations
498
499 let guarantor = cmt.guarantor();
500 debug!("check_for_aliasable_mutable_writes(cmt={}, guarantor={})",
501 cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
502 match guarantor.cat {
503 mc::cat_deref(ref b, _, mc::BorrowedPtr(ty::MutBorrow, _)) => {
504 // Statically prohibit writes to `&mut` when aliasable
505
506 check_for_aliasability_violation(this, expr, b.clone());
507 }
508
509 _ => {}
510 }
511
512 return true; // no errors reported
513 }
514
515 fn check_for_aliasability_violation(this: &CheckLoanCtxt,
516 expr: &ast::Expr,
517 cmt: mc::cmt)
518 -> bool {
519 match cmt.freely_aliasable(this.tcx()) {
520 None => {
521 return true;
522 }
523 Some(mc::AliasableStaticMut(..)) => {
524 return true;
525 }
526 Some(cause) => {
527 this.bccx.report_aliasability_violation(
528 expr.span,
529 MutabilityViolation,
530 cause);
531 return false;
532 }
533 }
534 }
535
536 fn check_for_assignment_to_restricted_or_frozen_location(
537 this: &CheckLoanCtxt,
538 expr: &ast::Expr,
539 cmt: mc::cmt) -> bool
540 {
541 //! Check for assignments that violate the terms of an
542 //! outstanding loan.
543
544 let loan_path = match opt_loan_path(&cmt) {
545 Some(lp) => lp,
546 None => { return true; /* no loan path, can't be any loans */ }
547 };
548
549 // Start by searching for an assignment to a *restricted*
550 // location. Here is one example of the kind of error caught
551 // by this check:
552 //
553 // let mut v = ~[1, 2, 3];
554 // let p = &v;
555 // v = ~[4];
556 //
557 // In this case, creating `p` triggers a RESTR_MUTATE
558 // restriction on the path `v`.
559 //
560 // Here is a second, more subtle example:
561 //
562 // let mut v = ~[1, 2, 3];
563 // let p = &const v[0];
564 // v[0] = 4; // OK
565 // v[1] = 5; // OK
566 // v = ~[4, 5, 3]; // Error
567 //
568 // In this case, `p` is pointing to `v[0]`, and it is a
569 // `const` pointer in any case. So the first two
570 // assignments are legal (and would be permitted by this
571 // check). However, the final assignment (which is
572 // logically equivalent) is forbidden, because it would
573 // cause the existing `v` array to be freed, thus
574 // invalidating `p`. In the code, this error results
575 // because `gather_loans::restrictions` adds a
576 // `RESTR_MUTATE` restriction whenever the contents of an
577 // owned pointer are borrowed, and hence while `v[*]` is not
578 // restricted from being written, `v` is.
579 let cont = this.each_in_scope_restriction(expr.id,
580 &*loan_path,
581 |loan, restr| {
582 if restr.set.intersects(RESTR_MUTATE) {
583 this.report_illegal_mutation(expr, &*loan_path, loan);
584 false
585 } else {
586 true
587 }
588 });
589
590 if !cont { return false }
591
592 // The previous code handled assignments to paths that
593 // have been restricted. This covers paths that have been
594 // directly lent out and their base paths, but does not
595 // cover random extensions of those paths. For example,
596 // the following program is not declared illegal by the
597 // previous check:
598 //
599 // let mut v = ~[1, 2, 3];
600 // let p = &v;
601 // v[0] = 4; // declared error by loop below, not code above
602 //
603 // The reason that this passes the previous check whereas
604 // an assignment like `v = ~[4]` fails is because the assignment
605 // here is to `v[*]`, and the existing restrictions were issued
606 // for `v`, not `v[*]`.
607 //
608 // So in this loop, we walk back up the loan path so long
609 // as the mutability of the path is dependent on a super
610 // path, and check that the super path was not lent out as
611 // mutable or immutable (a const loan is ok).
612 //
613 // Mutability of a path can be dependent on the super path
614 // in two ways. First, it might be inherited mutability.
615 // Second, the pointee of an `&mut` pointer can only be
616 // mutated if it is found in an unaliased location, so we
617 // have to check that the owner location is not borrowed.
618 //
619 // Note that we are *not* checking for any and all
620 // restrictions. We are only interested in the pointers
621 // that the user created, whereas we add restrictions for
622 // all kinds of paths that are not directly aliased. If we checked
623 // for all restrictions, and not just loans, then the following
624 // valid program would be considered illegal:
625 //
626 // let mut v = ~[1, 2, 3];
627 // let p = &const v[0];
628 // v[1] = 5; // ok
629 //
630 // Here the restriction that `v` not be mutated would be misapplied
631 // to block the subpath `v[1]`.
632 let full_loan_path = loan_path.clone();
633 let mut loan_path = loan_path;
634 loop {
635 loan_path = match *loan_path {
636 // Peel back one layer if, for `loan_path` to be
637 // mutable, `lp_base` must be mutable. This occurs
638 // with inherited mutability and with `&mut`
639 // pointers.
640 LpExtend(ref lp_base, mc::McInherited, _) |
641 LpExtend(ref lp_base, _, LpDeref(mc::BorrowedPtr(ty::MutBorrow, _))) => {
642 lp_base.clone()
643 }
644
645 // Otherwise stop iterating
646 LpExtend(_, mc::McDeclared, _) |
647 LpExtend(_, mc::McImmutable, _) |
648 LpVar(_) => {
649 return true;
650 }
651 };
652
653 // Check for a non-const loan of `loan_path`
654 let cont = this.each_in_scope_loan(expr.id, |loan| {
655 if loan.loan_path == loan_path {
656 this.report_illegal_mutation(expr, &*full_loan_path, loan);
657 false
658 } else {
659 true
660 }
661 });
662
663 if !cont { return false }
664 }
665 }
666 }
667
668 pub fn report_illegal_mutation(&self,
669 expr: &ast::Expr,
670 loan_path: &LoanPath,
671 loan: &Loan) {
672 self.bccx.span_err(
673 expr.span,
674 format!("cannot assign to `{}` because it is borrowed",
675 self.bccx.loan_path_to_str(loan_path)));
676 self.bccx.span_note(
677 loan.span,
678 format!("borrow of `{}` occurs here",
679 self.bccx.loan_path_to_str(loan_path)));
680 }
681
682 fn check_move_out_from_expr(&self, expr: &ast::Expr) {
683 match expr.node {
684 ast::ExprFnBlock(..) | ast::ExprProc(..) => {
685 // Moves due to captures are checked in
686 // check_captured_variables() because it allows
687 // us to give a more precise error message with
688 // a more precise span.
689 }
690 _ => {
691 self.check_move_out_from_id(expr.id, expr.span)
692 }
693 }
694 }
695
696 fn check_move_out_from_id(&self, id: ast::NodeId, span: Span) {
697 self.move_data.each_path_moved_by(id, |_, move_path| {
698 match self.analyze_move_out_from(id, move_path) {
699 MoveOk => {}
700 MoveWhileBorrowed(loan_path, loan_span) => {
701 self.bccx.span_err(
702 span,
703 format!("cannot move out of `{}` \
704 because it is borrowed",
705 self.bccx.loan_path_to_str(move_path)));
706 self.bccx.span_note(
707 loan_span,
708 format!("borrow of `{}` occurs here",
709 self.bccx.loan_path_to_str(&*loan_path)));
710 }
711 }
712 true
713 });
714 }
715
716 fn check_captured_variables(&self,
717 closure_id: ast::NodeId,
718 span: Span) {
719 let freevar_mode = freevars::get_capture_mode(self.tcx(), closure_id);
720 freevars::with_freevars(self.tcx(), closure_id, |freevars| {
721 for freevar in freevars.iter() {
722 let var_id = ast_util::def_id_of_def(freevar.def).node;
723 let var_path = Rc::new(LpVar(var_id));
724 self.check_if_path_is_moved(closure_id, span,
725 MovedInCapture, &var_path);
726 match freevar_mode {
727 freevars::CaptureByRef => { }
728 freevars::CaptureByValue => {
729 check_by_move_capture(self, closure_id, freevar, &*var_path);
730 }
731 }
732 }
733 });
734 return;
735
736 fn check_by_move_capture(this: &CheckLoanCtxt,
737 closure_id: ast::NodeId,
738 freevar: &freevars::freevar_entry,
739 move_path: &LoanPath) {
740 let move_err = this.analyze_move_out_from(closure_id, move_path);
741 match move_err {
742 MoveOk => {}
743 MoveWhileBorrowed(loan_path, loan_span) => {
744 this.bccx.span_err(
745 freevar.span,
746 format!("cannot move `{}` into closure \
747 because it is borrowed",
748 this.bccx.loan_path_to_str(move_path)));
749 this.bccx.span_note(
750 loan_span,
751 format!("borrow of `{}` occurs here",
752 this.bccx.loan_path_to_str(&*loan_path)));
753 }
754 }
755 }
756 }
757
758 pub fn analyze_move_out_from(&self,
759 expr_id: ast::NodeId,
760 move_path: &LoanPath)
761 -> MoveError {
762 debug!("analyze_move_out_from(expr_id={:?}, move_path={})",
763 self.tcx().map.node_to_str(expr_id),
764 move_path.repr(self.tcx()));
765
766 // We must check every element of a move path. See
767 // `borrowck-move-subcomponent.rs` for a test case.
768
769 // check for a conflicting loan:
770 let mut ret = MoveOk;
771 self.each_in_scope_restriction(expr_id, move_path, |loan, _| {
772 // Any restriction prevents moves.
773 ret = MoveWhileBorrowed(loan.loan_path.clone(), loan.span);
774 false
775 });
776
777 if ret != MoveOk {
778 return ret
779 }
780
781 match *move_path {
782 LpVar(_) => MoveOk,
783 LpExtend(ref subpath, _, _) => {
784 self.analyze_move_out_from(expr_id, &**subpath)
785 }
786 }
787 }
788
789 pub fn check_call(&self,
790 _expr: &ast::Expr,
791 _callee: Option<@ast::Expr>,
792 _callee_span: Span,
793 _args: &[@ast::Expr]) {
794 // NB: This call to check for conflicting loans is not truly
795 // necessary, because the callee_id never issues new loans.
796 // However, I added it for consistency and lest the system
797 // should change in the future.
798 //
799 // FIXME(#6268) nested method calls
800 // self.check_for_conflicting_loans(callee_id);
801 }
802 }
803
804 fn check_loans_in_local<'a>(this: &mut CheckLoanCtxt<'a>,
805 local: &ast::Local) {
806 visit::walk_local(this, local, ());
807 }
808
809 fn check_loans_in_expr<'a>(this: &mut CheckLoanCtxt<'a>,
810 expr: &ast::Expr) {
811 visit::walk_expr(this, expr, ());
812
813 debug!("check_loans_in_expr(expr={})",
814 expr.repr(this.tcx()));
815
816 this.check_for_conflicting_loans(expr.id);
817 this.check_move_out_from_expr(expr);
818
819 let method_map = this.bccx.tcx.method_map.borrow();
820 match expr.node {
821 ast::ExprPath(..) => {
822 if !this.move_data.is_assignee(expr.id) {
823 let cmt = this.bccx.cat_expr_unadjusted(expr);
824 debug!("path cmt={}", cmt.repr(this.tcx()));
825 for lp in opt_loan_path(&cmt).iter() {
826 this.check_if_path_is_moved(expr.id, expr.span, MovedInUse, lp);
827 }
828 }
829 }
830 ast::ExprFnBlock(..) | ast::ExprProc(..) => {
831 this.check_captured_variables(expr.id, expr.span)
832 }
833 ast::ExprAssign(dest, _) |
834 ast::ExprAssignOp(_, dest, _) => {
835 this.check_assignment(dest);
836 }
837 ast::ExprCall(f, ref args) => {
838 this.check_call(expr, Some(f), f.span, args.as_slice());
839 }
840 ast::ExprMethodCall(_, _, ref args) => {
841 this.check_call(expr, None, expr.span, args.as_slice());
842 }
843 ast::ExprIndex(_, rval) | ast::ExprBinary(_, _, rval)
844 if method_map.contains_key(&MethodCall::expr(expr.id)) => {
845 this.check_call(expr, None, expr.span, [rval]);
846 }
847 ast::ExprUnary(_, _) | ast::ExprIndex(_, _)
848 if method_map.contains_key(&MethodCall::expr(expr.id)) => {
849 this.check_call(expr, None, expr.span, []);
850 }
851 ast::ExprInlineAsm(ref ia) => {
852 for &(_, out) in ia.outputs.iter() {
853 this.check_assignment(out);
854 }
855 }
856 _ => {}
857 }
858 }
859
860 fn check_loans_in_pat<'a>(this: &mut CheckLoanCtxt<'a>,
861 pat: &ast::Pat)
862 {
863 this.check_for_conflicting_loans(pat.id);
864 this.check_move_out_from_id(pat.id, pat.span);
865 visit::walk_pat(this, pat, ());
866 }
867
868 fn check_loans_in_block<'a>(this: &mut CheckLoanCtxt<'a>,
869 blk: &ast::Block)
870 {
871 visit::walk_block(this, blk, ());
872 this.check_for_conflicting_loans(blk.id);
873 }