/*! * # Borrow check * * This pass is in job of enforcing *memory safety* and *purity*. As * memory safety is by far the more complex topic, I'll focus on that in * this description, but purity will be covered later on. In the context * of Rust, memory safety means three basic things: * * - no writes to immutable memory; * - all pointers point to non-freed memory; * - all pointers point to memory of the same type as the pointer. * * The last point might seem confusing: after all, for the most part, * this condition is guaranteed by the type check. However, there are * two cases where the type check effectively delegates to borrow check. * * The first case has to do with enums. If there is a pointer to the * interior of an enum, and the enum is in a mutable location (such as a * local variable or field declared to be mutable), it is possible that * the user will overwrite the enum with a new value of a different * variant, and thus effectively change the type of the memory that the * pointer is pointing at. * * The second case has to do with mutability. Basically, the type * checker has only a limited understanding of mutability. It will allow * (for example) the user to get an immutable pointer with the address of * a mutable local variable. It will also allow a `@mut T` or `~mut T` * pointer to be borrowed as a `&r.T` pointer. These seeming oversights * are in fact intentional; they allow the user to temporarily treat a * mutable value as immutable. It is up to the borrow check to guarantee * that the value in question is not in fact mutated during the lifetime * `r` of the reference. * * # Summary of the safety check * * In order to enforce mutability, the borrow check has three tricks up * its sleeve. * * First, data which is uniquely tied to the current stack frame (that'll * be defined shortly) is tracked very precisely. This means that, for * example, if an immutable pointer to a mutable local variable is * created, the borrowck will simply check for assignments to that * particular local variable: no other memory is affected. * * Second, if the data is not uniquely tied to the stack frame, it may * still be possible to ensure its validity by rooting garbage collected * pointers at runtime. For example, if there is a mutable local * variable `x` of type `@T`, and its contents are borrowed with an * expression like `&*x`, then the value of `x` will be rooted (today, * that means its ref count will be temporary increased) for the lifetime * of the reference that is created. This means that the pointer remains * valid even if `x` is reassigned. * * Finally, if neither of these two solutions are applicable, then we * require that all operations within the scope of the reference be * *pure*. A pure operation is effectively one that does not write to * any aliasable memory. This means that it is still possible to write * to local variables or other data that is uniquely tied to the stack * frame (there's that term again; formal definition still pending) but * not to data reached via a `&T` or `@T` pointer. Such writes could * possibly have the side-effect of causing the data which must remain * valid to be overwritten. * * # Possible future directions * * There are numerous ways that the `borrowck` could be strengthened, but * these are the two most likely: * * - flow-sensitivity: we do not currently consider flow at all but only * block-scoping. This means that innocent code like the following is * rejected: * * let mut x: int; * ... * x = 5; * let y: &int = &x; // immutable ptr created * ... * * The reason is that the scope of the pointer `y` is the entire * enclosing block, and the assignment `x = 5` occurs within that * block. The analysis is not smart enough to see that `x = 5` always * happens before the immutable pointer is created. This is relatively * easy to fix and will surely be fixed at some point. * * - finer-grained purity checks: currently, our fallback for * guaranteeing random references into mutable, aliasable memory is to * require *total purity*. This is rather strong. We could use local * type-based alias analysis to distinguish writes that could not * possibly invalid the references which must be guaranteed. This * would only work within the function boundaries; function calls would * still require total purity. This seems less likely to be * implemented in the short term as it would make the code * significantly more complex; there is currently no code to analyze * the types and determine the possible impacts of a write. * * # Terminology * * A **loan** is . * * # How the code works * * The borrow check code is divided into several major modules, each of * which is documented in its own file. * * The `gather_loans` and `check_loans` are the two major passes of the * analysis. The `gather_loans` pass runs over the IR once to determine * what memory must remain valid and for how long. Its name is a bit of * a misnomer; it does in fact gather up the set of loans which are * granted, but it also determines when @T pointers must be rooted and * for which scopes purity must be required. * * The `check_loans` pass walks the IR and examines the loans and purity * requirements computed in `gather_loans`. It checks to ensure that (a) * the conditions of all loans are honored; (b) no contradictory loans * were granted (for example, loaning out the same memory as mutable and * immutable simultaneously); and (c) any purity requirements are * honored. * * The remaining modules are helper modules used by `gather_loans` and * `check_loans`: * * - `categorization` has the job of analyzing an expression to determine * what kind of memory is used in evaluating it (for example, where * dereferences occur and what kind of pointer is dereferenced; whether * the memory is mutable; etc) * - `loan` determines when data uniquely tied to the stack frame can be * loaned out. * - `preserve` determines what actions (if any) must be taken to preserve * aliasable data. This is the code which decides when to root * an @T pointer or to require purity. * * # Maps that are created * * Borrowck results in two maps. * * - `root_map`: identifies those expressions or patterns whose result * needs to be rooted. Conceptually the root_map maps from an * expression or pattern node to a `node_id` identifying the scope for * which the expression must be rooted (this `node_id` should identify * a block or call). The actual key to the map is not an expression id, * however, but a `root_map_key`, which combines an expression id with a * deref count and is used to cope with auto-deref. * * - `mutbl_map`: identifies those local variables which are modified or * moved. This is used by trans to guarantee that such variables are * given a memory location and not used as immediates. */ import syntax::ast; import syntax::ast::{mutability, m_mutbl, m_imm, m_const}; import syntax::visit; import syntax::ast_util; import syntax::ast_map; import syntax::codemap::span; import util::ppaux::{ty_to_str, region_to_str}; import std::map::{int_hash, hashmap, set}; import std::list; import std::list::{list, cons, nil}; import result::{result, ok, err, extensions}; import syntax::print::pprust; import util::common::indenter; import ast_util::op_expr_callee_id; import ty::to_str; import driver::session::session; import dvec::{dvec, extensions}; export check_crate, root_map, mutbl_map; fn check_crate(tcx: ty::ctxt, method_map: typeck::method_map, last_use_map: liveness::last_use_map, crate: @ast::crate) -> (root_map, mutbl_map) { let bccx = @{tcx: tcx, method_map: method_map, last_use_map: last_use_map, binding_map: int_hash(), root_map: root_map(), mutbl_map: int_hash()}; let req_maps = gather_loans::gather_loans(bccx, crate); check_loans::check_loans(bccx, req_maps, crate); ret (bccx.root_map, bccx.mutbl_map); } // ---------------------------------------------------------------------- // Type definitions type borrowck_ctxt = @{tcx: ty::ctxt, method_map: typeck::method_map, last_use_map: liveness::last_use_map, binding_map: binding_map, root_map: root_map, mutbl_map: mutbl_map}; // a map mapping id's of expressions of gc'd type (@T, @[], etc) where // the box needs to be kept live to the id of the scope for which they // must stay live. type root_map = hashmap; // the keys to the root map combine the `id` of the expression with // the number of types that it is autodereferenced. So, for example, // if you have an expression `x.f` and x has type ~@T, we could add an // entry {id:x, derefs:0} to refer to `x` itself, `{id:x, derefs:1}` // to refer to the deref of the unique pointer, and so on. type root_map_key = {id: ast::node_id, derefs: uint}; // set of ids of local vars / formal arguments that are modified / moved. // this is used in trans for optimization purposes. type mutbl_map = std::map::hashmap; // maps from each binding's id to the mutability of the location it // points at. See gather_loan.rs for more detail (search for binding_map) type binding_map = std::map::hashmap; // Errors that can occur"] enum bckerr_code { err_mut_uniq, err_mut_variant, err_preserve_gc, err_mutbl(ast::mutability, ast::mutability) } // Combination of an error code and the categorization of the expression // that caused it type bckerr = {cmt: cmt, code: bckerr_code}; // shorthand for something that fails with `bckerr` or succeeds with `T` type bckres = result; enum categorization { cat_rvalue, // result of eval'ing some misc expr cat_special(special_kind), // cat_local(ast::node_id), // local variable cat_binding(ast::node_id), // pattern binding cat_arg(ast::node_id), // formal argument cat_stack_upvar(cmt), // upvar in stack closure cat_deref(cmt, uint, ptr_kind), // deref of a ptr cat_comp(cmt, comp_kind), // adjust to locate an internal component cat_discr(cmt, ast::node_id), // alt discriminant (see preserve()) } // different kinds of pointers: 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, // elt in a tuple comp_variant(ast::def_id), // internals to a variant of given enum comp_field(ast::ident, // name of field ast::mutability), // declared mutability of field comp_index(ty::t, // type of vec/str/etc being deref'd ast::mutability) // mutability of vec content } // We pun on *T to mean both actual deref of a ptr as well // as accessing of components: enum deref_kind {deref_ptr(ptr_kind), deref_comp(comp_kind)} // different kinds of expressions we might evaluate enum special_kind { sk_method, sk_static_item, sk_self, sk_heap_upvar } // a complete categorization of a value indicating where it originated // and how it is located, as well as the mutability of the memory in // which the value is stored. type cmt = @{id: ast::node_id, // id of expr/pat producing this value span: span, // span of same expr/pat cat: categorization, // categorization of expr lp: option<@loan_path>, // loan path for expr, if any mutbl: ast::mutability, // mutability of expr as lvalue ty: ty::t}; // type of the expr // a loan path is like a category, but it exists only when the data is // interior to the stack frame. loan paths are used as the key to a // map indicating what is borrowed at any point in time. enum loan_path { lp_local(ast::node_id), lp_arg(ast::node_id), lp_deref(@loan_path, ptr_kind), lp_comp(@loan_path, comp_kind) } // 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` type req_maps = { req_loan_map: hashmap>>, pure_map: hashmap }; fn save_and_restore(&save_and_restore_t: T, f: fn() -> U) -> U { let old_save_and_restore_t = save_and_restore_t; let u <- f(); save_and_restore_t = old_save_and_restore_t; ret u; } /// Creates and returns a new root_map fn root_map() -> root_map { ret hashmap(root_map_key_hash, root_map_key_eq); fn root_map_key_eq(k1: root_map_key, k2: root_map_key) -> bool { k1.id == k2.id && k1.derefs == k2.derefs } fn root_map_key_hash(k: root_map_key) -> uint { (k.id << 4) as uint | k.derefs } } // ___________________________________________________________________________ // Misc iface ast_node { fn id() -> ast::node_id; fn span() -> span; } impl of ast_node for @ast::expr { fn id() -> ast::node_id { self.id } fn span() -> span { self.span } } impl of ast_node for @ast::pat { fn id() -> ast::node_id { self.id } fn span() -> span { self.span } } impl methods for ty::ctxt { fn ty(node: N) -> ty::t { ty::node_id_to_type(self, node.id()) } } impl error_methods for borrowck_ctxt { fn report_if_err(bres: bckres<()>) { alt bres { ok(()) { } err(e) { self.report(e); } } } fn report(err: bckerr) { self.span_err( err.cmt.span, #fmt["illegal borrow: %s", self.bckerr_code_to_str(err.code)]); } fn span_err(s: span, m: str) { self.tcx.sess.span_err(s, m); } fn span_note(s: span, m: str) { self.tcx.sess.span_note(s, m); } fn add_to_mutbl_map(cmt: cmt) { alt cmt.cat { cat_local(id) | cat_arg(id) { self.mutbl_map.insert(id, ()); } cat_stack_upvar(cmt) { self.add_to_mutbl_map(cmt); } _ {} } } } impl to_str_methods for borrowck_ctxt { fn cat_to_repr(cat: categorization) -> str { alt cat { cat_special(sk_method) { "method" } cat_special(sk_static_item) { "static_item" } cat_special(sk_self) { "self" } cat_special(sk_heap_upvar) { "heap-upvar" } cat_stack_upvar(_) { "stack-upvar" } cat_rvalue { "rvalue" } cat_local(node_id) { #fmt["local(%d)", node_id] } cat_binding(node_id) { #fmt["binding(%d)", node_id] } cat_arg(node_id) { #fmt["arg(%d)", node_id] } cat_deref(cmt, derefs, ptr) { #fmt["%s->(%s, %u)", self.cat_to_repr(cmt.cat), self.ptr_sigil(ptr), derefs] } cat_comp(cmt, comp) { #fmt["%s.%s", self.cat_to_repr(cmt.cat), self.comp_to_repr(comp)] } cat_discr(cmt, _) { self.cat_to_repr(cmt.cat) } } } fn mut_to_str(mutbl: ast::mutability) -> str { alt mutbl { m_mutbl { "mutable" } m_const { "const" } m_imm { "immutable" } } } fn ptr_sigil(ptr: ptr_kind) -> str { alt ptr { uniq_ptr { "~" } gc_ptr { "@" } region_ptr { "&" } unsafe_ptr { "*" } } } fn comp_to_repr(comp: comp_kind) -> str { alt comp { comp_field(fld, _) { *fld } comp_index(*) { "[]" } comp_tuple { "()" } comp_variant(_) { "" } } } fn lp_to_str(lp: @loan_path) -> str { alt *lp { lp_local(node_id) { #fmt["local(%d)", node_id] } lp_arg(node_id) { #fmt["arg(%d)", node_id] } lp_deref(lp, ptr) { #fmt["%s->(%s)", self.lp_to_str(lp), self.ptr_sigil(ptr)] } lp_comp(lp, comp) { #fmt["%s.%s", self.lp_to_str(lp), self.comp_to_repr(comp)] } } } fn cmt_to_repr(cmt: cmt) -> str { #fmt["{%s id:%d m:%s lp:%s ty:%s}", self.cat_to_repr(cmt.cat), cmt.id, self.mut_to_str(cmt.mutbl), cmt.lp.map_default("none", |p| self.lp_to_str(p) ), 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 { cat_special(sk_method) { "method" } cat_special(sk_static_item) { "static item" } cat_special(sk_self) { "self reference" } cat_special(sk_heap_upvar) { "captured outer variable in a heap closure" } cat_rvalue { "non-lvalue" } cat_local(_) { mut_str + " local variable" } cat_binding(_) { "pattern binding" } cat_arg(_) { "argument" } cat_deref(_, _, pk) { #fmt["dereference of %s %s pointer", mut_str, self.pk_to_sigil(pk)] } cat_stack_upvar(_) { "captured outer " + mut_str + " variable in a stack closure" } cat_comp(_, comp_field(*)) { mut_str + " field" } cat_comp(_, comp_tuple) { "tuple content" } cat_comp(_, comp_variant(_)) { "enum content" } cat_comp(_, comp_index(t, _)) { alt ty::get(t).struct { ty::ty_vec(*) | ty::ty_evec(*) { mut_str + " vec content" } ty::ty_str | ty::ty_estr(*) { mut_str + " str content" } _ { mut_str + " indexed content" } } } cat_discr(cmt, _) { self.cmt_to_str(cmt) } } } fn bckerr_code_to_str(code: bckerr_code) -> str { alt code { err_mutbl(req, act) { #fmt["creating %s alias to aliasable, %s memory", self.mut_to_str(req), self.mut_to_str(act)] } err_mut_uniq { "unique value in aliasable, mutable location" } 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" } } } } // 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_variant(_) {m_imm} comp_field(_, m) | comp_index(_, m) {m} } }