From a0949260962af9b6662ac550a9842adef1a6edaf Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 25 Sep 2019 22:41:08 +0200 Subject: [PATCH 01/10] rm -rf librustc_ast_borrowck --- src/librustc_ast_borrowck/Cargo.toml | 20 - src/librustc_ast_borrowck/borrowck/README.md | 1167 ----------------- .../borrowck/check_loans.rs | 680 ---------- .../borrowck/gather_loans/gather_moves.rs | 135 -- .../borrowck/gather_loans/lifetime.rs | 113 -- .../borrowck/gather_loans/mod.rs | 433 ------ .../borrowck/gather_loans/restrictions.rs | 179 --- src/librustc_ast_borrowck/borrowck/mod.rs | 621 --------- .../borrowck/move_data.rs | 730 ----------- src/librustc_ast_borrowck/cfg/construct.rs | 545 -------- src/librustc_ast_borrowck/cfg/graphviz.rs | 119 -- src/librustc_ast_borrowck/cfg/mod.rs | 55 - src/librustc_ast_borrowck/dataflow.rs | 672 ---------- src/librustc_ast_borrowck/graphviz.rs | 145 -- src/librustc_ast_borrowck/lib.rs | 23 - 15 files changed, 5637 deletions(-) delete mode 100644 src/librustc_ast_borrowck/Cargo.toml delete mode 100644 src/librustc_ast_borrowck/borrowck/README.md delete mode 100644 src/librustc_ast_borrowck/borrowck/check_loans.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/gather_loans/gather_moves.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/gather_loans/lifetime.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/gather_loans/mod.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/gather_loans/restrictions.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/mod.rs delete mode 100644 src/librustc_ast_borrowck/borrowck/move_data.rs delete mode 100644 src/librustc_ast_borrowck/cfg/construct.rs delete mode 100644 src/librustc_ast_borrowck/cfg/graphviz.rs delete mode 100644 src/librustc_ast_borrowck/cfg/mod.rs delete mode 100644 src/librustc_ast_borrowck/dataflow.rs delete mode 100644 src/librustc_ast_borrowck/graphviz.rs delete mode 100644 src/librustc_ast_borrowck/lib.rs diff --git a/src/librustc_ast_borrowck/Cargo.toml b/src/librustc_ast_borrowck/Cargo.toml deleted file mode 100644 index 40c4c1fc3fee6..0000000000000 --- a/src/librustc_ast_borrowck/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -authors = ["The Rust Project Developers"] -name = "rustc_ast_borrowck" -version = "0.0.0" -edition = "2018" - -[lib] -name = "rustc_ast_borrowck" -path = "lib.rs" -test = false -doctest = false - -[dependencies] -log = "0.4" -syntax_pos = { path = "../libsyntax_pos" } -# for "clarity", rename the graphviz crate to dot; graphviz within `borrowck` -# refers to the borrowck-specific graphviz adapter traits. -dot = { path = "../libgraphviz", package = "graphviz" } -rustc = { path = "../librustc" } -rustc_data_structures = { path = "../librustc_data_structures" } diff --git a/src/librustc_ast_borrowck/borrowck/README.md b/src/librustc_ast_borrowck/borrowck/README.md deleted file mode 100644 index 3f2175921d48c..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/README.md +++ /dev/null @@ -1,1167 +0,0 @@ -% The Borrow Checker - -> WARNING: This README is more or less obsolete, and will be removed -> soon! The new system is described in the [rustc guide]. - -[rustc guide]: https://rust-lang.github.io/rustc-guide/borrow_check.html - -This pass has the job of enforcing memory safety. This is a subtle -topic. This docs aim to explain both the practice and the theory -behind the borrow checker. They start with a high-level overview of -how it works, and then proceed to dive into the theoretical -background. Finally, they go into detail on some of the more subtle -aspects. - -# Table of contents - -These docs are long. Search for the section you are interested in. - -- Overview -- Formal model -- Borrowing and loans -- Moves and initialization -- Drop flags and structural fragments -- Future work - -# Overview - -The borrow checker checks one function at a time. It operates in two -passes. The first pass, called `gather_loans`, walks over the function -and identifies all of the places where borrows (e.g., `&` expressions -and `ref` bindings) and moves (copies or captures of a linear value) -occur. It also tracks initialization sites. For each borrow and move, -it checks various basic safety conditions at this time (for example, -that the lifetime of the borrow doesn't exceed the lifetime of the -value being borrowed, or that there is no move out of an `&T` -referent). - -It then uses the dataflow module to propagate which of those borrows -may be in scope at each point in the procedure. A loan is considered -to come into scope at the expression that caused it and to go out of -scope when the lifetime of the resulting reference expires. - -Once the in-scope loans are known for each point in the program, the -borrow checker walks the IR again in a second pass called -`check_loans`. This pass examines each statement and makes sure that -it is safe with respect to the in-scope loans. - -# Formal model - -Throughout the docs we'll consider a simple subset of Rust in which -you can only borrow from places, defined like so: - -```text -P = x | P.f | *P -``` - -Here `x` represents some variable, `P.f` is a field reference, -and `*P` is a pointer dereference. There is no auto-deref or other -niceties. This means that if you have a type like: - -```rust -struct S { f: i32 } -``` - -and a variable `a: Box`, then the rust expression `a.f` would correspond -to an `P` of `(*a).f`. - -Here is the formal grammar for the types we'll consider: - -```text -TY = i32 | bool | S<'LT...> | Box | & 'LT MQ TY -MQ = mut | imm -``` - -Most of these types should be pretty self explanatory. Here `S` is a -struct name and we assume structs are declared like so: - -```text -SD = struct S<'LT...> { (f: TY)... } -``` - -# Borrowing and loans - -## An intuitive explanation - -### Issuing loans - -Now, imagine we had a program like this: - -```rust -struct Foo { f: i32, g: i32 } -... -'a: { - let mut x: Box = ...; - let y = &mut (*x).f; - x = ...; -} -``` - -This is of course dangerous because mutating `x` will free the old -value and hence invalidate `y`. The borrow checker aims to prevent -this sort of thing. - -#### Loans and restrictions - -The way the borrow checker works is that it analyzes each borrow -expression (in our simple model, that's stuff like `&P`, though in -real life there are a few other cases to consider). For each borrow -expression, it computes a `Loan`, which is a data structure that -records (1) the value being borrowed, (2) the mutability and scope of -the borrow, and (3) a set of restrictions. In the code, `Loan` is a -struct defined in `middle::borrowck`. Formally, we define `LOAN` as -follows: - -```text -LOAN = (P, LT, MQ, RESTRICTION*) -RESTRICTION = (P, ACTION*) -ACTION = MUTATE | CLAIM | FREEZE -``` - -Here the `LOAN` tuple defines the place `P` being borrowed; the -lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a -list of restrictions. The restrictions indicate actions which, if -taken, could invalidate the loan and lead to type safety violations. - -Each `RESTRICTION` is a pair of a restrictive place `P` (which will -either be the path that was borrowed or some prefix of the path that -was borrowed) and a set of restricted actions. There are three kinds -of actions that may be restricted for the path `P`: - -- `MUTATE` means that `P` cannot be assigned to; -- `CLAIM` means that the `P` cannot be borrowed mutably; -- `FREEZE` means that the `P` cannot be borrowed immutably; - -Finally, it is never possible to move from a place that appears in a -restriction. This implies that the "empty restriction" `(P, [])`, -which contains an empty set of actions, still has a purpose---it -prevents moves from `P`. I chose not to make `MOVE` a fourth kind of -action because that would imply that sometimes moves are permitted -from restricted values, which is not the case. - -#### Example - -To give you a better feeling for what kind of restrictions derived -from a loan, let's look at the loan `L` that would be issued as a -result of the borrow `&mut (*x).f` in the example above: - -```text -L = ((*x).f, 'a, mut, RS) where - RS = [((*x).f, [MUTATE, CLAIM, FREEZE]), - (*x, [MUTATE, CLAIM, FREEZE]), - (x, [MUTATE, CLAIM, FREEZE])] -``` - -The loan states that the expression `(*x).f` has been loaned as -mutable for the lifetime `'a`. Because the loan is mutable, that means -that the value `(*x).f` may be mutated via the newly created reference -(and *only* via that pointer). This is reflected in the -restrictions `RS` that accompany the loan. - -The first restriction `((*x).f, [MUTATE, CLAIM, FREEZE])` states that -the lender may not mutate, freeze, nor alias `(*x).f`. Mutation is -illegal because `(*x).f` is only supposed to be mutated via the new -reference, not by mutating the original path `(*x).f`. Freezing is -illegal because the path now has an `&mut` alias; so even if we the -lender were to consider `(*x).f` to be immutable, it might be mutated -via this alias. They will be enforced for the lifetime `'a` of the -loan. After the loan expires, the restrictions no longer apply. - -The second restriction on `*x` is interesting because it does not -apply to the path that was lent (`(*x).f`) but rather to a prefix of -the borrowed path. This is due to the rules of inherited mutability: -if the user were to assign to (or freeze) `*x`, they would indirectly -overwrite (or freeze) `(*x).f`, and thus invalidate the reference -that was created. In general it holds that when a path is -lent, restrictions are issued for all the owning prefixes of that -path. In this case, the path `*x` owns the path `(*x).f` and, -because `x` has ownership, the path `x` owns the path `*x`. -Therefore, borrowing `(*x).f` yields restrictions on both -`*x` and `x`. - -### Checking for illegal assignments, moves, and reborrows - -Once we have computed the loans introduced by each borrow, the borrow -checker uses a data flow propagation to compute the full set of loans -in scope at each expression and then uses that set to decide whether -that expression is legal. Remember that the scope of loan is defined -by its lifetime LT. We sometimes say that a loan which is in-scope at -a particular point is an "outstanding loan", and the set of -restrictions included in those loans as the "outstanding -restrictions". - -The kinds of expressions which in-scope loans can render illegal are: -- *assignments* (`lv = v`): illegal if there is an in-scope restriction - against mutating `lv`; -- *moves*: illegal if there is any in-scope restriction on `lv` at all; -- *mutable borrows* (`&mut lv`): illegal there is an in-scope restriction - against claiming `lv`; -- *immutable borrows* (`&lv`): illegal there is an in-scope restriction - against freezing `lv`. - -## Formal rules - -Now that we hopefully have some kind of intuitive feeling for how the -borrow checker works, let's look a bit more closely now at the precise -conditions that it uses. - -I will present the rules in a modified form of standard inference -rules, which looks as follows: - -```text -PREDICATE(X, Y, Z) // Rule-Name - Condition 1 - Condition 2 - Condition 3 -``` - -The initial line states the predicate that is to be satisfied. The -indented lines indicate the conditions that must be met for the -predicate to be satisfied. The right-justified comment states the name -of this rule: there are comments in the borrowck source referencing -these names, so that you can cross reference to find the actual code -that corresponds to the formal rule. - -### Invariants - -I want to collect, at a high-level, the invariants the borrow checker -maintains. I will give them names and refer to them throughout the -text. Together these invariants are crucial for the overall soundness -of the system. - -**Mutability requires uniqueness.** To mutate a path - -**Unique mutability.** There is only one *usable* mutable path to any -given memory at any given time. This implies that when claiming memory -with an expression like `p = &mut x`, the compiler must guarantee that -the borrowed value `x` can no longer be mutated so long as `p` is -live. (This is done via restrictions, read on.) - -**.** - - -### The `gather_loans` pass - -We start with the `gather_loans` pass, which walks the AST looking for -borrows. For each borrow, there are three bits of information: the -place `P` being borrowed and the mutability `MQ` and lifetime `LT` -of the resulting pointer. Given those, `gather_loans` applies four -validity tests: - -1. `MUTABILITY(P, MQ)`: The mutability of the reference is -compatible with the mutability of `P` (i.e., not borrowing immutable -data as mutable). - -2. `ALIASABLE(P, MQ)`: The aliasability of the reference is -compatible with the aliasability of `P`. The goal is to prevent -`&mut` borrows of aliasability data. - -3. `LIFETIME(P, LT, MQ)`: The lifetime of the borrow does not exceed -the lifetime of the value being borrowed. - -4. `RESTRICTIONS(P, LT, ACTIONS) = RS`: This pass checks and computes the -restrictions to maintain memory safety. These are the restrictions -that will go into the final loan. We'll discuss in more detail below. - -## Checking mutability - -Checking mutability is fairly straightforward. We just want to prevent -immutable data from being borrowed as mutable. Note that it is ok to borrow -mutable data as immutable, since that is simply a freeze. The judgement -`MUTABILITY(P, MQ)` means the mutability of `P` is compatible with a borrow -of mutability `MQ`. The Rust code corresponding to this predicate is the -function `check_mutability` in `middle::borrowck::gather_loans`. - -### Checking mutability of variables - -*Code pointer:* Function `check_mutability()` in `gather_loans/mod.rs`, -but also the code in `mem_categorization`. - -Let's begin with the rules for variables, which state that if a -variable is declared as mutable, it may be borrowed any which way, but -otherwise the variable must be borrowed as immutable: - -```text -MUTABILITY(X, MQ) // M-Var-Mut - DECL(X) = mut - -MUTABILITY(X, imm) // M-Var-Imm - DECL(X) = imm -``` - -### Checking mutability of owned content - -Fields and boxes inherit their mutability from -their base expressions, so both of their rules basically -delegate the check to the base expression `P`: - -```text -MUTABILITY(P.f, MQ) // M-Field - MUTABILITY(P, MQ) - -MUTABILITY(*P, MQ) // M-Deref-Unique - TYPE(P) = Box - MUTABILITY(P, MQ) -``` - -### Checking mutability of immutable pointer types - -Immutable pointer types like `&T` can only -be borrowed if MQ is immutable: - -```text -MUTABILITY(*P, imm) // M-Deref-Borrowed-Imm - TYPE(P) = &Ty -``` - -### Checking mutability of mutable pointer types - -`&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut: - -```text -MUTABILITY(*P, MQ) // M-Deref-Borrowed-Mut - TYPE(P) = &mut Ty -``` - -## Checking aliasability - -The goal of the aliasability check is to ensure that we never permit `&mut` -borrows of aliasable data. The judgement `ALIASABLE(P, MQ)` means the -aliasability of `P` is compatible with a borrow of mutability `MQ`. The Rust -code corresponding to this predicate is the function `check_aliasability()` in -`middle::borrowck::gather_loans`. - -### Checking aliasability of variables - -Local variables are never aliasable as they are accessible only within -the stack frame. - -```text - ALIASABLE(X, MQ) // M-Var-Mut -``` - -### Checking aliasable of owned content - -Owned content is aliasable if it is found in an aliasable location: - -```text -ALIASABLE(P.f, MQ) // M-Field - ALIASABLE(P, MQ) - -ALIASABLE(*P, MQ) // M-Deref-Unique - ALIASABLE(P, MQ) -``` - -### Checking aliasability of immutable pointer types - -Immutable pointer types like `&T` are aliasable, and hence can only be -borrowed immutably: - -```text -ALIASABLE(*P, imm) // M-Deref-Borrowed-Imm - TYPE(P) = &Ty -``` - -### Checking aliasability of mutable pointer types - -`&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut: - -```text -ALIASABLE(*P, MQ) // M-Deref-Borrowed-Mut - TYPE(P) = &mut Ty -``` - -## Checking lifetime - -These rules aim to ensure that no data is borrowed for a scope that exceeds -its lifetime. These two computations wind up being intimately related. -Formally, we define a predicate `LIFETIME(P, LT, MQ)`, which states that -"the place `P` can be safely borrowed for the lifetime `LT` with mutability -`MQ`". The Rust code corresponding to this predicate is the module -`middle::borrowck::gather_loans::lifetime`. - -### Checking lifetime of variables - -The rule for variables states that a variable can only be borrowed a -lifetime `LT` that is a subregion of the variable's scope: - -```text -LIFETIME(X, LT, MQ) // L-Local - LT <= block where X is declared -``` - -### Checking lifetime for owned content - -The lifetime of a field or box is the same as the lifetime -of its owner: - -```text -LIFETIME(P.f, LT, MQ) // L-Field - LIFETIME(P, LT, MQ) - -LIFETIME(*P, LT, MQ) // L-Deref-Send - TYPE(P) = Box - LIFETIME(P, LT, MQ) -``` - -### Checking lifetime for derefs of references - -References have a lifetime `LT'` associated with them. The -data they point at has been guaranteed to be valid for at least this -lifetime. Therefore, the borrow is valid so long as the lifetime `LT` -of the borrow is shorter than the lifetime `LT'` of the pointer -itself: - -```text -LIFETIME(*P, LT, MQ) // L-Deref-Borrowed - TYPE(P) = <' Ty OR <' mut Ty - LT <= LT' -``` - -## Computing the restrictions - -The final rules govern the computation of *restrictions*, meaning that -we compute the set of actions that will be illegal for the life of the -loan. The predicate is written `RESTRICTIONS(P, LT, ACTIONS) = -RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from -occurring on `P`, the restrictions `RESTRICTION*` must be respected -for the lifetime of the loan". - -Note that there is an initial set of restrictions: these restrictions -are computed based on the kind of borrow: - -```text -&mut P => RESTRICTIONS(P, LT, MUTATE|CLAIM|FREEZE) -&P => RESTRICTIONS(P, LT, MUTATE|CLAIM) -``` - -The reasoning here is that a mutable borrow must be the only writer, -therefore it prevents other writes (`MUTATE`), mutable borrows -(`CLAIM`), and immutable borrows (`FREEZE`). An immutable borrow -permits other immutable borrows but forbids writes and mutable borrows. - -### Restrictions for loans of a local variable - -The simplest case is a borrow of a local variable `X`: - -```text -RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable -``` - -In such cases we just record the actions that are not permitted. - -### Restrictions for loans of fields - -Restricting a field is the same as restricting the owner of that -field: - -```text -RESTRICTIONS(P.f, LT, ACTIONS) = RS, (P.f, ACTIONS) // R-Field - RESTRICTIONS(P, LT, ACTIONS) = RS -``` - -The reasoning here is as follows. If the field must not be mutated, -then you must not mutate the owner of the field either, since that -would indirectly modify the field. Similarly, if the field cannot be -frozen or aliased, we cannot allow the owner to be frozen or aliased, -since doing so indirectly freezes/aliases the field. This is the -origin of inherited mutability. - -### Restrictions for loans of owned referents - -Because the mutability of owned referents is inherited, restricting an -owned referent is similar to restricting a field, in that it implies -restrictions on the pointer. However, boxes have an important -twist: if the owner `P` is mutated, that causes the owned referent -`*P` to be freed! So whenever an owned referent `*P` is borrowed, we -must prevent the box `P` from being mutated, which means -that we always add `MUTATE` and `CLAIM` to the restriction set imposed -on `P`: - -```text -RESTRICTIONS(*P, LT, ACTIONS) = RS, (*P, ACTIONS) // R-Deref-Send-Pointer - TYPE(P) = Box - RESTRICTIONS(P, LT, ACTIONS|MUTATE|CLAIM) = RS -``` - -### Restrictions for loans of immutable borrowed referents - -Immutable borrowed referents are freely aliasable, meaning that -the compiler does not prevent you from copying the pointer. This -implies that issuing restrictions is useless. We might prevent the -user from acting on `*P` itself, but there could be another path -`*P1` that refers to the exact same memory, and we would not be -restricting that path. Therefore, the rule for `&Ty` pointers -always returns an empty set of restrictions, and it only permits -restricting `MUTATE` and `CLAIM` actions: - -```text -RESTRICTIONS(*P, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed - TYPE(P) = <' Ty - LT <= LT' // (1) - ACTIONS subset of [MUTATE, CLAIM] -``` - -The reason that we can restrict `MUTATE` and `CLAIM` actions even -without a restrictions list is that it is never legal to mutate nor to -borrow mutably the contents of a `&Ty` pointer. In other words, -those restrictions are already inherent in the type. - -Clause (1) in the rule for `&Ty` deserves mention. Here I -specify that the lifetime of the loan must be less than the lifetime -of the `&Ty` pointer. In simple cases, this clause is redundant, since -the `LIFETIME()` function will already enforce the required rule: - -```rust -fn foo(point: &'a Point) -> &'static i32 { - &point.x // Error -} -``` - -The above example fails to compile both because of clause (1) above -but also by the basic `LIFETIME()` check. However, in more advanced -examples involving multiple nested pointers, clause (1) is needed: - -```rust -fn foo(point: &'a &'b mut Point) -> &'b i32 { - &point.x // Error -} -``` - -The `LIFETIME` rule here would accept `'b` because, in fact, the -*memory is* guaranteed to remain valid (i.e., not be freed) for the -lifetime `'b`, since the `&mut` pointer is valid for `'b`. However, we -are returning an immutable reference, so we need the memory to be both -valid and immutable. Even though `point.x` is referenced by an `&mut` -pointer, it can still be considered immutable so long as that `&mut` -pointer is found in an aliased location. That means the memory is -guaranteed to be *immutable* for the lifetime of the `&` pointer, -which is only `'a`, not `'b`. Hence this example yields an error. - -As a final twist, consider the case of two nested *immutable* -pointers, rather than a mutable pointer within an immutable one: - -```rust -fn foo(point: &'a &'b Point) -> &'b i32 { - &point.x // OK -} -``` - -This function is legal. The reason for this is that the inner pointer -(`*point : &'b Point`) is enough to guarantee the memory is immutable -and valid for the lifetime `'b`. This is reflected in -`RESTRICTIONS()` by the fact that we do not recurse (i.e., we impose -no restrictions on `P`, which in this particular case is the pointer -`point : &'a &'b Point`). - -#### Why both `LIFETIME()` and `RESTRICTIONS()`? - -Given the previous text, it might seem that `LIFETIME` and -`RESTRICTIONS` should be folded together into one check, but there is -a reason that they are separated. They answer separate concerns. -The rules pertaining to `LIFETIME` exist to ensure that we don't -create a borrowed pointer that outlives the memory it points at. So -`LIFETIME` prevents a function like this: - -```rust -fn get_1<'a>() -> &'a i32 { - let x = 1; - &x -} -``` - -Here we would be returning a pointer into the stack. Clearly bad. - -However, the `RESTRICTIONS` rules are more concerned with how memory -is used. The example above doesn't generate an error according to -`RESTRICTIONS` because, for local variables, we don't require that the -loan lifetime be a subset of the local variable lifetime. The idea -here is that we *can* guarantee that `x` is not (e.g.) mutated for the -lifetime `'a`, even though `'a` exceeds the function body and thus -involves unknown code in the caller -- after all, `x` ceases to exist -after we return and hence the remaining code in `'a` cannot possibly -mutate it. This distinction is important for type checking functions -like this one: - -```rust -fn inc_and_get<'a>(p: &'a mut Point) -> &'a i32 { - p.x += 1; - &p.x -} -``` - -In this case, we take in a `&mut` and return a frozen borrowed pointer -with the same lifetime. So long as the lifetime of the returned value -doesn't exceed the lifetime of the `&mut` we receive as input, this is -fine, though it may seem surprising at first (it surprised me when I -first worked it through). After all, we're guaranteeing that `*p` -won't be mutated for the lifetime `'a`, even though we can't "see" the -entirety of the code during that lifetime, since some of it occurs in -our caller. But we *do* know that nobody can mutate `*p` except -through `p`. So if we don't mutate `*p` and we don't return `p`, then -we know that the right to mutate `*p` has been lost to our caller -- -in terms of capability, the caller passed in the ability to mutate -`*p`, and we never gave it back. (Note that we can't return `p` while -`*p` is borrowed since that would be a move of `p`, as `&mut` pointers -are affine.) - -### Restrictions for loans of mutable borrowed referents - -Mutable borrowed pointers are guaranteed to be the only way to mutate -their referent. This permits us to take greater license with them; for -example, the referent can be frozen simply be ensuring that we do not -use the original pointer to perform mutate. Similarly, we can allow -the referent to be claimed, so long as the original pointer is unused -while the new claimant is live. - -The rule for mutable borrowed pointers is as follows: - -```text -RESTRICTIONS(*P, LT, ACTIONS) = RS, (*P, ACTIONS) // R-Deref-Mut-Borrowed - TYPE(P) = <' mut Ty - LT <= LT' // (1) - RESTRICTIONS(P, LT, ACTIONS) = RS // (2) -``` - -Let's examine the two numbered clauses: - -Clause (1) specifies that the lifetime of the loan (`LT`) cannot -exceed the lifetime of the `&mut` pointer (`LT'`). The reason for this -is that the `&mut` pointer is guaranteed to be the only legal way to -mutate its referent -- but only for the lifetime `LT'`. After that -lifetime, the loan on the referent expires and hence the data may be -modified by its owner again. This implies that we are only able to -guarantee that the referent will not be modified or aliased for a -maximum of `LT'`. - -Here is a concrete example of a bug this rule prevents: - -```rust -// Test region-reborrow-from-shorter-mut-ref.rs: -fn copy_borrowed_ptr<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T { - &mut **p // ERROR due to clause (1) -} -fn main() { - let mut x = 1; - let mut y = &mut x; // <-'b-----------------------------+ - // +-'a--------------------+ | - // v v | - let z = copy_borrowed_ptr(&mut y); // y is lent | - *y += 1; // Here y==z, so both should not be usable... | - *z += 1; // ...and yet they would be, but for clause 1. | -} // <------------------------------------------------------+ -``` - -Clause (2) propagates the restrictions on the referent to the pointer -itself. This is the same as with an box, though the -reasoning is mildly different. The basic goal in all cases is to -prevent the user from establishing another route to the same data. To -see what I mean, let's examine various cases of what can go wrong and -show how it is prevented. - -**Example danger 1: Moving the base pointer.** One of the simplest -ways to violate the rules is to move the base pointer to a new name -and access it via that new name, thus bypassing the restrictions on -the old name. Here is an example: - -```rust -// src/test/compile-fail/borrowck-move-mut-base-ptr.rs -fn foo(t0: &mut i32) { - let p: &i32 = &*t0; // Freezes `*t0` - let t1 = t0; //~ ERROR cannot move out of `t0` - *t1 = 22; // OK, not a write through `*t0` -} -``` - -Remember that `&mut` pointers are linear, and hence `let t1 = t0` is a -move of `t0` -- or would be, if it were legal. Instead, we get an -error, because clause (2) imposes restrictions on `P` (`t0`, here), -and any restrictions on a path make it impossible to move from that -path. - -**Example danger 2: Claiming the base pointer.** Another possible -danger is to mutably borrow the base path. This can lead to two bad -scenarios. The most obvious is that the mutable borrow itself becomes -another path to access the same data, as shown here: - -```rust -// src/test/compile-fail/borrowck-mut-borrow-of-mut-base-ptr.rs -fn foo<'a>(mut t0: &'a mut i32, - mut t1: &'a mut i32) { - let p: &i32 = &*t0; // Freezes `*t0` - let mut t2 = &mut t0; //~ ERROR cannot borrow `t0` - **t2 += 1; // Mutates `*t0` -} -``` - -In this example, `**t2` is the same memory as `*t0`. Because `t2` is -an `&mut` pointer, `**t2` is a unique path and hence it would be -possible to mutate `**t2` even though that memory was supposed to be -frozen by the creation of `p`. However, an error is reported -- the -reason is that the freeze `&*t0` will restrict claims and mutation -against `*t0` which, by clause 2, in turn prevents claims and mutation -of `t0`. Hence the claim `&mut t0` is illegal. - -Another danger with an `&mut` pointer is that we could swap the `t0` -value away to create a new path: - -```rust -// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs -fn foo<'a>(mut t0: &'a mut i32, - mut t1: &'a mut i32) { - let p: &i32 = &*t0; // Freezes `*t0` - swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0` - *t1 = 22; -} -``` - -This is illegal for the same reason as above. Note that if we added -back a swap operator -- as we used to have -- we would want to be very -careful to ensure this example is still illegal. - -**Example danger 3: Freeze the base pointer.** In the case where the -referent is claimed, even freezing the base pointer can be dangerous, -as shown in the following example: - -```rust -// src/test/compile-fail/borrowck-borrow-of-mut-base-ptr.rs -fn foo<'a>(mut t0: &'a mut i32, - mut t1: &'a mut i32) { - let p: &mut i32 = &mut *t0; // Claims `*t0` - let mut t2 = &t0; //~ ERROR cannot borrow `t0` - let q: &i32 = &*t2; // Freezes `*t0` but not through `*p` - *p += 1; // violates type of `*q` -} -``` - -Here the problem is that `*t0` is claimed by `p`, and hence `p` wants -to be the controlling pointer through which mutation or freezes occur. -But `t2` would -- if it were legal -- have the type `& &mut i32`, and -hence would be a mutable pointer in an aliasable location, which is -considered frozen (since no one can write to `**t2` as it is not a -unique path). Therefore, we could reasonably create a frozen `&i32` -pointer pointing at `*t0` that coexists with the mutable pointer `p`, -which is clearly unsound. - -However, it is not always unsafe to freeze the base pointer. In -particular, if the referent is frozen, there is no harm in it: - -```rust -// src/test/ui/borrowck-borrow-of-mut-base-ptr-safe.rs -fn foo<'a>(mut t0: &'a mut i32, - mut t1: &'a mut i32) { - let p: &i32 = &*t0; // Freezes `*t0` - let mut t2 = &t0; - let q: &i32 = &*t2; // Freezes `*t0`, but that's ok... - let r: &i32 = &*t0; // ...after all, could do same thing directly. -} -``` - -In this case, creating the alias `t2` of `t0` is safe because the only -thing `t2` can be used for is to further freeze `*t0`, which is -already frozen. In particular, we cannot assign to `*t0` through the -new alias `t2`, as demonstrated in this test case: - -```rust -// src/test/ui/borrowck-borrow-mut-base-ptr-in-aliasable-loc.rs -fn foo(t0: & &mut i32) { - let t1 = t0; - let p: &i32 = &**t0; - **t1 = 22; //~ ERROR cannot assign -} -``` - -This distinction is reflected in the rules. When doing an `&mut` -borrow -- as in the first example -- the set `ACTIONS` will be -`CLAIM|MUTATE|FREEZE`, because claiming the referent implies that it -cannot be claimed, mutated, or frozen by anyone else. These -restrictions are propagated back to the base path and hence the base -path is considered unfreezable. - -In contrast, when the referent is merely frozen -- as in the second -example -- the set `ACTIONS` will be `CLAIM|MUTATE`, because freezing -the referent implies that it cannot be claimed or mutated but permits -others to freeze. Hence when these restrictions are propagated back to -the base path, it will still be considered freezable. - - - -**FIXME [RFC 1751](https://github.com/rust-lang/rfcs/issues/1751) -Restrictions against mutating the base pointer.** -When an `&mut` pointer is frozen or claimed, we currently pass along the -restriction against MUTATE to the base pointer. I do not believe this -restriction is needed. It dates from the days when we had a way to -mutate that preserved the value being mutated (i.e., swap). Nowadays -the only form of mutation is assignment, which destroys the pointer -being mutated -- therefore, a mutation cannot create a new path to the -same data. Rather, it removes an existing path. This implies that not -only can we permit mutation, we can have mutation kill restrictions in -the dataflow sense. - -**WARNING:** We do not currently have `const` borrows in the -language. If they are added back in, we must ensure that they are -consistent with all of these examples. The crucial question will be -what sorts of actions are permitted with a `&const &mut` pointer. I -would suggest that an `&mut` referent found in an `&const` location be -prohibited from both freezes and claims. This would avoid the need to -prevent `const` borrows of the base pointer when the referent is -borrowed. - -[ Previous revisions of this document discussed `&const` in more detail. -See the revision history. ] - -# Moves and initialization - -The borrow checker is also in charge of ensuring that: - -- all memory which is accessed is initialized -- immutable local variables are assigned at most once. - -These are two separate dataflow analyses built on the same -framework. Let's look at checking that memory is initialized first; -the checking of immutable local variable assignments works in a very -similar way. - -To track the initialization of memory, we actually track all the -points in the program that *create uninitialized memory*, meaning -moves and the declaration of uninitialized variables. For each of -these points, we create a bit in the dataflow set. Assignments to a -variable `x` or path `a.b.c` kill the move/uninitialization bits for -those paths and any subpaths (e.g., `x`, `x.y`, `a.b.c`, `*a.b.c`). -Bits are unioned when two control-flow paths join. Thus, the -presence of a bit indicates that the move may have occurred without an -intervening assignment to the same memory. At each use of a variable, -we examine the bits in scope, and check that none of them are -moves/uninitializations of the variable that is being used. - -Let's look at a simple example: - -```rust -fn foo(a: Box) { - let b: Box; // Gen bit 0. - - if cond { // Bits: 0 - use(&*a); - b = a; // Gen bit 1, kill bit 0. - use(&*b); - } else { - // Bits: 0 - } - // Bits: 0,1 - use(&*a); // Error. - use(&*b); // Error. -} - -fn use(a: &i32) { } -``` - -In this example, the variable `b` is created uninitialized. In one -branch of an `if`, we then move the variable `a` into `b`. Once we -exit the `if`, therefore, it is an error to use `a` or `b` since both -are only conditionally initialized. I have annotated the dataflow -state using comments. There are two dataflow bits, with bit 0 -corresponding to the creation of `b` without an initializer, and bit 1 -corresponding to the move of `a`. The assignment `b = a` both -generates bit 1, because it is a move of `a`, and kills bit 0, because -`b` is now initialized. On the else branch, though, `b` is never -initialized, and so bit 0 remains untouched. When the two flows of -control join, we union the bits from both sides, resulting in both -bits 0 and 1 being set. Thus any attempt to use `a` uncovers the bit 1 -from the "then" branch, showing that `a` may be moved, and any attempt -to use `b` uncovers bit 0, from the "else" branch, showing that `b` -may not be initialized. - -## Initialization of immutable variables - -Initialization of immutable variables works in a very similar way, -except that: - -1. we generate bits for each assignment to a variable; -2. the bits are never killed except when the variable goes out of scope. - -Thus the presence of an assignment bit indicates that the assignment -may have occurred. Note that assignments are only killed when the -variable goes out of scope, as it is not relevant whether or not there -has been a move in the meantime. Using these bits, we can declare that -an assignment to an immutable variable is legal iff there is no other -assignment bit to that same variable in scope. - -## Why is the design made this way? - -It may seem surprising that we assign dataflow bits to *each move* -rather than *each path being moved*. This is somewhat less efficient, -since on each use, we must iterate through all moves and check whether -any of them correspond to the path in question. Similar concerns apply -to the analysis for double assignments to immutable variables. The -main reason to do it this way is that it allows us to print better -error messages, because when a use occurs, we can print out the -precise move that may be in scope, rather than simply having to say -"the variable may not be initialized". - -## Data structures used in the move analysis - -The move analysis maintains several data structures that enable it to -cross-reference moves and assignments to determine when they may be -moving/assigning the same memory. These are all collected into the -`MoveData` and `FlowedMoveData` structs. The former represents the set -of move paths, moves, and assignments, and the latter adds in the -results of a dataflow computation. - -### Move paths - -The `MovePath` tree tracks every path that is moved or assigned to. -These paths have the same form as the `LoanPath` data structure, which -in turn is the "real world version of the places `P` that we -introduced earlier. The difference between a `MovePath` and a `LoanPath` -is that move paths are: - -1. Canonicalized, so that we have exactly one copy of each, and - we can refer to move paths by index; -2. Cross-referenced with other paths into a tree, so that given a move - path we can efficiently find all parent move paths and all - extensions (e.g., given the `a.b` move path, we can easily find the - move path `a` and also the move paths `a.b.c`) -3. Cross-referenced with moves and assignments, so that we can - easily find all moves and assignments to a given path. - -The mechanism that we use is to create a `MovePath` record for each -move path. These are arranged in an array and are referenced using -`MovePathIndex` values, which are newtype'd indices. The `MovePath` -structs are arranged into a tree, representing using the standard -Knuth representation where each node has a child 'pointer' and a "next -sibling" 'pointer'. In addition, each `MovePath` has a parent -'pointer'. In this case, the 'pointers' are just `MovePathIndex` -values. - -In this way, if we want to find all base paths of a given move path, -we can just iterate up the parent pointers (see `each_base_path()` in -the `move_data` module). If we want to find all extensions, we can -iterate through the subtree (see `each_extending_path()`). - -### Moves and assignments - -There are structs to represent moves (`Move`) and assignments -(`Assignment`), and these are also placed into arrays and referenced -by index. All moves of a particular path are arranged into a linked -lists, beginning with `MovePath.first_move` and continuing through -`Move.next_move`. - -We distinguish between "var" assignments, which are assignments to a -variable like `x = foo`, and "path" assignments (`x.f = foo`). This -is because we need to assign dataflows to the former, but not the -latter, so as to check for double initialization of immutable -variables. - -### Gathering and checking moves - -Like loans, we distinguish two phases. The first, gathering, is where -we uncover all the moves and assignments. As with loans, we do some -basic sanity checking in this phase, so we'll report errors if you -attempt to move out of a borrowed pointer etc. Then we do the dataflow -(see `FlowedMoveData::new`). Finally, in the `check_loans.rs` code, we -walk back over, identify all uses, assignments, and captures, and -check that they are legal given the set of dataflow bits we have -computed for that program point. - -# Drop flags and structural fragments - -In addition to the job of enforcing memory safety, the borrow checker -code is also responsible for identifying the *structural fragments* of -data in the function, to support out-of-band dynamic drop flags -allocated on the stack. (For background, see [RFC PR #320].) - -[RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320 - -Semantically, each piece of data that has a destructor may need a -boolean flag to indicate whether or not its destructor has been run -yet. However, in many cases there is no need to actually maintain such -a flag: It can be apparent from the code itself that a given path is -always initialized (or always deinitialized) when control reaches the -end of its owner's scope, and thus we can unconditionally emit (or -not) the destructor invocation for that path. - -A simple example of this is the following: - -```rust -struct D { p: i32 } -impl D { fn new(x: i32) -> D { ... } -impl Drop for D { ... } - -fn foo(a: D, b: D, t: || -> bool) { - let c: D; - let d: D; - if t() { c = b; } -} -``` - -At the end of the body of `foo`, the compiler knows that `a` is -initialized, introducing a drop obligation (deallocating the boxed -integer) for the end of `a`'s scope that is run unconditionally. -Likewise the compiler knows that `d` is not initialized, and thus it -leave out the drop code for `d`. - -The compiler cannot statically know the drop-state of `b` nor `c` at -the end of their scope, since that depends on the value of -`t`. Therefore, we need to insert boolean flags to track whether we -need to drop `b` and `c`. - -However, the matter is not as simple as just mapping local variables -to their corresponding drop flags when necessary. In particular, in -addition to being able to move data out of local variables, Rust -allows one to move values in and out of structured data. - -Consider the following: - -```rust -struct S { x: D, y: D, z: D } - -fn foo(a: S, mut b: S, t: || -> bool) { - let mut c: S; - let d: S; - let e: S = a.clone(); - if t() { - c = b; - b.x = e.y; - } - if t() { c.y = D::new(4); } -} -``` - -As before, the drop obligations of `a` and `d` can be statically -determined, and again the state of `b` and `c` depend on dynamic -state. But additionally, the dynamic drop obligations introduced by -`b` and `c` are not just per-local boolean flags. For example, if the -first call to `t` returns `false` and the second call `true`, then at -the end of their scope, `b` will be completely initialized, but only -`c.y` in `c` will be initialized. If both calls to `t` return `true`, -then at the end of their scope, `c` will be completely initialized, -but only `b.x` will be initialized in `b`, and only `e.x` and `e.z` -will be initialized in `e`. - -Note that we need to cover the `z` field in each case in some way, -since it may (or may not) need to be dropped, even though `z` is never -directly mentioned in the body of the `foo` function. We call a path -like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes -from the same structure `S` that declared the field `x` in `b.x`. - -In general we need to maintain boolean flags that match the -`S`-structure of both `b` and `c`. In addition, we need to consult -such a flag when doing an assignment (such as `c.y = D::new(4);` -above), in order to know whether or not there is a previous value that -needs to be dropped before we do the assignment. - -So for any given function, we need to determine what flags are needed -to track its drop obligations. Our strategy for determining the set of -flags is to represent the fragmentation of the structure explicitly: -by starting initially from the paths that are explicitly mentioned in -moves and assignments (such as `b.x` and `c.y` above), and then -traversing the structure of the path's type to identify leftover -*unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z` -are leftover unmoved fragments. Each fragment represents a drop -obligation that may need to be tracked. Paths that are only moved or -assigned in their entirety (like `a` and `d`) are treated as a single -drop obligation. - -The fragment construction process works by piggy-backing on the -existing `move_data` module. We already have callbacks that visit each -direct move and assignment; these form the basis for the sets of -moved_leaf_paths and assigned_leaf_paths. From these leaves, we can -walk up their parent chain to identify all of their parent paths. -We need to identify the parents because of cases like the following: - -```rust -struct Pair{ x: X, y: Y } -fn foo(dd_d_d: Pair, D>, D>) { - other_function(dd_d_d.x.y); -} -``` - -In this code, the move of the path `dd_d.x.y` leaves behind not only -the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well. - -Once we have identified the directly-referenced leaves and their -parents, we compute the left-over fragments, in the function -`fragments::add_fragment_siblings`. As of this writing this works by -looking at each directly-moved or assigned path P, and blindly -gathering all sibling fields of P (as well as siblings for the parents -of P, etc). After accumulating all such siblings, we filter out the -entries added as siblings of P that turned out to be -directly-referenced paths (or parents of directly referenced paths) -themselves, thus leaving the never-referenced "left-overs" as the only -thing left from the gathering step. - -## Array structural fragments - -A special case of the structural fragments discussed above are -the elements of an array that has been passed by value, such as -the following: - -```rust -fn foo(a: [D; 10], i: i32) -> D { - a[i] -} -``` - -The above code moves a single element out of the input array `a`. -The remainder of the array still needs to be dropped; i.e., it -is a structural fragment. Note that after performing such a move, -it is not legal to read from the array `a`. There are a number of -ways to deal with this, but the important thing to note is that -the semantics needs to distinguish in some manner between a -fragment that is the *entire* array versus a fragment that represents -all-but-one element of the array. A place where that distinction -would arise is the following: - -```rust -fn foo(a: [D; 10], b: [D; 10], i: i32, t: bool) -> D { - if t { - a[i] - } else { - b[i] - } - - // When control exits, we will need either to drop all of `a` - // and all-but-one of `b`, or to drop all of `b` and all-but-one - // of `a`. -} -``` - -There are a number of ways that the codegen backend could choose to -compile this (e.g. a `[bool; 10]` array for each such moved array; -or an `Option` for each moved array). From the viewpoint of the -borrow-checker, the important thing is to record what kind of fragment -is implied by the relevant moves. - -# Future work - -While writing up these docs, I encountered some rules I believe to be -stricter than necessary: - -- I think restricting the `&mut` P against moves and `ALIAS` is sufficient, - `MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was - a built-in operator, but as it is not, it is implied by `CLAIM`, - and `CLAIM` is implied by `ALIAS`. The only net effect of this is an - extra error message in some cases, though. -- I have not described how closures interact. Current code is unsound. - I am working on describing and implementing the fix. -- If we wish, we can easily extend the move checking to allow finer-grained - tracking of what is initialized and what is not, enabling code like - this: - - a = x.f.g; // x.f.g is now uninitialized - // here, x and x.f are not usable, but x.f.h *is* - x.f.g = b; // x.f.g is not initialized - // now x, x.f, x.f.g, x.f.h are all usable - - What needs to change here, most likely, is that the `moves` module - should record not only what paths are moved, but what expressions - are actual *uses*. For example, the reference to `x` in `x.f.g = b` - is not a true *use* in the sense that it requires `x` to be fully - initialized. This is in fact why the above code produces an error - today: the reference to `x` in `x.f.g = b` is considered illegal - because `x` is not fully initialized. - -There are also some possible refactorings: - -- It might be nice to replace all loan paths with the MovePath mechanism, - since they allow lightweight comparison using an integer. diff --git a/src/librustc_ast_borrowck/borrowck/check_loans.rs b/src/librustc_ast_borrowck/borrowck/check_loans.rs deleted file mode 100644 index dd2aeb4276faa..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/check_loans.rs +++ /dev/null @@ -1,680 +0,0 @@ -// ---------------------------------------------------------------------- -// Checking loans -// -// Phase 2 of check: we walk down the tree and check that: -// 1. assignments are always made to mutable locations; -// 2. loans made in overlapping scopes do not conflict -// 3. assignments do not affect things loaned out as immutable -// 4. moves do not affect things loaned out in any way - -use crate::borrowck::*; -use crate::borrowck::InteriorKind::{InteriorElement, InteriorField}; -use rustc::middle::expr_use_visitor as euv; -use rustc::middle::expr_use_visitor::MutateMode; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::middle::region; -use rustc::ty::{self, TyCtxt, RegionKind}; -use syntax_pos::Span; -use rustc::hir; -use rustc::hir::Node; -use log::debug; - -use std::rc::Rc; - -// FIXME (#16118): These functions are intended to allow the borrow checker to -// be less precise in its handling of Box while still allowing moves out of a -// Box. They should be removed when Unique is removed from LoanPath. - -fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> { - //! Returns the base of the leftmost dereference of an Unique in - //! `loan_path`. If there is no dereference of an Unique in `loan_path`, - //! then it just returns `loan_path` itself. - - return match helper(loan_path) { - Some(new_loan_path) => new_loan_path, - None => loan_path, - }; - - fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> { - match loan_path.kind { - LpVar(_) | LpUpvar(_) => None, - LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => { - match helper(&lp_base) { - v @ Some(_) => v, - None => Some(&lp_base) - } - } - LpDowncast(ref lp_base, _) | - LpExtend(ref lp_base, ..) => helper(&lp_base) - } - } -} - -fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc>) -> Rc> { - //! The equivalent of `owned_ptr_base_path` for an &Rc rather than - //! a &LoanPath. - - return match helper(loan_path) { - Some(new_loan_path) => new_loan_path, - None => loan_path.clone() - }; - - fn helper<'tcx>(loan_path: &Rc>) -> Option>> { - match loan_path.kind { - LpVar(_) | LpUpvar(_) => None, - LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => { - match helper(lp_base) { - v @ Some(_) => v, - None => Some(lp_base.clone()) - } - } - LpDowncast(ref lp_base, _) | - LpExtend(ref lp_base, ..) => helper(lp_base) - } - } -} - -struct CheckLoanCtxt<'a, 'tcx> { - bccx: &'a BorrowckCtxt<'a, 'tcx>, - dfcx_loans: &'a LoanDataFlow<'tcx>, - move_data: &'a move_data::FlowedMoveData<'tcx>, - all_loans: &'a [Loan<'tcx>], - movable_generator: bool, -} - -impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> { - fn consume(&mut self, - consume_id: hir::HirId, - _: Span, - cmt: &mc::cmt_<'tcx>, - mode: euv::ConsumeMode) { - debug!("consume(consume_id={}, cmt={:?})", consume_id, cmt); - - self.consume_common(consume_id.local_id, cmt, mode); - } - - fn matched_pat(&mut self, - _matched_pat: &hir::Pat, - _cmt: &mc::cmt_<'_>, - _mode: euv::MatchMode) { } - - fn consume_pat(&mut self, - consume_pat: &hir::Pat, - cmt: &mc::cmt_<'tcx>, - mode: euv::ConsumeMode) { - debug!("consume_pat(consume_pat={:?}, cmt={:?})", consume_pat, cmt); - - self.consume_common(consume_pat.hir_id.local_id, cmt, mode); - } - - fn borrow(&mut self, - borrow_id: hir::HirId, - borrow_span: Span, - cmt: &mc::cmt_<'tcx>, - loan_region: ty::Region<'tcx>, - bk: ty::BorrowKind, - loan_cause: euv::LoanCause) - { - debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \ - bk={:?}, loan_cause={:?})", - borrow_id, cmt, loan_region, - bk, loan_cause); - - if let Some(lp) = opt_loan_path(cmt) { - self.check_if_path_is_moved(borrow_id.local_id, &lp); - } - - self.check_for_conflicting_loans(borrow_id.local_id); - - self.check_for_loans_across_yields(cmt, loan_region, borrow_span); - } - - fn mutate(&mut self, - assignment_id: hir::HirId, - _: Span, - assignee_cmt: &mc::cmt_<'tcx>, - mode: euv::MutateMode) - { - debug!("mutate(assignment_id={}, assignee_cmt={:?})", - assignment_id, assignee_cmt); - - if let Some(lp) = opt_loan_path(assignee_cmt) { - match mode { - MutateMode::Init | MutateMode::JustWrite => { - // In a case like `path = 1`, then path does not - // have to be *FULLY* initialized, but we still - // must be careful lest it contains derefs of - // pointers. - self.check_if_assigned_path_is_moved(assignee_cmt.hir_id.local_id, &lp); - } - MutateMode::WriteAndRead => { - // In a case like `path += 1`, then path must be - // fully initialized, since we will read it before - // we write it. - self.check_if_path_is_moved(assignee_cmt.hir_id.local_id, - &lp); - } - } - } - self.check_assignment(assignment_id.local_id, assignee_cmt); - } - - fn decl_without_init(&mut self, _id: hir::HirId, _span: Span) { } -} - -pub fn check_loans<'a, 'tcx>( - bccx: &BorrowckCtxt<'a, 'tcx>, - dfcx_loans: &LoanDataFlow<'tcx>, - move_data: &move_data::FlowedMoveData<'tcx>, - all_loans: &[Loan<'tcx>], - body: &hir::Body, -) { - debug!("check_loans(body id={})", body.value.hir_id); - - let def_id = bccx.tcx.hir().body_owner_def_id(body.id()); - - let hir_id = bccx.tcx.hir().as_local_hir_id(def_id).unwrap(); - let movable_generator = !match bccx.tcx.hir().get(hir_id) { - Node::Expr(&hir::Expr { - kind: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)), - .. - }) => true, - _ => false, - }; - - let param_env = bccx.tcx.param_env(def_id); - let mut clcx = CheckLoanCtxt { - bccx, - dfcx_loans, - move_data, - all_loans, - movable_generator, - }; - let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id); - euv::ExprUseVisitor::new(&mut clcx, - bccx.tcx, - def_id, - param_env, - &bccx.region_scope_tree, - bccx.tables, - Some(rvalue_promotable_map)) - .consume_body(body); -} - -fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind, - borrow_kind2: ty::BorrowKind) - -> bool { - borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow -} - -impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { - pub fn tcx(&self) -> TyCtxt<'tcx> { self.bccx.tcx } - - pub fn each_issued_loan(&self, node: hir::ItemLocalId, mut op: F) -> bool where - F: FnMut(&Loan<'tcx>) -> bool, - { - //! Iterates over each loan that has been issued - //! on entrance to `node`, regardless of whether it is - //! actually *in scope* at that point. Sometimes loans - //! are issued for future scopes and thus they may have been - //! *issued* but not yet be in effect. - - self.dfcx_loans.each_bit_on_entry(node, |loan_index| { - let loan = &self.all_loans[loan_index]; - op(loan) - }) - } - - pub fn each_in_scope_loan(&self, scope: region::Scope, mut op: F) -> bool where - F: FnMut(&Loan<'tcx>) -> bool, - { - //! Like `each_issued_loan()`, but only considers loans that are - //! currently in scope. - - self.each_issued_loan(scope.item_local_id(), |loan| { - if self.bccx.region_scope_tree.is_subscope_of(scope, loan.kill_scope) { - op(loan) - } else { - true - } - }) - } - - fn each_in_scope_loan_affecting_path(&self, - scope: region::Scope, - loan_path: &LoanPath<'tcx>, - mut op: F) - -> bool where - F: FnMut(&Loan<'tcx>) -> bool, - { - //! Iterates through all of the in-scope loans affecting `loan_path`, - //! calling `op`, and ceasing iteration if `false` is returned. - - // First, we check for a loan restricting the path P being used. This - // accounts for borrows of P but also borrows of subpaths, like P.a.b. - // Consider the following example: - // - // let x = &mut a.b.c; // Restricts a, a.b, and a.b.c - // let y = a; // Conflicts with restriction - - let loan_path = owned_ptr_base_path(loan_path); - let cont = self.each_in_scope_loan(scope, |loan| { - let mut ret = true; - for restr_path in &loan.restricted_paths { - if **restr_path == *loan_path { - if !op(loan) { - ret = false; - break; - } - } - } - ret - }); - - if !cont { - return false; - } - - // Next, we must check for *loans* (not restrictions) on the path P or - // any base path. This rejects examples like the following: - // - // let x = &mut a.b; - // let y = a.b.c; - // - // Limiting this search to *loans* and not *restrictions* means that - // examples like the following continue to work: - // - // let x = &mut a.b; - // let y = a.c; - - let mut loan_path = loan_path; - loop { - match loan_path.kind { - LpVar(_) | LpUpvar(_) => { - break; - } - LpDowncast(ref lp_base, _) | - LpExtend(ref lp_base, ..) => { - loan_path = &lp_base; - } - } - - let cont = self.each_in_scope_loan(scope, |loan| { - if *loan.loan_path == *loan_path { - op(loan) - } else { - true - } - }); - - if !cont { - return false; - } - } - - return true; - } - - pub fn loans_generated_by(&self, node: hir::ItemLocalId) -> Vec { - //! Returns a vector of the loans that are generated as - //! we enter `node`. - - let mut result = Vec::new(); - self.dfcx_loans.each_gen_bit(node, |loan_index| { - result.push(loan_index); - true - }); - return result; - } - - pub fn check_for_loans_across_yields(&self, - cmt: &mc::cmt_<'tcx>, - loan_region: ty::Region<'tcx>, - borrow_span: Span) { - pub fn borrow_of_local_data(cmt: &mc::cmt_<'_>) -> bool { - match cmt.cat { - // Borrows of static items is allowed - Categorization::StaticItem => false, - // Reborrow of already borrowed data is ignored - // Any errors will be caught on the initial borrow - Categorization::Deref(..) => false, - - // By-ref upvars has Derefs so they will get ignored. - // Generators counts as FnOnce so this leaves only - // by-move upvars, which is local data for generators - Categorization::Upvar(..) => true, - - Categorization::ThreadLocal(region) | - Categorization::Rvalue(region) => { - // Rvalues promoted to 'static are no longer local - if let RegionKind::ReStatic = *region { - false - } else { - true - } - } - - // Borrow of local data must be checked - Categorization::Local(..) => true, - - // For interior references and downcasts, find out if the base is local - Categorization::Downcast(ref cmt_base, _) | - Categorization::Interior(ref cmt_base, _) => borrow_of_local_data(&cmt_base), - } - } - - if !self.movable_generator { - return; - } - - if !borrow_of_local_data(cmt) { - return; - } - - let scope = match *loan_region { - // A concrete region in which we will look for a yield expression - RegionKind::ReScope(scope) => scope, - - // There cannot be yields inside an empty region - RegionKind::ReEmpty => return, - - // Local data cannot have these lifetimes - RegionKind::ReEarlyBound(..) | - RegionKind::ReLateBound(..) | - RegionKind::ReFree(..) | - RegionKind::ReStatic => { - self.bccx - .tcx - .sess.delay_span_bug(borrow_span, - &format!("unexpected region for local data {:?}", - loan_region)); - return - } - - // These cannot exist in borrowck - RegionKind::ReVar(..) | - RegionKind::RePlaceholder(..) | - RegionKind::ReClosureBound(..) | - RegionKind::ReErased => span_bug!(borrow_span, - "unexpected region in borrowck {:?}", - loan_region), - }; - - let body_id = self.bccx.body.value.hir_id.local_id; - - if self.bccx.region_scope_tree.containing_body(scope) != Some(body_id) { - // We are borrowing local data longer than its storage. - // This should result in other borrowck errors. - self.bccx.tcx.sess.delay_span_bug(borrow_span, - "borrowing local data longer than its storage"); - return; - } - - if let Some(_) = self.bccx.region_scope_tree - .yield_in_scope_for_expr(scope, cmt.hir_id, self.bccx.body) - { - self.bccx.signal_error(); - } - } - - pub fn check_for_conflicting_loans(&self, node: hir::ItemLocalId) { - //! Checks to see whether any of the loans that are issued - //! on entrance to `node` conflict with loans that have already been - //! issued when we enter `node` (for example, we do not - //! permit two `&mut` borrows of the same variable). - //! - //! (Note that some loans can be *issued* without necessarily - //! taking effect yet.) - - debug!("check_for_conflicting_loans(node={:?})", node); - - let new_loan_indices = self.loans_generated_by(node); - debug!("new_loan_indices = {:?}", new_loan_indices); - - for &new_loan_index in &new_loan_indices { - self.each_issued_loan(node, |issued_loan| { - let new_loan = &self.all_loans[new_loan_index]; - // Only report an error for the first issued loan that conflicts - // to avoid O(n^2) errors. - self.report_error_if_loans_conflict(issued_loan, new_loan) - }); - } - - for (i, &x) in new_loan_indices.iter().enumerate() { - let old_loan = &self.all_loans[x]; - for &y in &new_loan_indices[(i+1) ..] { - let new_loan = &self.all_loans[y]; - self.report_error_if_loans_conflict(old_loan, new_loan); - } - } - } - - pub fn report_error_if_loans_conflict( - &self, - old_loan: &Loan<'tcx>, - new_loan: &Loan<'tcx>, - ) -> bool { - //! Checks whether `old_loan` and `new_loan` can safely be issued - //! simultaneously. - - debug!("report_error_if_loans_conflict(old_loan={:?}, new_loan={:?})", - old_loan, - new_loan); - - // Should only be called for loans that are in scope at the same time. - assert!(self.bccx.region_scope_tree.scopes_intersect(old_loan.kill_scope, - new_loan.kill_scope)); - - self.report_error_if_loan_conflicts_with_restriction( - old_loan, new_loan) - && self.report_error_if_loan_conflicts_with_restriction( - new_loan, old_loan) - } - - pub fn report_error_if_loan_conflicts_with_restriction( - &self, - loan1: &Loan<'tcx>, - loan2: &Loan<'tcx>, - ) -> bool { - //! Checks whether the restrictions introduced by `loan1` would - //! prohibit `loan2`. - debug!("report_error_if_loan_conflicts_with_restriction(\ - loan1={:?}, loan2={:?})", - loan1, - loan2); - - if compatible_borrow_kinds(loan1.kind, loan2.kind) { - return true; - } - - let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path); - for restr_path in &loan1.restricted_paths { - if *restr_path != loan2_base_path { continue; } - - self.bccx.signal_error(); - return false; - } - - true - } - - fn consume_common( - &self, - id: hir::ItemLocalId, - cmt: &mc::cmt_<'tcx>, - mode: euv::ConsumeMode, - ) { - if let Some(lp) = opt_loan_path(cmt) { - match mode { - euv::Copy => { - self.check_for_copy_of_frozen_path(id, &lp); - } - euv::Move(_) => { - // Sometimes moves aren't from a move path; - // this either means that the original move - // was from something illegal to move, - // or was moved from referent of an unsafe - // pointer or something like that. - if self.move_data.is_move_path(id, &lp) { - self.check_for_move_of_borrowed_path(id, &lp); - } - } - } - self.check_if_path_is_moved(id, &lp); - } - } - - fn check_for_copy_of_frozen_path(&self, - id: hir::ItemLocalId, - copy_path: &LoanPath<'tcx>) { - self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow); - } - - fn check_for_move_of_borrowed_path(&self, - id: hir::ItemLocalId, - move_path: &LoanPath<'tcx>) { - // We want to detect if there are any loans at all, so we search for - // any loans incompatible with MutBorrrow, since all other kinds of - // loans are incompatible with that. - self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow); - } - - fn analyze_restrictions_on_use(&self, - expr_id: hir::ItemLocalId, - use_path: &LoanPath<'tcx>, - borrow_kind: ty::BorrowKind) { - debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={:?})", - expr_id, use_path); - - let scope = region::Scope { - id: expr_id, - data: region::ScopeData::Node - }; - self.each_in_scope_loan_affecting_path( - scope, use_path, |loan| { - if !compatible_borrow_kinds(loan.kind, borrow_kind) { - self.bccx.signal_error(); - false - } else { - true - } - }); - } - - /// Reports an error if `expr` (which should be a path) - /// is using a moved/uninitialized value - fn check_if_path_is_moved(&self, - id: hir::ItemLocalId, - lp: &Rc>) { - debug!("check_if_path_is_moved(id={:?}, lp={:?})", id, lp); - - // FIXME: if you find yourself tempted to cut and paste - // the body below and then specializing the error reporting, - // consider refactoring this instead! - - let base_lp = owned_ptr_base_path_rc(lp); - self.move_data.each_move_of(id, &base_lp, |_, _| { - self.bccx.signal_error(); - false - }); - } - - /// Reports an error if assigning to `lp` will use a - /// moved/uninitialized value. Mainly this is concerned with - /// detecting derefs of uninitialized pointers. - /// - /// For example: - /// - /// ``` - /// let a: i32; - /// a = 10; // ok, even though a is uninitialized - /// ``` - /// - /// ``` - /// struct Point { x: u32, y: u32 } - /// let mut p: Point; - /// p.x = 22; // ok, even though `p` is uninitialized - /// ``` - /// - /// ```compile_fail,E0381 - /// # struct Point { x: u32, y: u32 } - /// let mut p: Box; - /// (*p).x = 22; // not ok, p is uninitialized, can't deref - /// ``` - fn check_if_assigned_path_is_moved(&self, - id: hir::ItemLocalId, - lp: &Rc>) - { - match lp.kind { - LpVar(_) | LpUpvar(_) => { - // assigning to `x` does not require that `x` is initialized - } - LpDowncast(ref lp_base, _) => { - // assigning to `(P->Variant).f` is ok if assigning to `P` is ok - self.check_if_assigned_path_is_moved(id, lp_base); - } - LpExtend(ref lp_base, _, LpInterior(_, InteriorField(_))) => { - match lp_base.to_type().kind { - ty::Adt(def, _) if def.has_dtor(self.tcx()) => { - // In the case where the owner implements drop, then - // the path must be initialized to prevent a case of - // partial reinitialization - // - // FIXME: could refactor via hypothetical - // generalized check_if_path_is_moved - let loan_path = owned_ptr_base_path_rc(lp_base); - self.move_data.each_move_of(id, &loan_path, |_, _| { - self.bccx - .signal_error(); - false - }); - return; - }, - _ => {}, - } - - // assigning to `P.f` is ok if assigning to `P` is ok - self.check_if_assigned_path_is_moved(id, lp_base); - } - LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) | - LpExtend(ref lp_base, _, LpDeref(_)) => { - // assigning to `P[i]` requires `P` is initialized - // assigning to `(*P)` requires `P` is initialized - self.check_if_path_is_moved(id, lp_base); - } - } - } - - fn check_assignment(&self, - assignment_id: hir::ItemLocalId, - assignee_cmt: &mc::cmt_<'tcx>) { - debug!("check_assignment(assignee_cmt={:?})", assignee_cmt); - - // Check that we don't invalidate any outstanding loans - if let Some(loan_path) = opt_loan_path(assignee_cmt) { - let scope = region::Scope { - id: assignment_id, - data: region::ScopeData::Node - }; - self.each_in_scope_loan_affecting_path(scope, &loan_path, |_| { - self.bccx.signal_error(); - false - }); - } - - // Check for reassignments to (immutable) local variables. This - // needs to be done here instead of in check_loans because we - // depend on move data. - if let Categorization::Local(_) = assignee_cmt.cat { - let lp = opt_loan_path(assignee_cmt).unwrap(); - self.move_data.each_assignment_of(assignment_id, &lp, |_| { - if !assignee_cmt.mutbl.is_mutable() { - self.bccx.signal_error(); - } - false - }); - return - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/gather_loans/gather_moves.rs b/src/librustc_ast_borrowck/borrowck/gather_loans/gather_moves.rs deleted file mode 100644 index 2239bf56bbed2..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/gather_loans/gather_moves.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Computes moves. - -use crate::borrowck::*; -use crate::borrowck::move_data::*; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::middle::mem_categorization::InteriorOffsetKind as Kind; -use rustc::ty::{self, Ty}; - -use std::rc::Rc; -use syntax_pos::Span; -use log::debug; - -struct GatherMoveInfo<'c, 'tcx> { - id: hir::ItemLocalId, - cmt: &'c mc::cmt_<'tcx>, -} - -pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData<'tcx>, - var_id: hir::HirId, - var_ty: Ty<'tcx>) { - let loan_path = Rc::new(LoanPath::new(LpVar(var_id), var_ty)); - move_data.add_move(bccx.tcx, loan_path, var_id.local_id); -} - -pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData<'tcx>, - move_expr_id: hir::ItemLocalId, - cmt: &mc::cmt_<'tcx>) { - let move_info = GatherMoveInfo { - id: move_expr_id, - cmt, - }; - gather_move(bccx, move_data, move_info); -} - -pub fn gather_move_from_pat<'a, 'c, 'tcx>( - bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData<'tcx>, - move_pat: &hir::Pat, - cmt: &'c mc::cmt_<'tcx>, -) { - let move_info = GatherMoveInfo { - id: move_pat.hir_id.local_id, - cmt, - }; - - debug!("gather_move_from_pat: move_pat={:?}", move_pat); - - gather_move(bccx, move_data, move_info); -} - -fn gather_move<'a, 'c, 'tcx>( - bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData<'tcx>, - move_info: GatherMoveInfo<'c, 'tcx>, -) { - debug!("gather_move(move_id={:?}, cmt={:?})", - move_info.id, move_info.cmt); - - let potentially_illegal_move = check_and_get_illegal_move_origin(bccx, move_info.cmt); - if let Some(_) = potentially_illegal_move { - bccx.signal_error(); - return; - } - - match opt_loan_path(&move_info.cmt) { - Some(loan_path) => { - move_data.add_move(bccx.tcx, loan_path, - move_info.id); - } - None => { - // move from rvalue or raw pointer, hence ok - } - } -} - -pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData<'tcx>, - assignment_id: hir::ItemLocalId, - assignment_span: Span, - assignee_loan_path: Rc>) { - move_data.add_assignment(bccx.tcx, - assignee_loan_path, - assignment_id, - assignment_span); -} - -// (keep in sync with move_error::report_cannot_move_out_of ) -fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - cmt: &mc::cmt_<'tcx>) - -> Option> { - match cmt.cat { - Categorization::Deref(_, mc::BorrowedPtr(..)) | - Categorization::Deref(_, mc::UnsafePtr(..)) | - Categorization::ThreadLocal(..) | - Categorization::StaticItem => { - Some(cmt.clone()) - } - - Categorization::Rvalue(..) | - Categorization::Local(..) | - Categorization::Upvar(..) => { - None - } - - Categorization::Downcast(ref b, _) | - Categorization::Interior(ref b, mc::InteriorField(_)) | - Categorization::Interior(ref b, mc::InteriorElement(Kind::Pattern)) => { - match b.ty.kind { - ty::Adt(def, _) => { - if def.has_dtor(bccx.tcx) { - Some(cmt.clone()) - } else { - check_and_get_illegal_move_origin(bccx, b) - } - } - ty::Slice(..) => Some(cmt.clone()), - _ => { - check_and_get_illegal_move_origin(bccx, b) - } - } - } - - Categorization::Interior(_, mc::InteriorElement(Kind::Index)) => { - // Forbid move of arr[i] for arr: [T; 3]; see RFC 533. - Some(cmt.clone()) - } - - Categorization::Deref(ref b, mc::Unique) => { - check_and_get_illegal_move_origin(bccx, b) - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/gather_loans/lifetime.rs b/src/librustc_ast_borrowck/borrowck/gather_loans/lifetime.rs deleted file mode 100644 index ff7dd66793d18..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/gather_loans/lifetime.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! This module implements the check that the lifetime of a borrow -//! does not exceed the lifetime of the value being borrowed. - -use crate::borrowck::*; -use rustc::hir::HirId; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::middle::region; -use rustc::ty; - -use log::debug; - -type R = Result<(),()>; - -pub fn guarantee_lifetime<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - item_scope: region::Scope, - cmt: &'a mc::cmt_<'tcx>, - loan_region: ty::Region<'tcx>) - -> Result<(),()> { - //! Reports error if `loan_region` is larger than S - //! where S is `item_scope` if `cmt` is an upvar, - //! and is scope of `cmt` otherwise. - debug!("guarantee_lifetime(cmt={:?}, loan_region={:?})", - cmt, loan_region); - let ctxt = GuaranteeLifetimeContext { bccx, item_scope, loan_region }; - ctxt.check(cmt, None) -} - -/////////////////////////////////////////////////////////////////////////// -// Private - -struct GuaranteeLifetimeContext<'a, 'tcx> { - bccx: &'a BorrowckCtxt<'a, 'tcx>, - - // the scope of the function body for the enclosing item - item_scope: region::Scope, - - loan_region: ty::Region<'tcx>, -} - -impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> { - fn check(&self, cmt: &mc::cmt_<'tcx>, discr_scope: Option) -> R { - //! Main routine. Walks down `cmt` until we find the - //! "guarantor". Reports an error if `self.loan_region` is - //! larger than scope of `cmt`. - debug!("guarantee_lifetime.check(cmt={:?}, loan_region={:?})", - cmt, - self.loan_region); - - match cmt.cat { - Categorization::Rvalue(..) | - Categorization::ThreadLocal(..) | - Categorization::Local(..) | // L-Local - Categorization::Upvar(..) | - Categorization::Deref(_, mc::BorrowedPtr(..)) | // L-Deref-Borrowed - Categorization::Deref(_, mc::UnsafePtr(..)) => { - self.check_scope(self.scope(cmt)) - } - - Categorization::StaticItem => { - Ok(()) - } - - Categorization::Downcast(ref base, _) | - Categorization::Deref(ref base, mc::Unique) | // L-Deref-Send - Categorization::Interior(ref base, _) => { // L-Field - self.check(base, discr_scope) - } - } - } - - fn check_scope(&self, max_scope: ty::Region<'tcx>) -> R { - //! Reports an error if `loan_region` is larger than `max_scope` - - if !self.bccx.is_subregion_of(self.loan_region, max_scope) { - Err(self.bccx.signal_error()) - } else { - Ok(()) - } - } - - fn scope(&self, cmt: &mc::cmt_<'tcx>) -> ty::Region<'tcx> { - //! Returns the maximal region scope for the which the - //! place `cmt` is guaranteed to be valid without any - //! rooting etc, and presuming `cmt` is not mutated. - - match cmt.cat { - Categorization::ThreadLocal(temp_scope) | - Categorization::Rvalue(temp_scope) => { - temp_scope - } - Categorization::Upvar(..) => { - self.bccx.tcx.mk_region(ty::ReScope(self.item_scope)) - } - Categorization::Local(hir_id) => { - self.bccx.tcx.mk_region(ty::ReScope( - self.bccx.region_scope_tree.var_scope(hir_id.local_id))) - } - Categorization::StaticItem | - Categorization::Deref(_, mc::UnsafePtr(..)) => { - self.bccx.tcx.lifetimes.re_static - } - Categorization::Deref(_, mc::BorrowedPtr(_, r)) => { - r - } - Categorization::Downcast(ref cmt, _) | - Categorization::Deref(ref cmt, mc::Unique) | - Categorization::Interior(ref cmt, _) => { - self.scope(cmt) - } - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/gather_loans/mod.rs b/src/librustc_ast_borrowck/borrowck/gather_loans/mod.rs deleted file mode 100644 index 16fef705ec953..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/gather_loans/mod.rs +++ /dev/null @@ -1,433 +0,0 @@ -// ---------------------------------------------------------------------- -// Gathering loans -// -// The borrow check proceeds in two phases. In phase one, we gather the full -// set of loans that are required at any point. These are sorted according to -// their associated scopes. In phase two, checking loans, we will then make -// sure that all of these loans are honored. - -use crate::borrowck::*; -use crate::borrowck::move_data::MoveData; -use rustc::middle::expr_use_visitor as euv; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::middle::region; -use rustc::ty::{self, TyCtxt}; - -use syntax_pos::Span; -use rustc::hir; -use log::debug; - -use restrictions::RestrictionResult; - -mod lifetime; -mod restrictions; -mod gather_moves; - -pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - body: hir::BodyId) - -> (Vec>, move_data::MoveData<'tcx>) { - let def_id = bccx.tcx.hir().body_owner_def_id(body); - let param_env = bccx.tcx.param_env(def_id); - let mut glcx = GatherLoanCtxt { - bccx, - all_loans: Vec::new(), - item_ub: region::Scope { - id: bccx.tcx.hir().body(body).value.hir_id.local_id, - data: region::ScopeData::Node - }, - move_data: MoveData::default(), - }; - - let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id); - euv::ExprUseVisitor::new(&mut glcx, - bccx.tcx, - def_id, - param_env, - &bccx.region_scope_tree, - bccx.tables, - Some(rvalue_promotable_map)) - .consume_body(bccx.body); - - let GatherLoanCtxt { all_loans, move_data, .. } = glcx; - (all_loans, move_data) -} - -struct GatherLoanCtxt<'a, 'tcx> { - bccx: &'a BorrowckCtxt<'a, 'tcx>, - move_data: move_data::MoveData<'tcx>, - all_loans: Vec>, - /// `item_ub` is used as an upper-bound on the lifetime whenever we - /// ask for the scope of an expression categorized as an upvar. - item_ub: region::Scope, -} - -impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> { - fn consume(&mut self, - consume_id: hir::HirId, - _consume_span: Span, - cmt: &mc::cmt_<'tcx>, - mode: euv::ConsumeMode) { - debug!("consume(consume_id={}, cmt={:?}, mode={:?})", - consume_id, cmt, mode); - - match mode { - euv::Move(_) => { - gather_moves::gather_move_from_expr( - self.bccx, &self.move_data, - consume_id.local_id, cmt); - } - euv::Copy => { } - } - } - - fn matched_pat(&mut self, - matched_pat: &hir::Pat, - cmt: &mc::cmt_<'tcx>, - mode: euv::MatchMode) { - debug!("matched_pat(matched_pat={:?}, cmt={:?}, mode={:?})", - matched_pat, - cmt, - mode); - } - - fn consume_pat(&mut self, - consume_pat: &hir::Pat, - cmt: &mc::cmt_<'tcx>, - mode: euv::ConsumeMode) { - debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})", - consume_pat, - cmt, - mode); - - match mode { - euv::Copy => { return; } - euv::Move(_) => { } - } - - gather_moves::gather_move_from_pat( - self.bccx, &self.move_data, - consume_pat, cmt); - } - - fn borrow(&mut self, - borrow_id: hir::HirId, - _: Span, - cmt: &mc::cmt_<'tcx>, - loan_region: ty::Region<'tcx>, - bk: ty::BorrowKind, - loan_cause: euv::LoanCause) - { - debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \ - bk={:?}, loan_cause={:?})", - borrow_id, cmt, loan_region, - bk, loan_cause); - - self.guarantee_valid(borrow_id.local_id, - cmt, - bk, - loan_region); - } - - fn mutate(&mut self, - assignment_id: hir::HirId, - assignment_span: Span, - assignee_cmt: &mc::cmt_<'tcx>, - _: euv::MutateMode) - { - self.guarantee_assignment_valid(assignment_id, - assignment_span, - assignee_cmt); - } - - fn decl_without_init(&mut self, id: hir::HirId, _span: Span) { - let ty = self.bccx - .tables - .node_type(id); - gather_moves::gather_decl(self.bccx, &self.move_data, id, ty); - } - - fn nested_body(&mut self, body_id: hir::BodyId) { - debug!("nested_body(body_id={:?})", body_id); - // rust-lang/rust#58776: MIR and AST borrow check disagree on where - // certain closure errors are reported. As such migrate borrowck has to - // operate at the level of items, rather than bodies. Check if the - // contained closure had any errors and set `signalled_any_error` if it - // has. - let bccx = self.bccx; - if bccx.tcx.migrate_borrowck() { - if let SignalledError::NoErrorsSeen = bccx.signalled_any_error.get() { - let closure_def_id = bccx.tcx.hir().body_owner_def_id(body_id); - debug!("checking closure: {:?}", closure_def_id); - - bccx.signalled_any_error.set(bccx.tcx.borrowck(closure_def_id).signalled_any_error); - } - } - } -} - -/// Implements the A-* rules in README.md. -fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - cmt: &mc::cmt_<'tcx>, - req_kind: ty::BorrowKind) - -> Result<(),()> { - - let aliasability = cmt.freely_aliasable(); - debug!("check_aliasability aliasability={:?} req_kind={:?}", - aliasability, req_kind); - - match (aliasability, req_kind) { - (mc::Aliasability::NonAliasable, _) => { - /* Uniquely accessible path -- OK for `&` and `&mut` */ - Ok(()) - } - (mc::Aliasability::FreelyAliasable(mc::AliasableStatic), ty::ImmBorrow) => { - // Borrow of an immutable static item. - Ok(()) - } - (mc::Aliasability::FreelyAliasable(mc::AliasableStaticMut), _) => { - // Even touching a static mut is considered unsafe. We assume the - // user knows what they're doing in these cases. - Ok(()) - } - (mc::Aliasability::FreelyAliasable(_), ty::UniqueImmBorrow) | - (mc::Aliasability::FreelyAliasable(_), ty::MutBorrow) => { - bccx.signal_error(); - Err(()) - } - (..) => { - Ok(()) - } - } -} - -/// Implements the M-* rules in README.md. -fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - cmt: &mc::cmt_<'tcx>, - req_kind: ty::BorrowKind) - -> Result<(),()> { - debug!("check_mutability(cmt={:?} req_kind={:?}", cmt, req_kind); - match req_kind { - ty::UniqueImmBorrow | ty::ImmBorrow => { - match cmt.mutbl { - // I am intentionally leaving this here to help - // refactoring if, in the future, we should add new - // kinds of mutability. - mc::McImmutable | mc::McDeclared | mc::McInherited => { - // both imm and mut data can be lent as imm; - // for mutable data, this is a freeze - Ok(()) - } - } - } - - ty::MutBorrow => { - // Only mutable data can be lent as mutable. - if !cmt.mutbl.is_mutable() { - Err(bccx.signal_error()) - } else { - Ok(()) - } - } - } -} - -impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> { - pub fn tcx(&self) -> TyCtxt<'tcx> { self.bccx.tcx } - - /// Guarantees that `cmt` is assignable, or reports an error. - fn guarantee_assignment_valid(&mut self, - assignment_id: hir::HirId, - assignment_span: Span, - cmt: &mc::cmt_<'tcx>) { - - let opt_lp = opt_loan_path(cmt); - debug!("guarantee_assignment_valid(assignment_id={}, cmt={:?}) opt_lp={:?}", - assignment_id, cmt, opt_lp); - - if let Categorization::Local(..) = cmt.cat { - // Only re-assignments to locals require it to be - // mutable - this is checked in check_loans. - } else { - // Check that we don't allow assignments to non-mutable data. - if check_mutability(self.bccx, cmt, ty::MutBorrow).is_err() { - return; // reported an error, no sense in reporting more. - } - } - - // Check that we don't allow assignments to aliasable data - if check_aliasability(self.bccx, cmt, ty::MutBorrow).is_err() { - return; // reported an error, no sense in reporting more. - } - - match opt_lp { - Some(lp) => { - gather_moves::gather_assignment(self.bccx, &self.move_data, - assignment_id.local_id, - assignment_span, - lp); - } - None => { - // This can occur with e.g., `*foo() = 5`. In such - // cases, there is no need to check for conflicts - // with moves etc, just ignore. - } - } - } - - /// Guarantees that `addr_of(cmt)` will be valid for the duration of `static_scope_r`, or - /// reports an error. This may entail taking out loans, which will be added to the - /// `req_loan_map`. - fn guarantee_valid(&mut self, - borrow_id: hir::ItemLocalId, - cmt: &mc::cmt_<'tcx>, - req_kind: ty::BorrowKind, - loan_region: ty::Region<'tcx>) { - debug!("guarantee_valid(borrow_id={:?}, cmt={:?}, \ - req_mutbl={:?}, loan_region={:?})", - borrow_id, - cmt, - req_kind, - loan_region); - - // a loan for the empty region can never be dereferenced, so - // it is always safe - if *loan_region == ty::ReEmpty { - return; - } - - // Check that the lifetime of the borrow does not exceed - // the lifetime of the data being borrowed. - if lifetime::guarantee_lifetime(self.bccx, self.item_ub, cmt, loan_region).is_err() { - return; // reported an error, no sense in reporting more. - } - - // Check that we don't allow mutable borrows of non-mutable data. - if check_mutability(self.bccx, cmt, req_kind).is_err() { - return; // reported an error, no sense in reporting more. - } - - // Check that we don't allow mutable borrows of aliasable data. - if check_aliasability(self.bccx, cmt, req_kind).is_err() { - return; // reported an error, no sense in reporting more. - } - - // Compute the restrictions that are required to enforce the - // loan is safe. - let restr = restrictions::compute_restrictions(self.bccx, &cmt, loan_region); - - debug!("guarantee_valid(): restrictions={:?}", restr); - - // Create the loan record (if needed). - let loan = match restr { - RestrictionResult::Safe => { - // No restrictions---no loan record necessary - return; - } - - RestrictionResult::SafeIf(loan_path, restricted_paths) => { - let loan_scope = match *loan_region { - ty::ReScope(scope) => scope, - - ty::ReEarlyBound(ref br) => { - self.bccx.region_scope_tree.early_free_scope(self.tcx(), br) - } - - ty::ReFree(ref fr) => { - self.bccx.region_scope_tree.free_scope(self.tcx(), fr) - } - - ty::ReStatic => self.item_ub, - - ty::ReEmpty | - ty::ReClosureBound(..) | - ty::ReLateBound(..) | - ty::ReVar(..) | - ty::RePlaceholder(..) | - ty::ReErased => { - span_bug!( - cmt.span, - "invalid borrow lifetime: {:?}", - loan_region); - } - }; - debug!("loan_scope = {:?}", loan_scope); - - let borrow_scope = region::Scope { - id: borrow_id, - data: region::ScopeData::Node - }; - let gen_scope = self.compute_gen_scope(borrow_scope, loan_scope); - debug!("gen_scope = {:?}", gen_scope); - - let kill_scope = self.compute_kill_scope(loan_scope, &loan_path); - debug!("kill_scope = {:?}", kill_scope); - - Loan { - index: self.all_loans.len(), - loan_path, - kind: req_kind, - gen_scope, - kill_scope, - restricted_paths, - } - } - }; - - debug!("guarantee_valid(borrow_id={:?}), loan={:?}", - borrow_id, loan); - - // let loan_path = loan.loan_path; - // let loan_gen_scope = loan.gen_scope; - // let loan_kill_scope = loan.kill_scope; - self.all_loans.push(loan); - } - - pub fn compute_gen_scope(&self, - borrow_scope: region::Scope, - loan_scope: region::Scope) - -> region::Scope { - //! Determine when to introduce the loan. Typically the loan - //! is introduced at the point of the borrow, but in some cases, - //! notably method arguments, the loan may be introduced only - //! later, once it comes into scope. - - if self.bccx.region_scope_tree.is_subscope_of(borrow_scope, loan_scope) { - borrow_scope - } else { - loan_scope - } - } - - pub fn compute_kill_scope(&self, loan_scope: region::Scope, lp: &LoanPath<'tcx>) - -> region::Scope { - //! Determine when the loan restrictions go out of scope. - //! This is either when the lifetime expires or when the - //! local variable which roots the loan-path goes out of scope, - //! whichever happens faster. - //! - //! It may seem surprising that we might have a loan region - //! larger than the variable which roots the loan-path; this can - //! come about when variables of `&mut` type are re-borrowed, - //! as in this example: - //! - //! struct Foo { counter: u32 } - //! - //! fn counter<'a>(v: &'a mut Foo) -> &'a mut u32 { - //! &mut v.counter - //! } - //! - //! In this case, the reference (`'a`) outlives the - //! variable `v` that hosts it. Note that this doesn't come up - //! with immutable `&` pointers, because borrows of such pointers - //! do not require restrictions and hence do not cause a loan. - - let lexical_scope = lp.kill_scope(self.bccx); - if self.bccx.region_scope_tree.is_subscope_of(lexical_scope, loan_scope) { - lexical_scope - } else { - assert!(self.bccx.region_scope_tree.is_subscope_of(loan_scope, lexical_scope)); - loan_scope - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/gather_loans/restrictions.rs b/src/librustc_ast_borrowck/borrowck/gather_loans/restrictions.rs deleted file mode 100644 index ee5099a97d57f..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/gather_loans/restrictions.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! Computes the restrictions that result from a borrow. - -use crate::borrowck::*; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::ty; -use log::debug; - -use crate::borrowck::ToInteriorKind; - -use std::rc::Rc; - -#[derive(Debug)] -pub enum RestrictionResult<'tcx> { - Safe, - SafeIf(Rc>, Vec>>) -} - -pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - cmt: &mc::cmt_<'tcx>, - loan_region: ty::Region<'tcx>) - -> RestrictionResult<'tcx> { - let ctxt = RestrictionsContext { bccx, loan_region }; - - ctxt.restrict(cmt) -} - -/////////////////////////////////////////////////////////////////////////// -// Private - -struct RestrictionsContext<'a, 'tcx> { - bccx: &'a BorrowckCtxt<'a, 'tcx>, - loan_region: ty::Region<'tcx>, -} - -impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { - fn restrict(&self, - cmt: &mc::cmt_<'tcx>) -> RestrictionResult<'tcx> { - debug!("restrict(cmt={:?})", cmt); - - let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty)); - - match cmt.cat.clone() { - Categorization::Rvalue(..) => { - // Effectively, rvalues are stored into a - // non-aliasable temporary on the stack. Since they - // are inherently non-aliasable, they can only be - // accessed later through the borrow itself and hence - // must inherently comply with its terms. - RestrictionResult::Safe - } - - Categorization::ThreadLocal(..) => { - // Thread-locals are statics that have a scope, with - // no underlying structure to provide restrictions. - RestrictionResult::Safe - } - - Categorization::Local(local_id) => { - // R-Variable, locally declared - let lp = new_lp(LpVar(local_id)); - RestrictionResult::SafeIf(lp.clone(), vec![lp]) - } - - Categorization::Upvar(mc::Upvar { id, .. }) => { - // R-Variable, captured into closure - let lp = new_lp(LpUpvar(id)); - RestrictionResult::SafeIf(lp.clone(), vec![lp]) - } - - Categorization::Downcast(cmt_base, _) => { - // When we borrow the interior of an enum, we have to - // ensure the enum itself is not mutated, because that - // could cause the type of the memory to change. - self.restrict(&cmt_base) - } - - Categorization::Interior(cmt_base, interior) => { - // R-Field - // - // Overwriting the base would not change the type of - // the memory, so no additional restrictions are - // needed. - let opt_variant_id = match cmt_base.cat { - Categorization::Downcast(_, variant_id) => Some(variant_id), - _ => None - }; - let interior = interior.cleaned(); - let base_ty = cmt_base.ty; - let result = self.restrict(&cmt_base); - // Borrowing one union field automatically borrows all its fields. - match base_ty.kind { - ty::Adt(adt_def, _) if adt_def.is_union() => match result { - RestrictionResult::Safe => RestrictionResult::Safe, - RestrictionResult::SafeIf(base_lp, mut base_vec) => { - for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() { - let field = InteriorKind::InteriorField( - mc::FieldIndex(i, field.ident.name) - ); - let field_ty = if field == interior { - cmt.ty - } else { - self.bccx.tcx.types.err // Doesn't matter - }; - let sibling_lp_kind = LpExtend(base_lp.clone(), cmt.mutbl, - LpInterior(opt_variant_id, field)); - let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty)); - base_vec.push(sibling_lp); - } - - let lp = new_lp(LpExtend(base_lp, cmt.mutbl, - LpInterior(opt_variant_id, interior))); - RestrictionResult::SafeIf(lp, base_vec) - } - }, - _ => self.extend(result, &cmt, LpInterior(opt_variant_id, interior)) - } - } - - Categorization::StaticItem => { - RestrictionResult::Safe - } - - Categorization::Deref(cmt_base, pk) => { - match pk { - mc::Unique => { - // R-Deref-Send-Pointer - // - // When we borrow the interior of a box, we - // cannot permit the base to be mutated, because that - // would cause the unique pointer to be freed. - // - // Eventually we should make these non-special and - // just rely on Deref implementation. - let result = self.restrict(&cmt_base); - self.extend(result, &cmt, LpDeref(pk)) - } - mc::BorrowedPtr(bk, lt) => { - // R-Deref-[Mut-]Borrowed - if !self.bccx.is_subregion_of(self.loan_region, lt) { - self.bccx.signal_error(); - return RestrictionResult::Safe; - } - - match bk { - ty::ImmBorrow => RestrictionResult::Safe, - ty::MutBorrow | ty::UniqueImmBorrow => { - // R-Deref-Mut-Borrowed - // - // The referent can be aliased after the - // references lifetime ends (by a newly-unfrozen - // borrow). - let result = self.restrict(&cmt_base); - self.extend(result, &cmt, LpDeref(pk)) - } - } - } - // Borrowck is not relevant for raw pointers - mc::UnsafePtr(..) => RestrictionResult::Safe - } - } - } - } - - fn extend(&self, - result: RestrictionResult<'tcx>, - cmt: &mc::cmt_<'tcx>, - elem: LoanPathElem<'tcx>) -> RestrictionResult<'tcx> { - match result { - RestrictionResult::Safe => RestrictionResult::Safe, - RestrictionResult::SafeIf(base_lp, mut base_vec) => { - let v = LpExtend(base_lp, cmt.mutbl, elem); - let lp = Rc::new(LoanPath::new(v, cmt.ty)); - base_vec.push(lp.clone()); - RestrictionResult::SafeIf(lp, base_vec) - } - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/mod.rs b/src/librustc_ast_borrowck/borrowck/mod.rs deleted file mode 100644 index 40e28299a5c0a..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/mod.rs +++ /dev/null @@ -1,621 +0,0 @@ -//! See The Book chapter on the borrow checker for more details. - -#![allow(non_camel_case_types)] - -pub use LoanPathKind::*; -pub use LoanPathElem::*; - -use InteriorKind::*; - -use rustc::hir::HirId; -use rustc::hir::Node; -use rustc::middle::borrowck::{BorrowCheckResult, SignalledError}; -use rustc::hir::def_id::{DefId, LocalDefId}; -use rustc::middle::mem_categorization as mc; -use rustc::middle::mem_categorization::Categorization; -use rustc::middle::region; -use rustc::middle::free_region::RegionRelations; -use rustc::ty::{self, Ty, TyCtxt}; -use rustc::ty::query::Providers; - -use std::borrow::Cow; -use std::cell::{Cell}; -use std::fmt; -use std::rc::Rc; -use std::hash::{Hash, Hasher}; -use log::debug; - -use rustc::hir; - -use crate::cfg; -use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom}; - -pub mod check_loans; - -pub mod gather_loans; - -pub mod move_data; - -#[derive(Clone, Copy)] -pub struct LoanDataFlowOperator; - -pub type LoanDataFlow<'tcx> = DataFlowContext<'tcx, LoanDataFlowOperator>; - -pub fn check_crate(tcx: TyCtxt<'_>) { - tcx.par_body_owners(|body_owner_def_id| { - tcx.ensure().borrowck(body_owner_def_id); - }); -} - -pub fn provide(providers: &mut Providers<'_>) { - *providers = Providers { - borrowck, - ..*providers - }; -} - -/// Collection of conclusions determined via borrow checker analyses. -pub struct AnalysisData<'tcx> { - pub all_loans: Vec>, - pub loans: DataFlowContext<'tcx, LoanDataFlowOperator>, - pub move_data: move_data::FlowedMoveData<'tcx>, -} - -fn borrowck(tcx: TyCtxt<'_>, owner_def_id: DefId) -> &BorrowCheckResult { - assert!(tcx.use_ast_borrowck() || tcx.migrate_borrowck()); - - debug!("borrowck(body_owner_def_id={:?})", owner_def_id); - - let signalled_error = tcx.check_match(owner_def_id); - if let SignalledError::SawSomeError = signalled_error { - return tcx.arena.alloc(BorrowCheckResult { - signalled_any_error: SignalledError::SawSomeError, - }) - } - - let owner_id = tcx.hir().as_local_hir_id(owner_def_id).unwrap(); - - match tcx.hir().get(owner_id) { - Node::Ctor(..) => { - // We get invoked with anything that has MIR, but some of - // those things (notably the synthesized constructors from - // tuple structs/variants) do not have an associated body - // and do not need borrowchecking. - return tcx.arena.alloc(BorrowCheckResult { - signalled_any_error: SignalledError::NoErrorsSeen, - }) - } - _ => { } - } - - let body_id = tcx.hir().body_owned_by(owner_id); - let tables = tcx.typeck_tables_of(owner_def_id); - let region_scope_tree = tcx.region_scope_tree(owner_def_id); - let body = tcx.hir().body(body_id); - let mut bccx = BorrowckCtxt { - tcx, - tables, - region_scope_tree, - owner_def_id, - body, - signalled_any_error: Cell::new(SignalledError::NoErrorsSeen), - }; - - // Eventually, borrowck will always read the MIR, but at the - // moment we do not. So, for now, we always force MIR to be - // constructed for a given fn, since this may result in errors - // being reported and we want that to happen. - // - // Note that `mir_validated` is a "stealable" result; the - // thief, `optimized_mir()`, forces borrowck, so we know that - // is not yet stolen. - tcx.ensure().mir_validated(owner_def_id); - - // option dance because you can't capture an uninitialized variable - // by mut-ref. - let mut cfg = None; - if let Some(AnalysisData { all_loans, - loans: loan_dfcx, - move_data: flowed_moves }) = - build_borrowck_dataflow_data(&mut bccx, false, body_id, - |bccx| { - cfg = Some(cfg::CFG::new(bccx.tcx, &body)); - cfg.as_mut().unwrap() - }) - { - check_loans::check_loans(&mut bccx, &loan_dfcx, &flowed_moves, &all_loans, body); - } - - tcx.arena.alloc(BorrowCheckResult { - signalled_any_error: bccx.signalled_any_error.into_inner(), - }) -} - -fn build_borrowck_dataflow_data<'a, 'c, 'tcx, F>(this: &mut BorrowckCtxt<'a, 'tcx>, - force_analysis: bool, - body_id: hir::BodyId, - get_cfg: F) - -> Option> - where F: FnOnce(&mut BorrowckCtxt<'a, 'tcx>) -> &'c cfg::CFG -{ - // Check the body of fn items. - let (all_loans, move_data) = - gather_loans::gather_loans_in_fn(this, body_id); - - if !force_analysis && move_data.is_empty() && all_loans.is_empty() { - // large arrays of data inserted as constants can take a lot of - // time and memory to borrow-check - see issue #36799. However, - // they don't have places, so no borrow-check is actually needed. - // Recognize that case and skip borrow-checking. - debug!("skipping loan propagation for {:?} because of no loans", body_id); - return None; - } else { - debug!("propagating loans in {:?}", body_id); - } - - let cfg = get_cfg(this); - let mut loan_dfcx = - DataFlowContext::new(this.tcx, - "borrowck", - Some(this.body), - cfg, - LoanDataFlowOperator, - all_loans.len()); - for (loan_idx, loan) in all_loans.iter().enumerate() { - loan_dfcx.add_gen(loan.gen_scope.item_local_id(), loan_idx); - loan_dfcx.add_kill(KillFrom::ScopeEnd, - loan.kill_scope.item_local_id(), - loan_idx); - } - loan_dfcx.add_kills_from_flow_exits(cfg); - loan_dfcx.propagate(cfg, this.body); - - let flowed_moves = move_data::FlowedMoveData::new(move_data, - this, - cfg, - this.body); - - Some(AnalysisData { all_loans, - loans: loan_dfcx, - move_data:flowed_moves }) -} - -/// Accessor for introspective clients inspecting `AnalysisData` and -/// the `BorrowckCtxt` itself , e.g., the flowgraph visualizer. -pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>( - tcx: TyCtxt<'tcx>, - body_id: hir::BodyId, - cfg: &cfg::CFG) - -> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'tcx>) -{ - let owner_id = tcx.hir().body_owner(body_id); - let owner_def_id = tcx.hir().local_def_id(owner_id); - let tables = tcx.typeck_tables_of(owner_def_id); - let region_scope_tree = tcx.region_scope_tree(owner_def_id); - let body = tcx.hir().body(body_id); - let mut bccx = BorrowckCtxt { - tcx, - tables, - region_scope_tree, - owner_def_id, - body, - signalled_any_error: Cell::new(SignalledError::NoErrorsSeen), - }; - - let dataflow_data = build_borrowck_dataflow_data(&mut bccx, true, body_id, |_| cfg); - (bccx, dataflow_data.unwrap()) -} - -// ---------------------------------------------------------------------- -// Type definitions - -pub struct BorrowckCtxt<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - - // tables for the current thing we are checking; set to - // Some in `borrowck_fn` and cleared later - tables: &'a ty::TypeckTables<'tcx>, - - region_scope_tree: &'tcx region::ScopeTree, - - owner_def_id: DefId, - - body: &'tcx hir::Body, - - signalled_any_error: Cell, -} - - -impl<'a, 'tcx: 'a> BorrowckCtxt<'a, 'tcx> { - fn signal_error(&self) { - self.signalled_any_error.set(SignalledError::SawSomeError); - } -} - -/////////////////////////////////////////////////////////////////////////// -// Loans and loan paths - -/// Record of a loan that was issued. -pub struct Loan<'tcx> { - index: usize, - loan_path: Rc>, - kind: ty::BorrowKind, - restricted_paths: Vec>>, - - /// gen_scope indicates where loan is introduced. Typically the - /// loan is introduced at the point of the borrow, but in some - /// cases, notably method arguments, the loan may be introduced - /// only later, once it comes into scope. See also - /// `GatherLoanCtxt::compute_gen_scope`. - gen_scope: region::Scope, - - /// kill_scope indicates when the loan goes out of scope. This is - /// either when the lifetime expires or when the local variable - /// which roots the loan-path goes out of scope, whichever happens - /// faster. See also `GatherLoanCtxt::compute_kill_scope`. - kill_scope: region::Scope, -} - -impl<'tcx> Loan<'tcx> { - pub fn loan_path(&self) -> Rc> { - self.loan_path.clone() - } -} - -#[derive(Eq)] -pub struct LoanPath<'tcx> { - kind: LoanPathKind<'tcx>, - ty: Ty<'tcx>, -} - -impl<'tcx> PartialEq for LoanPath<'tcx> { - fn eq(&self, that: &LoanPath<'tcx>) -> bool { - self.kind == that.kind - } -} - -impl<'tcx> Hash for LoanPath<'tcx> { - fn hash(&self, state: &mut H) { - self.kind.hash(state); - } -} - -#[derive(PartialEq, Eq, Hash, Debug)] -pub enum LoanPathKind<'tcx> { - LpVar(hir::HirId), // `x` in README.md - LpUpvar(ty::UpvarId), // `x` captured by-value into closure - LpDowncast(Rc>, DefId), // `x` downcast to particular enum variant - LpExtend(Rc>, mc::MutabilityCategory, LoanPathElem<'tcx>) -} - -impl<'tcx> LoanPath<'tcx> { - fn new(kind: LoanPathKind<'tcx>, ty: Ty<'tcx>) -> LoanPath<'tcx> { - LoanPath { kind: kind, ty: ty } - } - - fn to_type(&self) -> Ty<'tcx> { self.ty } -} - -// FIXME (pnkfelix): See discussion here -// https://github.com/pnkfelix/rust/commit/ -// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003 -const DOWNCAST_PRINTED_OPERATOR: &'static str = " as "; - -// A local, "cleaned" version of `mc::InteriorKind` that drops -// information that is not relevant to loan-path analysis. (In -// particular, the distinction between how precisely an array-element -// is tracked is irrelevant here.) -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum InteriorKind { - InteriorField(mc::FieldIndex), - InteriorElement, -} - -trait ToInteriorKind { fn cleaned(self) -> InteriorKind; } -impl ToInteriorKind for mc::InteriorKind { - fn cleaned(self) -> InteriorKind { - match self { - mc::InteriorField(name) => InteriorField(name), - mc::InteriorElement(_) => InteriorElement, - } - } -} - -// This can be: -// - a pointer dereference (`*P` in README.md) -// - a field reference, with an optional definition of the containing -// enum variant (`P.f` in README.md) -// `DefId` is present when the field is part of struct that is in -// a variant of an enum. For instance in: -// `enum E { X { foo: u32 }, Y { foo: u32 }}` -// each `foo` is qualified by the definitition id of the variant (`X` or `Y`). -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub enum LoanPathElem<'tcx> { - LpDeref(mc::PointerKind<'tcx>), - LpInterior(Option, InteriorKind), -} - -fn closure_to_block(closure_id: LocalDefId, tcx: TyCtxt<'_>) -> HirId { - let closure_id = tcx.hir().local_def_id_to_hir_id(closure_id); - match tcx.hir().get(closure_id) { - Node::Expr(expr) => match expr.kind { - hir::ExprKind::Closure(.., body_id, _, _) => { - body_id.hir_id - } - _ => { - bug!("encountered non-closure id: {}", closure_id) - } - }, - _ => bug!("encountered non-expr id: {}", closure_id) - } -} - -impl<'a, 'tcx> LoanPath<'tcx> { - pub fn kill_scope(&self, bccx: &BorrowckCtxt<'a, 'tcx>) -> region::Scope { - match self.kind { - LpVar(hir_id) => { - bccx.region_scope_tree.var_scope(hir_id.local_id) - } - LpUpvar(upvar_id) => { - let block_id = closure_to_block(upvar_id.closure_expr_id, bccx.tcx); - region::Scope { id: block_id.local_id, data: region::ScopeData::Node } - } - LpDowncast(ref base, _) | - LpExtend(ref base, ..) => base.kill_scope(bccx), - } - } -} - -// Avoid "cannot borrow immutable field `self.x` as mutable" as that implies that a field *can* be -// mutable independently of the struct it belongs to. (#35937) -pub fn opt_loan_path_is_field<'tcx>(cmt: &mc::cmt_<'tcx>) -> (Option>>, bool) { - let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty)); - - match cmt.cat { - Categorization::Rvalue(..) | - Categorization::ThreadLocal(..) | - Categorization::StaticItem => { - (None, false) - } - - Categorization::Local(id) => { - (Some(new_lp(LpVar(id))), false) - } - - Categorization::Upvar(mc::Upvar { id, .. }) => { - (Some(new_lp(LpUpvar(id))), false) - } - - Categorization::Deref(ref cmt_base, pk) => { - let lp = opt_loan_path_is_field(cmt_base); - (lp.0.map(|lp| { - new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk))) - }), lp.1) - } - - Categorization::Interior(ref cmt_base, ik) => { - (opt_loan_path(cmt_base).map(|lp| { - let opt_variant_id = match cmt_base.cat { - Categorization::Downcast(_, did) => Some(did), - _ => None - }; - new_lp(LpExtend(lp, cmt.mutbl, LpInterior(opt_variant_id, ik.cleaned()))) - }), true) - } - - Categorization::Downcast(ref cmt_base, variant_def_id) => { - let lp = opt_loan_path_is_field(cmt_base); - (lp.0.map(|lp| { - new_lp(LpDowncast(lp, variant_def_id)) - }), lp.1) - } - } -} - -/// Computes the `LoanPath` (if any) for a `cmt`. -/// Note that this logic is somewhat duplicated in -/// the method `compute()` found in `gather_loans::restrictions`, -/// which allows it to share common loan path pieces as it -/// traverses the CMT. -pub fn opt_loan_path<'tcx>(cmt: &mc::cmt_<'tcx>) -> Option>> { - opt_loan_path_is_field(cmt).0 -} - -/////////////////////////////////////////////////////////////////////////// -// Misc - -impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { - pub fn is_subregion_of(&self, - r_sub: ty::Region<'tcx>, - r_sup: ty::Region<'tcx>) - -> bool - { - let region_rels = RegionRelations::new(self.tcx, - self.owner_def_id, - &self.region_scope_tree, - &self.tables.free_region_map); - region_rels.is_subregion_of(r_sub, r_sup) - } - - pub fn append_loan_path_to_string(&self, - loan_path: &LoanPath<'tcx>, - out: &mut String) { - match loan_path.kind { - LpUpvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id: id }, closure_expr_id: _ }) => { - out.push_str(&self.tcx.hir().name(id).as_str()); - } - LpVar(id) => { - out.push_str(&self.tcx.hir().name(id).as_str()); - } - - LpDowncast(ref lp_base, variant_def_id) => { - out.push('('); - self.append_loan_path_to_string(&lp_base, out); - out.push_str(DOWNCAST_PRINTED_OPERATOR); - out.push_str(&self.tcx.def_path_str(variant_def_id)); - out.push(')'); - } - - LpExtend(ref lp_base, _, LpInterior(_, InteriorField(mc::FieldIndex(_, info)))) => { - self.append_autoderefd_loan_path_to_string(&lp_base, out); - out.push('.'); - out.push_str(&info.as_str()); - } - - LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) => { - self.append_autoderefd_loan_path_to_string(&lp_base, out); - out.push_str("[..]"); - } - - LpExtend(ref lp_base, _, LpDeref(_)) => { - out.push('*'); - self.append_loan_path_to_string(&lp_base, out); - } - } - } - - pub fn append_autoderefd_loan_path_to_string(&self, - loan_path: &LoanPath<'tcx>, - out: &mut String) { - match loan_path.kind { - LpExtend(ref lp_base, _, LpDeref(_)) => { - // For a path like `(*x).f` or `(*x)[3]`, autoderef - // rules would normally allow users to omit the `*x`. - // So just serialize such paths to `x.f` or x[3]` respectively. - self.append_autoderefd_loan_path_to_string(&lp_base, out) - } - - LpDowncast(ref lp_base, variant_def_id) => { - out.push('('); - self.append_autoderefd_loan_path_to_string(&lp_base, out); - out.push_str(DOWNCAST_PRINTED_OPERATOR); - out.push_str(&self.tcx.def_path_str(variant_def_id)); - out.push(')'); - } - - LpVar(..) | LpUpvar(..) | LpExtend(.., LpInterior(..)) => { - self.append_loan_path_to_string(loan_path, out) - } - } - } - - pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String { - let mut result = String::new(); - self.append_loan_path_to_string(loan_path, &mut result); - result - } - - pub fn cmt_to_cow_str(&self, cmt: &mc::cmt_<'tcx>) -> Cow<'static, str> { - cmt.descriptive_string(self.tcx) - } - - pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt_<'tcx>) -> String { - match opt_loan_path(cmt) { - Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)), - None => self.cmt_to_cow_str(cmt).into_owned(), - } - } -} - -impl BitwiseOperator for LoanDataFlowOperator { - #[inline] - fn join(&self, succ: usize, pred: usize) -> usize { - succ | pred // loans from both preds are in scope - } -} - -impl DataFlowOperator for LoanDataFlowOperator { - #[inline] - fn initial_value(&self) -> bool { - false // no loans in scope by default - } -} - -impl fmt::Debug for InteriorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - InteriorField(mc::FieldIndex(_, info)) => write!(f, "{}", info), - InteriorElement => write!(f, "[]"), - } - } -} - -impl<'tcx> fmt::Debug for Loan<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Loan_{}({:?}, {:?}, {:?}-{:?}, {:?})", - self.index, - self.loan_path, - self.kind, - self.gen_scope, - self.kill_scope, - self.restricted_paths) - } -} - -impl<'tcx> fmt::Debug for LoanPath<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - LpVar(id) => { - write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().node_to_string(id))) - } - - LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath {hir_id: var_id}, closure_expr_id }) => { - let s = ty::tls::with(|tcx| { - tcx.hir().node_to_string(var_id) - }); - write!(f, "$({} captured by id={:?})", s, closure_expr_id) - } - - LpDowncast(ref lp, variant_def_id) => { - let variant_str = if variant_def_id.is_local() { - ty::tls::with(|tcx| tcx.def_path_str(variant_def_id)) - } else { - format!("{:?}", variant_def_id) - }; - write!(f, "({:?}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str) - } - - LpExtend(ref lp, _, LpDeref(_)) => { - write!(f, "{:?}.*", lp) - } - - LpExtend(ref lp, _, LpInterior(_, ref interior)) => { - write!(f, "{:?}.{:?}", lp, interior) - } - } - } -} - -impl<'tcx> fmt::Display for LoanPath<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - LpVar(id) => { - write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().hir_to_user_string(id))) - } - - LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath { hir_id }, closure_expr_id: _ }) => { - let s = ty::tls::with(|tcx| { - tcx.hir().node_to_string(hir_id) - }); - write!(f, "$({} captured by closure)", s) - } - - LpDowncast(ref lp, variant_def_id) => { - let variant_str = if variant_def_id.is_local() { - ty::tls::with(|tcx| tcx.def_path_str(variant_def_id)) - } else { - format!("{:?}", variant_def_id) - }; - write!(f, "({}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str) - } - - LpExtend(ref lp, _, LpDeref(_)) => { - write!(f, "{}.*", lp) - } - - LpExtend(ref lp, _, LpInterior(_, ref interior)) => { - write!(f, "{}.{:?}", lp, interior) - } - } - } -} diff --git a/src/librustc_ast_borrowck/borrowck/move_data.rs b/src/librustc_ast_borrowck/borrowck/move_data.rs deleted file mode 100644 index 6bc42348bcf6c..0000000000000 --- a/src/librustc_ast_borrowck/borrowck/move_data.rs +++ /dev/null @@ -1,730 +0,0 @@ -//! Data structures used for tracking moves. Please see the extensive -//! comments in the section "Moves and initialization" in `README.md`. - -use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom}; - -use crate::borrowck::*; -use crate::cfg; -use rustc::ty::{self, TyCtxt}; -use rustc::util::nodemap::FxHashMap; - -use std::cell::RefCell; -use std::rc::Rc; -use std::usize; -use syntax_pos::Span; -use rustc::hir; -use log::debug; - -#[derive(Default)] -pub struct MoveData<'tcx> { - /// Move paths. See section "Move paths" in `README.md`. - pub paths: RefCell>>, - - /// Cache of loan path to move path index, for easy lookup. - pub path_map: RefCell>, MovePathIndex>>, - - /// Each move or uninitialized variable gets an entry here. - pub moves: RefCell>, - - /// Assignments to a variable, like `x = foo`. These are assigned - /// bits for dataflow, since we must track them to ensure that - /// immutable variables are assigned at most once along each path. - pub var_assignments: RefCell>, - - /// Assignments to a path, like `x.f = foo`. These are not - /// assigned dataflow bits, but we track them because they still - /// kill move bits. - pub path_assignments: RefCell>, -} - -pub struct FlowedMoveData<'tcx> { - pub move_data: MoveData<'tcx>, - - pub dfcx_moves: MoveDataFlow<'tcx>, - - // We could (and maybe should, for efficiency) combine both move - // and assign data flow into one, but this way it's easier to - // distinguish the bits that correspond to moves and assignments. - pub dfcx_assign: AssignDataFlow<'tcx>, -} - -/// Index into `MoveData.paths`, used like a pointer -#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct MovePathIndex(usize); - -impl MovePathIndex { - fn get(&self) -> usize { - let MovePathIndex(v) = *self; v - } -} - -impl Clone for MovePathIndex { - fn clone(&self) -> MovePathIndex { - MovePathIndex(self.get()) - } -} - -#[allow(non_upper_case_globals)] -const InvalidMovePathIndex: MovePathIndex = MovePathIndex(usize::MAX); - -/// Index into `MoveData.moves`, used like a pointer -#[derive(Copy, Clone, PartialEq)] -pub struct MoveIndex(usize); - -impl MoveIndex { - fn get(&self) -> usize { - let MoveIndex(v) = *self; v - } -} - -#[allow(non_upper_case_globals)] -const InvalidMoveIndex: MoveIndex = MoveIndex(usize::MAX); - -pub struct MovePath<'tcx> { - /// Loan path corresponding to this move path - pub loan_path: Rc>, - - /// Parent pointer, `InvalidMovePathIndex` if root - pub parent: MovePathIndex, - - /// Head of linked list of moves to this path, - /// `InvalidMoveIndex` if not moved - pub first_move: MoveIndex, - - /// First node in linked list of children, `InvalidMovePathIndex` if leaf - pub first_child: MovePathIndex, - - /// Next node in linked list of parent's children (siblings), - /// `InvalidMovePathIndex` if none. - pub next_sibling: MovePathIndex, -} - - -#[derive(Copy, Clone)] -pub struct Move { - /// Path being moved. - pub path: MovePathIndex, - - /// ID of node that is doing the move. - pub id: hir::ItemLocalId, - - /// Next node in linked list of moves from `path`, or `InvalidMoveIndex` - pub next_move: MoveIndex -} - -#[derive(Copy, Clone)] -pub struct Assignment { - /// Path being assigned. - pub path: MovePathIndex, - - /// ID where assignment occurs - pub id: hir::ItemLocalId, - - /// span of node where assignment occurs - pub span: Span, -} - -#[derive(Clone, Copy)] -pub struct MoveDataFlowOperator; - -pub type MoveDataFlow<'tcx> = DataFlowContext<'tcx, MoveDataFlowOperator>; - -#[derive(Clone, Copy)] -pub struct AssignDataFlowOperator; - -pub type AssignDataFlow<'tcx> = DataFlowContext<'tcx, AssignDataFlowOperator>; - -fn loan_path_is_precise(loan_path: &LoanPath<'_>) -> bool { - match loan_path.kind { - LpVar(_) | LpUpvar(_) => { - true - } - LpExtend(.., LpInterior(_, InteriorKind::InteriorElement)) => { - // Paths involving element accesses a[i] do not refer to a unique - // location, as there is no accurate tracking of the indices. - // - // (Paths involving element accesses via slice pattern bindings - // can in principle be tracked precisely, but that is future - // work. For now, continue claiming that they are imprecise.) - false - } - LpDowncast(ref lp_base, _) | - LpExtend(ref lp_base, ..) => { - loan_path_is_precise(&lp_base) - } - } -} - -impl MoveData<'tcx> { - /// Returns `true` if there are no trackable assignments or moves - /// in this move data -- that means that there is nothing that - /// could cause a borrow error. - pub fn is_empty(&self) -> bool { - self.moves.borrow().is_empty() && - self.path_assignments.borrow().is_empty() && - self.var_assignments.borrow().is_empty() - } - - pub fn path_loan_path(&self, index: MovePathIndex) -> Rc> { - (*self.paths.borrow())[index.get()].loan_path.clone() - } - - fn path_parent(&self, index: MovePathIndex) -> MovePathIndex { - (*self.paths.borrow())[index.get()].parent - } - - fn path_first_move(&self, index: MovePathIndex) -> MoveIndex { - (*self.paths.borrow())[index.get()].first_move - } - - /// Returns the index of first child, or `InvalidMovePathIndex` if - /// `index` is leaf. - fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex { - (*self.paths.borrow())[index.get()].first_child - } - - fn path_next_sibling(&self, index: MovePathIndex) -> MovePathIndex { - (*self.paths.borrow())[index.get()].next_sibling - } - - fn set_path_first_move(&self, - index: MovePathIndex, - first_move: MoveIndex) { - (*self.paths.borrow_mut())[index.get()].first_move = first_move - } - - fn set_path_first_child(&self, - index: MovePathIndex, - first_child: MovePathIndex) { - (*self.paths.borrow_mut())[index.get()].first_child = first_child - } - - fn move_next_move(&self, index: MoveIndex) -> MoveIndex { - //! Type safe indexing operator - (*self.moves.borrow())[index.get()].next_move - } - - fn is_var_path(&self, index: MovePathIndex) -> bool { - //! True if `index` refers to a variable - self.path_parent(index) == InvalidMovePathIndex - } - - /// Returns the existing move path index for `lp`, if any, and otherwise adds a new index for - /// `lp` and any of its base paths that do not yet have an index. - pub fn move_path(&self, tcx: TyCtxt<'tcx>, lp: Rc>) -> MovePathIndex { - if let Some(&index) = self.path_map.borrow().get(&lp) { - return index; - } - - let index = match lp.kind { - LpVar(..) | LpUpvar(..) => { - let index = MovePathIndex(self.paths.borrow().len()); - - self.paths.borrow_mut().push(MovePath { - loan_path: lp.clone(), - parent: InvalidMovePathIndex, - first_move: InvalidMoveIndex, - first_child: InvalidMovePathIndex, - next_sibling: InvalidMovePathIndex, - }); - - index - } - - LpDowncast(ref base, _) | - LpExtend(ref base, ..) => { - let parent_index = self.move_path(tcx, base.clone()); - - let index = MovePathIndex(self.paths.borrow().len()); - - let next_sibling = self.path_first_child(parent_index); - self.set_path_first_child(parent_index, index); - - self.paths.borrow_mut().push(MovePath { - loan_path: lp.clone(), - parent: parent_index, - first_move: InvalidMoveIndex, - first_child: InvalidMovePathIndex, - next_sibling, - }); - - index - } - }; - - debug!("move_path(lp={:?}, index={:?})", - lp, - index); - - assert_eq!(index.get(), self.paths.borrow().len() - 1); - self.path_map.borrow_mut().insert(lp, index); - return index; - } - - fn existing_move_path(&self, lp: &Rc>) - -> Option { - self.path_map.borrow().get(lp).cloned() - } - - fn existing_base_paths(&self, lp: &Rc>) - -> Vec { - let mut result = vec![]; - self.add_existing_base_paths(lp, &mut result); - result - } - - /// Adds any existing move path indices for `lp` and any base paths of `lp` to `result`, but - /// does not add new move paths - fn add_existing_base_paths(&self, lp: &Rc>, - result: &mut Vec) { - match self.path_map.borrow().get(lp).cloned() { - Some(index) => { - self.each_base_path(index, |p| { - result.push(p); - true - }); - } - None => { - match lp.kind { - LpVar(..) | LpUpvar(..) => { } - LpDowncast(ref b, _) | - LpExtend(ref b, ..) => { - self.add_existing_base_paths(b, result); - } - } - } - } - - } - - /// Adds a new move entry for a move of `lp` that occurs at location `id` with kind `kind`. - pub fn add_move( - &self, - tcx: TyCtxt<'tcx>, - orig_lp: Rc>, - id: hir::ItemLocalId, - ) { - // Moving one union field automatically moves all its fields. Also move siblings of - // all parent union fields, moves do not propagate upwards automatically. - let mut lp = orig_lp.clone(); - while let LpExtend(ref base_lp, mutbl, lp_elem) = lp.clone().kind { - if let (&ty::Adt(adt_def, _), LpInterior(opt_variant_id, interior)) - = (&base_lp.ty.kind, lp_elem) { - if adt_def.is_union() { - for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() { - let field = - InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name)); - if field != interior { - let sibling_lp_kind = - LpExtend(base_lp.clone(), mutbl, LpInterior(opt_variant_id, field)); - let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, tcx.types.err)); - self.add_move_helper(tcx, sibling_lp, id); - } - } - } - } - lp = base_lp.clone(); - } - - self.add_move_helper(tcx, orig_lp, id); - } - - fn add_move_helper( - &self, - tcx: TyCtxt<'tcx>, - lp: Rc>, - id: hir::ItemLocalId, - ) { - debug!("add_move(lp={:?}, id={:?})", lp, id); - - let path_index = self.move_path(tcx, lp); - let move_index = MoveIndex(self.moves.borrow().len()); - - let next_move = self.path_first_move(path_index); - self.set_path_first_move(path_index, move_index); - - self.moves.borrow_mut().push(Move { - path: path_index, - id, - next_move, - }); - } - - /// Adds a new record for an assignment to `lp` that occurs at location `id` with the given - /// `span`. - pub fn add_assignment( - &self, - tcx: TyCtxt<'tcx>, - lp: Rc>, - assign_id: hir::ItemLocalId, - span: Span, - ) { - // Assigning to one union field automatically assigns to all its fields. - if let LpExtend(ref base_lp, mutbl, LpInterior(opt_variant_id, interior)) = lp.kind { - if let ty::Adt(adt_def, _) = base_lp.ty.kind { - if adt_def.is_union() { - for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() { - let field = - InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name)); - let field_ty = if field == interior { - lp.ty - } else { - tcx.types.err // Doesn't matter - }; - let sibling_lp_kind = LpExtend(base_lp.clone(), mutbl, - LpInterior(opt_variant_id, field)); - let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty)); - self.add_assignment_helper(tcx, sibling_lp, assign_id, - span); - } - return; - } - } - } - - self.add_assignment_helper(tcx, lp, assign_id, span); - } - - fn add_assignment_helper( - &self, - tcx: TyCtxt<'tcx>, - lp: Rc>, - assign_id: hir::ItemLocalId, - span: Span, - ) { - debug!("add_assignment(lp={:?}, assign_id={:?}", lp, assign_id); - - let path_index = self.move_path(tcx, lp.clone()); - - let assignment = Assignment { - path: path_index, - id: assign_id, - span, - }; - - if self.is_var_path(path_index) { - debug!("add_assignment[var](lp={:?}, assignment={}, path_index={:?})", - lp, self.var_assignments.borrow().len(), path_index); - - self.var_assignments.borrow_mut().push(assignment); - } else { - debug!("add_assignment[path](lp={:?}, path_index={:?})", - lp, path_index); - - self.path_assignments.borrow_mut().push(assignment); - } - } - - /// Adds the gen/kills for the various moves and - /// assignments into the provided data flow contexts. - /// Moves are generated by moves and killed by assignments and - /// scoping. Assignments are generated by assignment to variables and - /// killed by scoping. See `README.md` for more details. - fn add_gen_kills( - &self, - bccx: &BorrowckCtxt<'_, 'tcx>, - dfcx_moves: &mut MoveDataFlow<'_>, - dfcx_assign: &mut AssignDataFlow<'_>, - ) { - for (i, the_move) in self.moves.borrow().iter().enumerate() { - dfcx_moves.add_gen(the_move.id, i); - } - - for (i, assignment) in self.var_assignments.borrow().iter().enumerate() { - dfcx_assign.add_gen(assignment.id, i); - self.kill_moves(assignment.path, assignment.id, - KillFrom::Execution, dfcx_moves); - } - - for assignment in self.path_assignments.borrow().iter() { - self.kill_moves(assignment.path, assignment.id, - KillFrom::Execution, dfcx_moves); - } - - // Kill all moves related to a variable `x` when - // it goes out of scope: - for path in self.paths.borrow().iter() { - match path.loan_path.kind { - LpVar(..) | LpUpvar(..) | LpDowncast(..) => { - let kill_scope = path.loan_path.kill_scope(bccx); - let path = *self.path_map.borrow().get(&path.loan_path).unwrap(); - self.kill_moves(path, kill_scope.item_local_id(), - KillFrom::ScopeEnd, dfcx_moves); - } - LpExtend(..) => {} - } - } - - // Kill all assignments when the variable goes out of scope: - for (assignment_index, assignment) in - self.var_assignments.borrow().iter().enumerate() { - let lp = self.path_loan_path(assignment.path); - match lp.kind { - LpVar(..) | LpUpvar(..) | LpDowncast(..) => { - let kill_scope = lp.kill_scope(bccx); - dfcx_assign.add_kill(KillFrom::ScopeEnd, - kill_scope.item_local_id(), - assignment_index); - } - LpExtend(..) => { - bug!("var assignment for non var path"); - } - } - } - } - - fn each_base_path(&self, index: MovePathIndex, mut f: F) -> bool where - F: FnMut(MovePathIndex) -> bool, - { - let mut p = index; - while p != InvalidMovePathIndex { - if !f(p) { - return false; - } - p = self.path_parent(p); - } - return true; - } - - // FIXME(#19596) This is a workaround, but there should be better way to do this - fn each_extending_path_(&self, index: MovePathIndex, f: &mut F) -> bool where - F: FnMut(MovePathIndex) -> bool, - { - if !(*f)(index) { - return false; - } - - let mut p = self.path_first_child(index); - while p != InvalidMovePathIndex { - if !self.each_extending_path_(p, f) { - return false; - } - p = self.path_next_sibling(p); - } - - return true; - } - - fn each_extending_path(&self, index: MovePathIndex, mut f: F) -> bool where - F: FnMut(MovePathIndex) -> bool, - { - self.each_extending_path_(index, &mut f) - } - - fn each_applicable_move(&self, index0: MovePathIndex, mut f: F) -> bool where - F: FnMut(MoveIndex) -> bool, - { - let mut ret = true; - self.each_extending_path(index0, |index| { - let mut p = self.path_first_move(index); - while p != InvalidMoveIndex { - if !f(p) { - ret = false; - break; - } - p = self.move_next_move(p); - } - ret - }); - ret - } - - fn kill_moves( - &self, - path: MovePathIndex, - kill_id: hir::ItemLocalId, - kill_kind: KillFrom, - dfcx_moves: &mut MoveDataFlow<'_>, - ) { - // We can only perform kills for paths that refer to a unique location, - // since otherwise we may kill a move from one location with an - // assignment referring to another location. - - let loan_path = self.path_loan_path(path); - if loan_path_is_precise(&loan_path) { - self.each_applicable_move(path, |move_index| { - debug!("kill_moves add_kill {:?} kill_id={:?} move_index={}", - kill_kind, kill_id, move_index.get()); - dfcx_moves.add_kill(kill_kind, kill_id, move_index.get()); - true - }); - } - } -} - -impl<'tcx> FlowedMoveData<'tcx> { - pub fn new( - move_data: MoveData<'tcx>, - bccx: &BorrowckCtxt<'_, 'tcx>, - cfg: &cfg::CFG, - body: &hir::Body, - ) -> FlowedMoveData<'tcx> { - let tcx = bccx.tcx; - - let mut dfcx_moves = - DataFlowContext::new(tcx, - "flowed_move_data_moves", - Some(body), - cfg, - MoveDataFlowOperator, - move_data.moves.borrow().len()); - let mut dfcx_assign = - DataFlowContext::new(tcx, - "flowed_move_data_assigns", - Some(body), - cfg, - AssignDataFlowOperator, - move_data.var_assignments.borrow().len()); - - move_data.add_gen_kills(bccx, - &mut dfcx_moves, - &mut dfcx_assign); - - dfcx_moves.add_kills_from_flow_exits(cfg); - dfcx_assign.add_kills_from_flow_exits(cfg); - - dfcx_moves.propagate(cfg, body); - dfcx_assign.propagate(cfg, body); - - FlowedMoveData { - move_data, - dfcx_moves, - dfcx_assign, - } - } - - pub fn is_move_path(&self, id: hir::ItemLocalId, loan_path: &Rc>) -> bool { - //! Returns the kind of a move of `loan_path` by `id`, if one exists. - - let mut ret = false; - if let Some(loan_path_index) = self.move_data.path_map.borrow().get(&*loan_path) { - self.dfcx_moves.each_gen_bit(id, |move_index| { - let the_move = self.move_data.moves.borrow(); - let the_move = (*the_move)[move_index]; - if the_move.path == *loan_path_index { - ret = true; - false - } else { - true - } - }); - } - ret - } - - /// Iterates through each move of `loan_path` (or some base path of `loan_path`) that *may* - /// have occurred on entry to `id` without an intervening assignment. In other words, any moves - /// that would invalidate a reference to `loan_path` at location `id`. - pub fn each_move_of(&self, - id: hir::ItemLocalId, - loan_path: &Rc>, - mut f: F) - -> bool where - F: FnMut(&Move, &LoanPath<'tcx>) -> bool, - { - // Bad scenarios: - // - // 1. Move of `a.b.c`, use of `a.b.c` - // 2. Move of `a.b.c`, use of `a.b.c.d` - // 3. Move of `a.b.c`, use of `a` or `a.b` - // - // OK scenario: - // - // 4. move of `a.b.c`, use of `a.b.d` - - let base_indices = self.move_data.existing_base_paths(loan_path); - if base_indices.is_empty() { - return true; - } - - let opt_loan_path_index = self.move_data.existing_move_path(loan_path); - - let mut ret = true; - - self.dfcx_moves.each_bit_on_entry(id, |index| { - let the_move = self.move_data.moves.borrow(); - let the_move = &(*the_move)[index]; - let moved_path = the_move.path; - if base_indices.iter().any(|x| x == &moved_path) { - // Scenario 1 or 2: `loan_path` or some base path of - // `loan_path` was moved. - if !f(the_move, &self.move_data.path_loan_path(moved_path)) { - ret = false; - } - } else { - if let Some(loan_path_index) = opt_loan_path_index { - let cont = self.move_data.each_base_path(moved_path, |p| { - if p == loan_path_index { - // Scenario 3: some extension of `loan_path` - // was moved - f(the_move, - &self.move_data.path_loan_path(moved_path)) - } else { - true - } - }); - if !cont { ret = false; } - } - } - ret - }) - } - - /// Iterates through every assignment to `loan_path` that may have occurred on entry to `id`. - /// `loan_path` must be a single variable. - pub fn each_assignment_of(&self, - id: hir::ItemLocalId, - loan_path: &Rc>, - mut f: F) - -> bool where - F: FnMut(&Assignment) -> bool, - { - let loan_path_index = { - match self.move_data.existing_move_path(loan_path) { - Some(i) => i, - None => { - // if there were any assignments, it'd have an index - return true; - } - } - }; - - self.dfcx_assign.each_bit_on_entry(id, |index| { - let assignment = self.move_data.var_assignments.borrow(); - let assignment = &(*assignment)[index]; - if assignment.path == loan_path_index && !f(assignment) { - false - } else { - true - } - }) - } -} - -impl BitwiseOperator for MoveDataFlowOperator { - #[inline] - fn join(&self, succ: usize, pred: usize) -> usize { - succ | pred // moves from both preds are in scope - } -} - -impl DataFlowOperator for MoveDataFlowOperator { - #[inline] - fn initial_value(&self) -> bool { - false // no loans in scope by default - } -} - -impl BitwiseOperator for AssignDataFlowOperator { - #[inline] - fn join(&self, succ: usize, pred: usize) -> usize { - succ | pred // moves from both preds are in scope - } -} - -impl DataFlowOperator for AssignDataFlowOperator { - #[inline] - fn initial_value(&self) -> bool { - false // no assignments in scope by default - } -} diff --git a/src/librustc_ast_borrowck/cfg/construct.rs b/src/librustc_ast_borrowck/cfg/construct.rs deleted file mode 100644 index ec7f40f8c9718..0000000000000 --- a/src/librustc_ast_borrowck/cfg/construct.rs +++ /dev/null @@ -1,545 +0,0 @@ -use crate::cfg::*; - -use rustc::hir::{self, PatKind}; -use rustc::hir::def_id::DefId; -use rustc::hir::ptr::P; -use rustc::middle::region; -use rustc::ty::{self, TyCtxt}; - -use rustc_data_structures::graph::implementation as graph; - -struct CFGBuilder<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - owner_def_id: DefId, - tables: &'a ty::TypeckTables<'tcx>, - graph: CFGGraph, - fn_exit: CFGIndex, - loop_scopes: Vec, - breakable_block_scopes: Vec, -} - -#[derive(Copy, Clone)] -struct BlockScope { - block_expr_id: hir::ItemLocalId, // ID of breakable block expr node - break_index: CFGIndex, // where to go on `break` -} - -#[derive(Copy, Clone)] -struct LoopScope { - loop_id: hir::ItemLocalId, // ID of `loop`/`while` node - continue_index: CFGIndex, // where to go on a `loop` - break_index: CFGIndex, // where to go on a `break` -} - -pub(super) fn construct(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG { - let mut graph = graph::Graph::new(); - let entry = graph.add_node(CFGNodeData::Entry); - - // `fn_exit` is target of return exprs, which lies somewhere - // outside input `body`. (Distinguishing `fn_exit` and `body_exit` - // also resolves chicken-and-egg problem that arises if you try to - // have return exprs jump to `body_exit` during construction.) - let fn_exit = graph.add_node(CFGNodeData::Exit); - let body_exit; - - // Find the tables for this body. - let owner_def_id = tcx.hir().body_owner_def_id(body.id()); - let tables = tcx.typeck_tables_of(owner_def_id); - - let mut cfg_builder = CFGBuilder { - tcx, - owner_def_id, - tables, - graph, - fn_exit, - loop_scopes: Vec::new(), - breakable_block_scopes: Vec::new(), - }; - body_exit = cfg_builder.expr(&body.value, entry); - cfg_builder.add_contained_edge(body_exit, fn_exit); - let CFGBuilder { graph, .. } = cfg_builder; - CFG { - owner_def_id, - graph, - entry, - exit: fn_exit, - } -} - -impl<'a, 'tcx> CFGBuilder<'a, 'tcx> { - fn block(&mut self, blk: &hir::Block, pred: CFGIndex) -> CFGIndex { - if blk.targeted_by_break { - let expr_exit = self.add_ast_node(blk.hir_id.local_id, &[]); - - self.breakable_block_scopes.push(BlockScope { - block_expr_id: blk.hir_id.local_id, - break_index: expr_exit, - }); - - let mut stmts_exit = pred; - for stmt in &blk.stmts { - stmts_exit = self.stmt(stmt, stmts_exit); - } - let blk_expr_exit = self.opt_expr(&blk.expr, stmts_exit); - self.add_contained_edge(blk_expr_exit, expr_exit); - - self.breakable_block_scopes.pop(); - - expr_exit - } else { - let mut stmts_exit = pred; - for stmt in &blk.stmts { - stmts_exit = self.stmt(stmt, stmts_exit); - } - - let expr_exit = self.opt_expr(&blk.expr, stmts_exit); - - self.add_ast_node(blk.hir_id.local_id, &[expr_exit]) - } - } - - fn stmt(&mut self, stmt: &hir::Stmt, pred: CFGIndex) -> CFGIndex { - let exit = match stmt.kind { - hir::StmtKind::Local(ref local) => { - let init_exit = self.opt_expr(&local.init, pred); - self.pat(&local.pat, init_exit) - } - hir::StmtKind::Item(_) => pred, - hir::StmtKind::Expr(ref expr) | - hir::StmtKind::Semi(ref expr) => { - self.expr(&expr, pred) - } - }; - self.add_ast_node(stmt.hir_id.local_id, &[exit]) - } - - fn pat(&mut self, pat: &hir::Pat, pred: CFGIndex) -> CFGIndex { - match pat.kind { - PatKind::Binding(.., None) | - PatKind::Path(_) | - PatKind::Lit(..) | - PatKind::Range(..) | - PatKind::Wild => self.add_ast_node(pat.hir_id.local_id, &[pred]), - - PatKind::Box(ref subpat) | - PatKind::Ref(ref subpat, _) | - PatKind::Binding(.., Some(ref subpat)) => { - let subpat_exit = self.pat(&subpat, pred); - self.add_ast_node(pat.hir_id.local_id, &[subpat_exit]) - } - - PatKind::TupleStruct(_, ref subpats, _) | - PatKind::Tuple(ref subpats, _) => { - let pats_exit = self.pats_all(subpats.iter(), pred); - self.add_ast_node(pat.hir_id.local_id, &[pats_exit]) - } - - PatKind::Struct(_, ref subpats, _) => { - let pats_exit = self.pats_all(subpats.iter().map(|f| &f.pat), pred); - self.add_ast_node(pat.hir_id.local_id, &[pats_exit]) - } - - PatKind::Or(ref pats) => { - let branches: Vec<_> = pats.iter().map(|p| self.pat(p, pred)).collect(); - self.add_ast_node(pat.hir_id.local_id, &branches) - } - - PatKind::Slice(ref pre, ref vec, ref post) => { - let pre_exit = self.pats_all(pre.iter(), pred); - let vec_exit = self.pats_all(vec.iter(), pre_exit); - let post_exit = self.pats_all(post.iter(), vec_exit); - self.add_ast_node(pat.hir_id.local_id, &[post_exit]) - } - } - } - - /// Handles case where all of the patterns must match. - fn pats_all<'b, I: Iterator>>( - &mut self, - pats: I, - pred: CFGIndex, - ) -> CFGIndex { - pats.fold(pred, |pred, pat| self.pat(&pat, pred)) - } - - fn expr(&mut self, expr: &hir::Expr, pred: CFGIndex) -> CFGIndex { - match expr.kind { - hir::ExprKind::Block(ref blk, _) => { - let blk_exit = self.block(&blk, pred); - self.add_ast_node(expr.hir_id.local_id, &[blk_exit]) - } - - hir::ExprKind::Loop(ref body, _, _) => { - // - // [pred] - // | - // v 1 - // [loopback] <---+ - // | 4 | - // v 3 | - // [body] ------+ - // - // [expr] 2 - // - // Note that `break` and `loop` statements - // may cause additional edges. - - let loopback = self.add_dummy_node(&[pred]); // 1 - let expr_exit = self.add_ast_node(expr.hir_id.local_id, &[]); // 2 - self.loop_scopes.push(LoopScope { - loop_id: expr.hir_id.local_id, - continue_index: loopback, - break_index: expr_exit, - }); - let body_exit = self.block(&body, loopback); // 3 - self.add_contained_edge(body_exit, loopback); // 4 - self.loop_scopes.pop(); - expr_exit - } - - hir::ExprKind::Match(ref discr, ref arms, _) => { - self.match_(expr.hir_id.local_id, &discr, &arms, pred) - } - - hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => { - // - // [pred] - // | - // v 1 - // [l] - // | - // / \ - // / \ - // v 2 * - // [r] | - // | | - // v 3 v 4 - // [..exit..] - // - let l_exit = self.expr(&l, pred); // 1 - let r_exit = self.expr(&r, l_exit); // 2 - self.add_ast_node(expr.hir_id.local_id, &[l_exit, r_exit]) // 3,4 - } - - hir::ExprKind::Ret(ref v) => { - let v_exit = self.opt_expr(v, pred); - let b = self.add_ast_node(expr.hir_id.local_id, &[v_exit]); - self.add_returning_edge(expr, b); - self.add_unreachable_node() - } - - hir::ExprKind::Break(destination, ref opt_expr) => { - let v = self.opt_expr(opt_expr, pred); - let (target_scope, break_dest) = - self.find_scope_edge(expr, destination, ScopeCfKind::Break); - let b = self.add_ast_node(expr.hir_id.local_id, &[v]); - self.add_exiting_edge(expr, b, target_scope, break_dest); - self.add_unreachable_node() - } - - hir::ExprKind::Continue(destination) => { - let (target_scope, cont_dest) = - self.find_scope_edge(expr, destination, ScopeCfKind::Continue); - let a = self.add_ast_node(expr.hir_id.local_id, &[pred]); - self.add_exiting_edge(expr, a, target_scope, cont_dest); - self.add_unreachable_node() - } - - hir::ExprKind::Array(ref elems) => { - self.straightline(expr, pred, elems.iter().map(|e| &*e)) - } - - hir::ExprKind::Call(ref func, ref args) => { - self.call(expr, pred, &func, args.iter().map(|e| &*e)) - } - - hir::ExprKind::MethodCall(.., ref args) => { - self.call(expr, pred, &args[0], args[1..].iter().map(|e| &*e)) - } - - hir::ExprKind::Index(ref l, ref r) | - hir::ExprKind::Binary(_, ref l, ref r) if self.tables.is_method_call(expr) => { - self.call(expr, pred, &l, Some(&**r).into_iter()) - } - - hir::ExprKind::Unary(_, ref e) if self.tables.is_method_call(expr) => { - self.call(expr, pred, &e, None::.iter()) - } - - hir::ExprKind::Tup(ref exprs) => { - self.straightline(expr, pred, exprs.iter().map(|e| &*e)) - } - - hir::ExprKind::Struct(_, ref fields, ref base) => { - let field_cfg = self.straightline(expr, pred, fields.iter().map(|f| &*f.expr)); - self.opt_expr(base, field_cfg) - } - - hir::ExprKind::Assign(ref l, ref r) | - hir::ExprKind::AssignOp(_, ref l, ref r) => { - self.straightline(expr, pred, [r, l].iter().map(|&e| &**e)) - } - - hir::ExprKind::Index(ref l, ref r) | - hir::ExprKind::Binary(_, ref l, ref r) => { // N.B., && and || handled earlier - self.straightline(expr, pred, [l, r].iter().map(|&e| &**e)) - } - - hir::ExprKind::Box(ref e) | - hir::ExprKind::AddrOf(_, ref e) | - hir::ExprKind::Cast(ref e, _) | - hir::ExprKind::Type(ref e, _) | - hir::ExprKind::DropTemps(ref e) | - hir::ExprKind::Unary(_, ref e) | - hir::ExprKind::Field(ref e, _) | - hir::ExprKind::Yield(ref e, _) | - hir::ExprKind::Repeat(ref e, _) => { - self.straightline(expr, pred, Some(&**e).into_iter()) - } - - hir::ExprKind::InlineAsm(_, ref outputs, ref inputs) => { - let post_outputs = self.exprs(outputs.iter().map(|e| &*e), pred); - let post_inputs = self.exprs(inputs.iter().map(|e| &*e), post_outputs); - self.add_ast_node(expr.hir_id.local_id, &[post_inputs]) - } - - hir::ExprKind::Closure(..) | - hir::ExprKind::Lit(..) | - hir::ExprKind::Path(_) | - hir::ExprKind::Err => { - self.straightline(expr, pred, None::.iter()) - } - } - } - - fn call<'b, I: Iterator>( - &mut self, - call_expr: &hir::Expr, - pred: CFGIndex, - func_or_rcvr: &hir::Expr, - args: I, - ) -> CFGIndex { - let func_or_rcvr_exit = self.expr(func_or_rcvr, pred); - let ret = self.straightline(call_expr, func_or_rcvr_exit, args); - let m = self.tcx.hir().get_module_parent(call_expr.hir_id); - if self.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(call_expr)) { - self.add_unreachable_node() - } else { - ret - } - } - - /// Constructs graph for `exprs` evaluated in order. - fn exprs<'b, I: Iterator>( - &mut self, - exprs: I, - pred: CFGIndex, - ) -> CFGIndex { - exprs.fold(pred, |p, e| self.expr(e, p)) - } - - /// Constructs graph for `opt_expr` evaluated, if `Some`. - fn opt_expr( - &mut self, - opt_expr: &Option>, - pred: CFGIndex, - ) -> CFGIndex { - opt_expr.iter().fold(pred, |p, e| self.expr(&e, p)) - } - - /// Handles case of an expression that evaluates `subexprs` in order. - fn straightline<'b, I: Iterator>( - &mut self, - expr: &hir::Expr, - pred: CFGIndex, - subexprs: I, - ) -> CFGIndex { - let subexprs_exit = self.exprs(subexprs, pred); - self.add_ast_node(expr.hir_id.local_id, &[subexprs_exit]) - } - - fn match_(&mut self, id: hir::ItemLocalId, discr: &hir::Expr, - arms: &[hir::Arm], pred: CFGIndex) -> CFGIndex { - // The CFG for match expressions is quite complex, so no ASCII - // art for it (yet). - // - // The CFG generated below matches roughly what MIR contains. - // Each pattern and guard is visited in parallel, with - // arms containing multiple patterns generating multiple nodes - // for the same guard expression. The guard expressions chain - // into each other from top to bottom, with a specific - // exception to allow some additional valid programs - // (explained below). MIR differs slightly in that the - // pattern matching may continue after a guard but the visible - // behaviour should be the same. - // - // What is going on is explained in further comments. - - // Visit the discriminant expression. - let discr_exit = self.expr(discr, pred); - - // Add a node for the exit of the match expression as a whole. - let expr_exit = self.add_ast_node(id, &[]); - - // Keep track of the previous guard expressions. - let mut prev_guard = None; - let match_scope = region::Scope { id, data: region::ScopeData::Node }; - - for arm in arms { - // Add an exit node for when we've visited all the - // patterns and the guard (if there is one) in the arm. - let bindings_exit = self.add_dummy_node(&[]); - - for pat in arm.top_pats_hack() { - // Visit the pattern, coming from the discriminant exit - let mut pat_exit = self.pat(&pat, discr_exit); - - // If there is a guard expression, handle it here. - if let Some(ref guard) = arm.guard { - // Add a dummy node for the previous guard - // expression to target. - let guard_start = self.add_dummy_node(&[pat_exit]); - // Visit the guard expression. - let guard_exit = match guard { - hir::Guard::If(ref e) => (&**e, self.expr(e, guard_start)), - }; - // #47295: We used to have very special case code - // here for when a pair of arms are both formed - // solely from constants, and if so, not add these - // edges. But this was not actually sound without - // other constraints that we stopped enforcing at - // some point. - if let Some((prev_guard, prev_index)) = prev_guard.take() { - self.add_exiting_edge(prev_guard, prev_index, match_scope, guard_start); - } - - // Push the guard onto the list of previous guards. - prev_guard = Some(guard_exit); - - // Update the exit node for the pattern. - pat_exit = guard_exit.1; - } - - // Add an edge from the exit of this pattern to the exit of the arm. - self.add_contained_edge(pat_exit, bindings_exit); - } - - // Visit the body of this arm. - let body_exit = self.expr(&arm.body, bindings_exit); - - let arm_exit = self.add_ast_node(arm.hir_id.local_id, &[body_exit]); - - // Link the body to the exit of the expression. - self.add_contained_edge(arm_exit, expr_exit); - } - - expr_exit - } - - fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex { - self.add_node(CFGNodeData::Dummy, preds) - } - - fn add_ast_node(&mut self, id: hir::ItemLocalId, preds: &[CFGIndex]) -> CFGIndex { - self.add_node(CFGNodeData::AST(id), preds) - } - - fn add_unreachable_node(&mut self) -> CFGIndex { - self.add_node(CFGNodeData::Unreachable, &[]) - } - - fn add_node(&mut self, data: CFGNodeData, preds: &[CFGIndex]) -> CFGIndex { - let node = self.graph.add_node(data); - for &pred in preds { - self.add_contained_edge(pred, node); - } - node - } - - fn add_contained_edge( - &mut self, - source: CFGIndex, - target: CFGIndex, - ) { - let data = CFGEdgeData {exiting_scopes: vec![] }; - self.graph.add_edge(source, target, data); - } - - fn add_exiting_edge( - &mut self, - from_expr: &hir::Expr, - from_index: CFGIndex, - target_scope: region::Scope, - to_index: CFGIndex, - ) { - let mut data = CFGEdgeData { exiting_scopes: vec![] }; - let mut scope = region::Scope { - id: from_expr.hir_id.local_id, - data: region::ScopeData::Node - }; - let region_scope_tree = self.tcx.region_scope_tree(self.owner_def_id); - while scope != target_scope { - data.exiting_scopes.push(scope.item_local_id()); - scope = region_scope_tree.encl_scope(scope); - } - self.graph.add_edge(from_index, to_index, data); - } - - fn add_returning_edge( - &mut self, - _from_expr: &hir::Expr, - from_index: CFGIndex, - ) { - let data = CFGEdgeData { - exiting_scopes: self.loop_scopes.iter() - .rev() - .map(|&LoopScope { loop_id: id, .. }| id) - .collect() - }; - self.graph.add_edge(from_index, self.fn_exit, data); - } - - fn find_scope_edge( - &self, - expr: &hir::Expr, - destination: hir::Destination, - scope_cf_kind: ScopeCfKind, - ) -> (region::Scope, CFGIndex) { - match destination.target_id { - Ok(loop_id) => { - for b in &self.breakable_block_scopes { - if b.block_expr_id == loop_id.local_id { - let scope = region::Scope { - id: loop_id.local_id, - data: region::ScopeData::Node - }; - return (scope, match scope_cf_kind { - ScopeCfKind::Break => b.break_index, - ScopeCfKind::Continue => bug!("can't continue to block"), - }); - } - } - for l in &self.loop_scopes { - if l.loop_id == loop_id.local_id { - let scope = region::Scope { - id: loop_id.local_id, - data: region::ScopeData::Node - }; - return (scope, match scope_cf_kind { - ScopeCfKind::Break => l.break_index, - ScopeCfKind::Continue => l.continue_index, - }); - } - } - span_bug!(expr.span, "no scope for ID {}", loop_id); - } - Err(err) => span_bug!(expr.span, "scope error: {}", err), - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq)] -enum ScopeCfKind { - Break, - Continue, -} diff --git a/src/librustc_ast_borrowck/cfg/graphviz.rs b/src/librustc_ast_borrowck/cfg/graphviz.rs deleted file mode 100644 index 99c6b49cad5d9..0000000000000 --- a/src/librustc_ast_borrowck/cfg/graphviz.rs +++ /dev/null @@ -1,119 +0,0 @@ -/// This module provides linkage between `rustc::middle::graph` and -/// libgraphviz traits. - -use crate::cfg; -use rustc::hir; -use rustc::ty::TyCtxt; - -pub(crate) type Node<'a> = (cfg::CFGIndex, &'a cfg::CFGNode); -pub(crate) type Edge<'a> = &'a cfg::CFGEdge; - -pub struct LabelledCFG<'a, 'tcx> { - pub tcx: TyCtxt<'tcx>, - pub cfg: &'a cfg::CFG, - pub name: String, - /// `labelled_edges` controls whether we emit labels on the edges. - pub labelled_edges: bool, -} - -impl<'a, 'tcx> LabelledCFG<'a, 'tcx> { - fn local_id_to_string(&self, local_id: hir::ItemLocalId) -> String { - assert!(self.cfg.owner_def_id.is_local()); - let hir_id = hir::HirId { - owner: self.tcx.hir().def_index_to_hir_id(self.cfg.owner_def_id.index).owner, - local_id - }; - let s = self.tcx.hir().node_to_string(hir_id); - - // Replacing newlines with `\\l` causes each line to be left-aligned, - // improving presentation of (long) pretty-printed expressions. - if s.contains("\n") { - let mut s = s.replace("\n", "\\l"); - // Apparently left-alignment applies to the line that precedes - // `\l`, not the line that follows; so, add `\l` at end of string - // if not already present, ensuring last line gets left-aligned - // as well. - let mut last_two: Vec<_> = - s.chars().rev().take(2).collect(); - last_two.reverse(); - if last_two != ['\\', 'l'] { - s.push_str("\\l"); - } - s - } else { - s - } - } -} - -impl<'a, 'hir> dot::Labeller<'a> for LabelledCFG<'a, 'hir> { - type Node = Node<'a>; - type Edge = Edge<'a>; - fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(&self.name[..]).unwrap() } - - fn node_id(&'a self, &(i,_): &Node<'a>) -> dot::Id<'a> { - dot::Id::new(format!("N{}", i.node_id())).unwrap() - } - - fn node_label(&'a self, &(i, n): &Node<'a>) -> dot::LabelText<'a> { - if i == self.cfg.entry { - dot::LabelText::LabelStr("entry".into()) - } else if i == self.cfg.exit { - dot::LabelText::LabelStr("exit".into()) - } else if n.data.id() == hir::DUMMY_ITEM_LOCAL_ID { - dot::LabelText::LabelStr("(dummy_node)".into()) - } else { - let s = self.local_id_to_string(n.data.id()); - dot::LabelText::EscStr(s.into()) - } - } - - fn edge_label(&self, e: &Edge<'a>) -> dot::LabelText<'a> { - let mut label = String::new(); - if !self.labelled_edges { - return dot::LabelText::EscStr(label.into()); - } - let mut put_one = false; - for (i, &id) in e.data.exiting_scopes.iter().enumerate() { - if put_one { - label.push_str(",\\l"); - } else { - put_one = true; - } - let s = self.local_id_to_string(id); - label.push_str(&format!("exiting scope_{} {}", - i, - &s[..])); - } - dot::LabelText::EscStr(label.into()) - } -} - -impl<'a> dot::GraphWalk<'a> for &'a cfg::CFG { - type Node = Node<'a>; - type Edge = Edge<'a>; - fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { - let v: Vec<_> = self.graph.enumerated_nodes().collect(); - v.into() - } - fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { - self.graph.all_edges().iter().collect() - } - fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { - let i = edge.source(); - (i, self.graph.node(i)) - } - fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { - let i = edge.target(); - (i, self.graph.node(i)) - } -} - -impl<'a, 'hir> dot::GraphWalk<'a> for LabelledCFG<'a, 'hir> { - type Node = Node<'a>; - type Edge = Edge<'a>; - fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.cfg.nodes() } - fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.cfg.edges() } - fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) } - fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) } -} diff --git a/src/librustc_ast_borrowck/cfg/mod.rs b/src/librustc_ast_borrowck/cfg/mod.rs deleted file mode 100644 index 981199c91d513..0000000000000 --- a/src/librustc_ast_borrowck/cfg/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Module that constructs a control-flow graph representing an item. -//! Uses `Graph` as the underlying representation. - -use rustc_data_structures::graph::implementation as graph; -use rustc::ty::TyCtxt; -use rustc::hir; -use rustc::hir::def_id::DefId; - -mod construct; -pub mod graphviz; - -pub struct CFG { - owner_def_id: DefId, - pub(crate) graph: CFGGraph, - pub(crate) entry: CFGIndex, - exit: CFGIndex, -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum CFGNodeData { - AST(hir::ItemLocalId), - Entry, - Exit, - Dummy, - Unreachable, -} - -impl CFGNodeData { - pub(crate) fn id(&self) -> hir::ItemLocalId { - if let CFGNodeData::AST(id) = *self { - id - } else { - hir::DUMMY_ITEM_LOCAL_ID - } - } -} - -#[derive(Debug)] -pub struct CFGEdgeData { - pub(crate) exiting_scopes: Vec -} - -pub(crate) type CFGIndex = graph::NodeIndex; - -pub(crate) type CFGGraph = graph::Graph; - -pub(crate) type CFGNode = graph::Node; - -pub(crate) type CFGEdge = graph::Edge; - -impl CFG { - pub fn new(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG { - construct::construct(tcx, body) - } -} diff --git a/src/librustc_ast_borrowck/dataflow.rs b/src/librustc_ast_borrowck/dataflow.rs deleted file mode 100644 index a8562901d99c5..0000000000000 --- a/src/librustc_ast_borrowck/dataflow.rs +++ /dev/null @@ -1,672 +0,0 @@ -//! A module for propagating forward dataflow information. The analysis -//! assumes that the items to be propagated can be represented as bits -//! and thus uses bitvectors. Your job is simply to specify the so-called -//! GEN and KILL bits for each expression. - -use crate::cfg::{self, CFGIndex}; -use std::mem; -use std::usize; -use log::debug; - -use rustc_data_structures::graph::implementation::OUTGOING; - -use rustc::util::nodemap::FxHashMap; -use rustc::hir; -use rustc::hir::intravisit; -use rustc::hir::print as pprust; -use rustc::ty::TyCtxt; - -#[derive(Copy, Clone, Debug)] -pub enum EntryOrExit { - Entry, - Exit, -} - -#[derive(Clone)] -pub struct DataFlowContext<'tcx, O> { - tcx: TyCtxt<'tcx>, - - /// a name for the analysis using this dataflow instance - analysis_name: &'static str, - - /// the data flow operator - oper: O, - - /// number of bits to propagate per id - bits_per_id: usize, - - /// number of words we will use to store bits_per_id. - /// equal to bits_per_id/usize::BITS rounded up. - words_per_id: usize, - - // mapping from node to cfg node index - // FIXME (#6298): Shouldn't this go with CFG? - local_id_to_index: FxHashMap>, - - // Bit sets per cfg node. The following three fields (`gens`, `kills`, - // and `on_entry`) all have the same structure. For each id in - // `id_range`, there is a range of words equal to `words_per_id`. - // So, to access the bits for any given id, you take a slice of - // the full vector (see the method `compute_id_range()`). - /// bits generated as we exit the cfg node. Updated by `add_gen()`. - gens: Vec, - - /// bits killed as we exit the cfg node, or non-locally jump over - /// it. Updated by `add_kill(KillFrom::ScopeEnd)`. - scope_kills: Vec, - - /// bits killed as we exit the cfg node directly; if it is jumped - /// over, e.g., via `break`, the kills are not reflected in the - /// jump's effects. Updated by `add_kill(KillFrom::Execution)`. - action_kills: Vec, - - /// bits that are valid on entry to the cfg node. Updated by - /// `propagate()`. - on_entry: Vec, -} - -pub trait BitwiseOperator { - /// Joins two predecessor bits together, typically either `|` or `&` - fn join(&self, succ: usize, pred: usize) -> usize; -} - -/// Parameterization for the precise form of data flow that is used. -pub trait DataFlowOperator : BitwiseOperator { - /// Specifies the initial value for each bit in the `on_entry` set - fn initial_value(&self) -> bool; -} - -struct PropagationContext<'a, 'tcx, O> { - dfcx: &'a mut DataFlowContext<'tcx, O>, - changed: bool, -} - -fn get_cfg_indices(id: hir::ItemLocalId, - index: &FxHashMap>) - -> &[CFGIndex] { - index.get(&id).map_or(&[], |v| &v[..]) -} - -impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> { - fn has_bitset_for_local_id(&self, n: hir::ItemLocalId) -> bool { - assert!(n != hir::DUMMY_ITEM_LOCAL_ID); - self.local_id_to_index.contains_key(&n) - } -} - -impl<'tcx, O: DataFlowOperator> pprust::PpAnn for DataFlowContext<'tcx, O> { - fn nested(&self, state: &mut pprust::State<'_>, nested: pprust::Nested) { - pprust::PpAnn::nested(self.tcx.hir(), state, nested) - } - fn pre(&self, - ps: &mut pprust::State<'_>, - node: pprust::AnnNode<'_>) { - let id = match node { - pprust::AnnNode::Name(_) => return, - pprust::AnnNode::Expr(expr) => expr.hir_id.local_id, - pprust::AnnNode::Block(blk) => blk.hir_id.local_id, - pprust::AnnNode::Item(_) | - pprust::AnnNode::SubItem(_) => return, - pprust::AnnNode::Pat(pat) => pat.hir_id.local_id, - pprust::AnnNode::Arm(arm) => arm.hir_id.local_id, - }; - - if !self.has_bitset_for_local_id(id) { - return; - } - - assert!(self.bits_per_id > 0); - let indices = get_cfg_indices(id, &self.local_id_to_index); - for &cfgidx in indices { - let (start, end) = self.compute_id_range(cfgidx); - let on_entry = &self.on_entry[start.. end]; - let entry_str = bits_to_string(on_entry); - - let gens = &self.gens[start.. end]; - let gens_str = if gens.iter().any(|&u| u != 0) { - format!(" gen: {}", bits_to_string(gens)) - } else { - String::new() - }; - - let action_kills = &self.action_kills[start .. end]; - let action_kills_str = if action_kills.iter().any(|&u| u != 0) { - format!(" action_kill: {}", bits_to_string(action_kills)) - } else { - String::new() - }; - - let scope_kills = &self.scope_kills[start .. end]; - let scope_kills_str = if scope_kills.iter().any(|&u| u != 0) { - format!(" scope_kill: {}", bits_to_string(scope_kills)) - } else { - String::new() - }; - - ps.synth_comment( - format!("id {}: {}{}{}{}", id.as_usize(), entry_str, - gens_str, action_kills_str, scope_kills_str)); - ps.s.space(); - } - } -} - -fn build_local_id_to_index(body: Option<&hir::Body>, - cfg: &cfg::CFG) - -> FxHashMap> { - let mut index = FxHashMap::default(); - - // FIXME(#15020) Would it be better to fold formals from decl - // into cfg itself? i.e., introduce a fn-based flow-graph in - // addition to the current block-based flow-graph, rather than - // have to put traversals like this here? - if let Some(body) = body { - add_entries_from_fn_body(&mut index, body, cfg.entry); - } - - cfg.graph.each_node(|node_idx, node| { - if let cfg::CFGNodeData::AST(id) = node.data { - index.entry(id).or_default().push(node_idx); - } - true - }); - - return index; - - /// Adds mappings from the ast nodes for the formal bindings to - /// the entry-node in the graph. - fn add_entries_from_fn_body(index: &mut FxHashMap>, - body: &hir::Body, - entry: CFGIndex) { - use rustc::hir::intravisit::Visitor; - - struct Formals<'a> { - entry: CFGIndex, - index: &'a mut FxHashMap>, - } - let mut formals = Formals { entry: entry, index: index }; - for param in &body.params { - formals.visit_pat(¶m.pat); - } - impl<'a, 'v> Visitor<'v> for Formals<'a> { - fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'v> { - intravisit::NestedVisitorMap::None - } - - fn visit_pat(&mut self, p: &hir::Pat) { - self.index.entry(p.hir_id.local_id).or_default().push(self.entry); - intravisit::walk_pat(self, p) - } - } - } -} - -/// Flag used by `add_kill` to indicate whether the provided kill -/// takes effect only when control flows directly through the node in -/// question, or if the kill's effect is associated with any -/// control-flow directly through or indirectly over the node. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum KillFrom { - /// A `ScopeEnd` kill is one that takes effect when any control - /// flow goes over the node. A kill associated with the end of the - /// scope of a variable declaration `let x;` is an example of a - /// `ScopeEnd` kill. - ScopeEnd, - - /// An `Execution` kill is one that takes effect only when control - /// flow goes through the node to completion. A kill associated - /// with an assignment statement `x = expr;` is an example of an - /// `Execution` kill. - Execution, -} - -impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> { - pub fn new( - tcx: TyCtxt<'tcx>, - analysis_name: &'static str, - body: Option<&hir::Body>, - cfg: &cfg::CFG, - oper: O, - bits_per_id: usize, - ) -> DataFlowContext<'tcx, O> { - let usize_bits = mem::size_of::() * 8; - let words_per_id = (bits_per_id + usize_bits - 1) / usize_bits; - let num_nodes = cfg.graph.all_nodes().len(); - - debug!("DataFlowContext::new(analysis_name: {}, \ - bits_per_id={}, words_per_id={}) \ - num_nodes: {}", - analysis_name, bits_per_id, words_per_id, - num_nodes); - - let entry = if oper.initial_value() { usize::MAX } else {0}; - - let zeroes = vec![0; num_nodes * words_per_id]; - let gens = zeroes.clone(); - let kills1 = zeroes.clone(); - let kills2 = zeroes; - let on_entry = vec![entry; num_nodes * words_per_id]; - - let local_id_to_index = build_local_id_to_index(body, cfg); - - DataFlowContext { - tcx, - analysis_name, - words_per_id, - local_id_to_index, - bits_per_id, - oper, - gens, - action_kills: kills1, - scope_kills: kills2, - on_entry, - } - } - - pub fn add_gen(&mut self, id: hir::ItemLocalId, bit: usize) { - //! Indicates that `id` generates `bit` - debug!("{} add_gen(id={:?}, bit={})", - self.analysis_name, id, bit); - assert!(self.local_id_to_index.contains_key(&id)); - assert!(self.bits_per_id > 0); - - let indices = get_cfg_indices(id, &self.local_id_to_index); - for &cfgidx in indices { - let (start, end) = self.compute_id_range(cfgidx); - let gens = &mut self.gens[start.. end]; - set_bit(gens, bit); - } - } - - pub fn add_kill(&mut self, kind: KillFrom, id: hir::ItemLocalId, bit: usize) { - //! Indicates that `id` kills `bit` - debug!("{} add_kill(id={:?}, bit={})", - self.analysis_name, id, bit); - assert!(self.local_id_to_index.contains_key(&id)); - assert!(self.bits_per_id > 0); - - let indices = get_cfg_indices(id, &self.local_id_to_index); - for &cfgidx in indices { - let (start, end) = self.compute_id_range(cfgidx); - let kills = match kind { - KillFrom::Execution => &mut self.action_kills[start.. end], - KillFrom::ScopeEnd => &mut self.scope_kills[start.. end], - }; - set_bit(kills, bit); - } - } - - fn apply_gen_kill(&self, cfgidx: CFGIndex, bits: &mut [usize]) { - //! Applies the gen and kill sets for `cfgidx` to `bits` - debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [before]", - self.analysis_name, cfgidx, mut_bits_to_string(bits)); - assert!(self.bits_per_id > 0); - - let (start, end) = self.compute_id_range(cfgidx); - let gens = &self.gens[start.. end]; - bitwise(bits, gens, &Union); - let kills = &self.action_kills[start.. end]; - bitwise(bits, kills, &Subtract); - let kills = &self.scope_kills[start.. end]; - bitwise(bits, kills, &Subtract); - - debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [after]", - self.analysis_name, cfgidx, mut_bits_to_string(bits)); - } - - fn compute_id_range(&self, cfgidx: CFGIndex) -> (usize, usize) { - let n = cfgidx.node_id(); - let start = n * self.words_per_id; - let end = start + self.words_per_id; - - assert!(start < self.gens.len()); - assert!(end <= self.gens.len()); - assert!(self.gens.len() == self.action_kills.len()); - assert!(self.gens.len() == self.scope_kills.len()); - assert!(self.gens.len() == self.on_entry.len()); - - (start, end) - } - - - pub fn each_bit_on_entry(&self, id: hir::ItemLocalId, mut f: F) -> bool where - F: FnMut(usize) -> bool, - { - //! Iterates through each bit that is set on entry to `id`. - //! Only useful after `propagate()` has been called. - if !self.has_bitset_for_local_id(id) { - return true; - } - let indices = get_cfg_indices(id, &self.local_id_to_index); - for &cfgidx in indices { - if !self.each_bit_for_node(EntryOrExit::Entry, cfgidx, |i| f(i)) { - return false; - } - } - return true; - } - - pub fn each_bit_for_node(&self, e: EntryOrExit, cfgidx: CFGIndex, f: F) -> bool where - F: FnMut(usize) -> bool, - { - //! Iterates through each bit that is set on entry/exit to `cfgidx`. - //! Only useful after `propagate()` has been called. - - if self.bits_per_id == 0 { - // Skip the surprisingly common degenerate case. (Note - // compute_id_range requires self.words_per_id > 0.) - return true; - } - - let (start, end) = self.compute_id_range(cfgidx); - let on_entry = &self.on_entry[start.. end]; - let temp_bits; - let slice = match e { - EntryOrExit::Entry => on_entry, - EntryOrExit::Exit => { - let mut t = on_entry.to_vec(); - self.apply_gen_kill(cfgidx, &mut t); - temp_bits = t; - &temp_bits[..] - } - }; - debug!("{} each_bit_for_node({:?}, cfgidx={:?}) bits={}", - self.analysis_name, e, cfgidx, bits_to_string(slice)); - self.each_bit(slice, f) - } - - pub fn each_gen_bit(&self, id: hir::ItemLocalId, mut f: F) -> bool where - F: FnMut(usize) -> bool, - { - //! Iterates through each bit in the gen set for `id`. - if !self.has_bitset_for_local_id(id) { - return true; - } - - if self.bits_per_id == 0 { - // Skip the surprisingly common degenerate case. (Note - // compute_id_range requires self.words_per_id > 0.) - return true; - } - - let indices = get_cfg_indices(id, &self.local_id_to_index); - for &cfgidx in indices { - let (start, end) = self.compute_id_range(cfgidx); - let gens = &self.gens[start.. end]; - debug!("{} each_gen_bit(id={:?}, gens={})", - self.analysis_name, id, bits_to_string(gens)); - if !self.each_bit(gens, |i| f(i)) { - return false; - } - } - return true; - } - - fn each_bit(&self, words: &[usize], mut f: F) -> bool where - F: FnMut(usize) -> bool, - { - //! Helper for iterating over the bits in a bit set. - //! Returns false on the first call to `f` that returns false; - //! if all calls to `f` return true, then returns true. - - let usize_bits = mem::size_of::() * 8; - for (word_index, &word) in words.iter().enumerate() { - if word != 0 { - let base_index = word_index * usize_bits; - for offset in 0..usize_bits { - let bit = 1 << offset; - if (word & bit) != 0 { - // N.B., we round up the total number of bits - // that we store in any given bit set so that - // it is an even multiple of usize::BITS. This - // means that there may be some stray bits at - // the end that do not correspond to any - // actual value. So before we callback, check - // whether the bit_index is greater than the - // actual value the user specified and stop - // iterating if so. - let bit_index = base_index + offset as usize; - if bit_index >= self.bits_per_id { - return true; - } else if !f(bit_index) { - return false; - } - } - } - } - } - return true; - } - - pub fn add_kills_from_flow_exits(&mut self, cfg: &cfg::CFG) { - //! Whenever you have a `break` or `continue` statement, flow - //! exits through any number of enclosing scopes on its way to - //! the new destination. This function infers the kill bits of - //! those control operators based on the kill bits associated - //! with those scopes. - //! - //! This is usually called (if it is called at all), after - //! all add_gen and add_kill calls, but before propagate. - - debug!("{} add_kills_from_flow_exits", self.analysis_name); - if self.bits_per_id == 0 { - // Skip the surprisingly common degenerate case. (Note - // compute_id_range requires self.words_per_id > 0.) - return; - } - cfg.graph.each_edge(|_edge_index, edge| { - let flow_exit = edge.source(); - let (start, end) = self.compute_id_range(flow_exit); - let mut orig_kills = self.scope_kills[start.. end].to_vec(); - - let mut changed = false; - for &id in &edge.data.exiting_scopes { - let opt_cfg_idx = self.local_id_to_index.get(&id); - match opt_cfg_idx { - Some(indices) => { - for &cfg_idx in indices { - let (start, end) = self.compute_id_range(cfg_idx); - let kills = &self.scope_kills[start.. end]; - if bitwise(&mut orig_kills, kills, &Union) { - debug!("scope exits: scope id={:?} \ - (node={:?} of {:?}) added killset: {}", - id, cfg_idx, indices, - bits_to_string(kills)); - changed = true; - } - } - } - None => { - debug!("{} add_kills_from_flow_exits flow_exit={:?} \ - no cfg_idx for exiting_scope={:?}", - self.analysis_name, flow_exit, id); - } - } - } - - if changed { - let bits = &mut self.scope_kills[start.. end]; - debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [before]", - self.analysis_name, flow_exit, mut_bits_to_string(bits)); - bits.copy_from_slice(&orig_kills[..]); - debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [after]", - self.analysis_name, flow_exit, mut_bits_to_string(bits)); - } - true - }); - } -} - -// N.B. `Clone + 'static` only needed for pretty printing. -impl<'tcx, O: DataFlowOperator + Clone + 'static> DataFlowContext<'tcx, O> { - pub fn propagate(&mut self, cfg: &cfg::CFG, body: &hir::Body) { - //! Performs the data flow analysis. - - if self.bits_per_id == 0 { - // Optimize the surprisingly common degenerate case. - return; - } - - { - let words_per_id = self.words_per_id; - let mut propcx = PropagationContext { - dfcx: &mut *self, - changed: true - }; - - let nodes_po = cfg.graph.nodes_in_postorder(OUTGOING, cfg.entry); - let mut temp = vec![0; words_per_id]; - let mut num_passes = 0; - while propcx.changed { - num_passes += 1; - propcx.changed = false; - propcx.reset(&mut temp); - propcx.walk_cfg(cfg, &nodes_po, &mut temp); - } - debug!("finished in {} iterations", num_passes); - } - - debug!("Dataflow result for {}:", self.analysis_name); - debug!("{}", pprust::to_string(self, |s| { - s.cbox(pprust::INDENT_UNIT); - s.ibox(0); - s.print_expr(&body.value) - })); - } -} - -impl PropagationContext<'_, 'tcx, O> { - fn walk_cfg(&mut self, - cfg: &cfg::CFG, - nodes_po: &[CFGIndex], - in_out: &mut [usize]) { - debug!("DataFlowContext::walk_cfg(in_out={}) {}", - bits_to_string(in_out), self.dfcx.analysis_name); - assert!(self.dfcx.bits_per_id > 0); - - // Iterate over nodes in reverse post-order. - for &node_index in nodes_po.iter().rev() { - let node = cfg.graph.node(node_index); - debug!("DataFlowContext::walk_cfg idx={:?} id={:?} begin in_out={}", - node_index, node.data.id(), bits_to_string(in_out)); - - let (start, end) = self.dfcx.compute_id_range(node_index); - - // Initialize local bitvector with state on-entry. - in_out.copy_from_slice(&self.dfcx.on_entry[start.. end]); - - // Compute state on-exit by applying transfer function to - // state on-entry. - self.dfcx.apply_gen_kill(node_index, in_out); - - // Propagate state on-exit from node into its successors. - self.propagate_bits_into_graph_successors_of(in_out, cfg, node_index); - } - } - - fn reset(&mut self, bits: &mut [usize]) { - let e = if self.dfcx.oper.initial_value() {usize::MAX} else {0}; - for b in bits { - *b = e; - } - } - - fn propagate_bits_into_graph_successors_of(&mut self, - pred_bits: &[usize], - cfg: &cfg::CFG, - cfgidx: CFGIndex) { - for (_, edge) in cfg.graph.outgoing_edges(cfgidx) { - self.propagate_bits_into_entry_set_for(pred_bits, edge); - } - } - - fn propagate_bits_into_entry_set_for(&mut self, - pred_bits: &[usize], - edge: &cfg::CFGEdge) { - let source = edge.source(); - let cfgidx = edge.target(); - debug!("{} propagate_bits_into_entry_set_for(pred_bits={}, {:?} to {:?})", - self.dfcx.analysis_name, bits_to_string(pred_bits), source, cfgidx); - assert!(self.dfcx.bits_per_id > 0); - - let (start, end) = self.dfcx.compute_id_range(cfgidx); - let changed = { - // (scoping mutable borrow of self.dfcx.on_entry) - let on_entry = &mut self.dfcx.on_entry[start.. end]; - bitwise(on_entry, pred_bits, &self.dfcx.oper) - }; - if changed { - debug!("{} changed entry set for {:?} to {}", - self.dfcx.analysis_name, cfgidx, - bits_to_string(&self.dfcx.on_entry[start.. end])); - self.changed = true; - } - } -} - -fn mut_bits_to_string(words: &mut [usize]) -> String { - bits_to_string(words) -} - -fn bits_to_string(words: &[usize]) -> String { - let mut result = String::new(); - let mut sep = '['; - - // Note: this is a little endian printout of bytes. - - for &word in words { - let mut v = word; - for _ in 0..mem::size_of::() { - result.push(sep); - result.push_str(&format!("{:02x}", v & 0xFF)); - v >>= 8; - sep = '-'; - } - } - result.push(']'); - return result -} - -#[inline] -fn bitwise(out_vec: &mut [usize], - in_vec: &[usize], - op: &Op) -> bool { - assert_eq!(out_vec.len(), in_vec.len()); - let mut changed = false; - for (out_elt, in_elt) in out_vec.iter_mut().zip(in_vec) { - let old_val = *out_elt; - let new_val = op.join(old_val, *in_elt); - *out_elt = new_val; - changed |= old_val != new_val; - } - changed -} - -fn set_bit(words: &mut [usize], bit: usize) -> bool { - debug!("set_bit: words={} bit={}", - mut_bits_to_string(words), bit_str(bit)); - let usize_bits = mem::size_of::() * 8; - let word = bit / usize_bits; - let bit_in_word = bit % usize_bits; - let bit_mask = 1 << bit_in_word; - debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, bit_mask); - let oldv = words[word]; - let newv = oldv | bit_mask; - words[word] = newv; - oldv != newv -} - -fn bit_str(bit: usize) -> String { - let byte = bit >> 3; - let lobits = 1 << (bit & 0b111); - format!("[{}:{}-{:02x}]", bit, byte, lobits) -} - -struct Union; -impl BitwiseOperator for Union { - fn join(&self, a: usize, b: usize) -> usize { a | b } -} -struct Subtract; -impl BitwiseOperator for Subtract { - fn join(&self, a: usize, b: usize) -> usize { a & !b } -} diff --git a/src/librustc_ast_borrowck/graphviz.rs b/src/librustc_ast_borrowck/graphviz.rs deleted file mode 100644 index c077dc828aba2..0000000000000 --- a/src/librustc_ast_borrowck/graphviz.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! This module provides linkage between rustc::middle::graph and -//! libgraphviz traits, specialized to attaching borrowck analysis -//! data to rendered labels. - -pub use Variant::*; - -pub(crate) use crate::cfg::graphviz::{Node, Edge}; -use crate::cfg::graphviz as cfg_dot; -use crate::cfg::CFGIndex; -use crate::borrowck::{self, BorrowckCtxt, LoanPath}; -use crate::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit}; -use log::debug; -use std::rc::Rc; - -#[derive(Debug, Copy, Clone)] -pub enum Variant { - Loans, - Moves, - Assigns, -} - -impl Variant { - pub fn short_name(&self) -> &'static str { - match *self { - Loans => "loans", - Moves => "moves", - Assigns => "assigns", - } - } -} - -pub struct DataflowLabeller<'a, 'tcx> { - pub inner: cfg_dot::LabelledCFG<'a, 'tcx>, - pub variants: Vec, - pub borrowck_ctxt: &'a BorrowckCtxt<'a, 'tcx>, - pub analysis_data: &'a borrowck::AnalysisData<'tcx>, -} - -impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> { - fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String { - let id = n.1.data.id(); - debug!("dataflow_for({:?}, id={:?}) {:?}", e, id, self.variants); - let mut sets = String::new(); - let mut seen_one = false; - for &variant in &self.variants { - if seen_one { sets.push_str(" "); } else { seen_one = true; } - sets.push_str(variant.short_name()); - sets.push_str(": "); - sets.push_str(&self.dataflow_for_variant(e, n, variant)); - } - sets - } - - fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node<'_>, v: Variant) -> String { - let cfgidx = n.0; - match v { - Loans => self.dataflow_loans_for(e, cfgidx), - Moves => self.dataflow_moves_for(e, cfgidx), - Assigns => self.dataflow_assigns_for(e, cfgidx), - } - } - - fn build_set( - &self, - e: EntryOrExit, - cfgidx: CFGIndex, - dfcx: &DataFlowContext<'tcx, O>, - mut to_lp: F, - ) -> String - where - F: FnMut(usize) -> Rc>, - { - let mut saw_some = false; - let mut set = "{".to_string(); - dfcx.each_bit_for_node(e, cfgidx, |index| { - let lp = to_lp(index); - if saw_some { - set.push_str(", "); - } - let loan_str = self.borrowck_ctxt.loan_path_to_string(&lp); - set.push_str(&loan_str); - saw_some = true; - true - }); - set.push_str("}"); - set - } - - fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String { - let dfcx = &self.analysis_data.loans; - let loan_index_to_path = |loan_index| { - let all_loans = &self.analysis_data.all_loans; - let l: &borrowck::Loan<'_> = &all_loans[loan_index]; - l.loan_path() - }; - self.build_set(e, cfgidx, dfcx, loan_index_to_path) - } - - fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String { - let dfcx = &self.analysis_data.move_data.dfcx_moves; - let move_index_to_path = |move_index| { - let move_data = &self.analysis_data.move_data.move_data; - let moves = move_data.moves.borrow(); - let the_move: &borrowck::move_data::Move = &(*moves)[move_index]; - move_data.path_loan_path(the_move.path) - }; - self.build_set(e, cfgidx, dfcx, move_index_to_path) - } - - fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String { - let dfcx = &self.analysis_data.move_data.dfcx_assign; - let assign_index_to_path = |assign_index| { - let move_data = &self.analysis_data.move_data.move_data; - let assignments = move_data.var_assignments.borrow(); - let assignment: &borrowck::move_data::Assignment = &(*assignments)[assign_index]; - move_data.path_loan_path(assignment.path) - }; - self.build_set(e, cfgidx, dfcx, assign_index_to_path) - } -} - -impl<'a, 'tcx> dot::Labeller<'a> for DataflowLabeller<'a, 'tcx> { - type Node = Node<'a>; - type Edge = Edge<'a>; - fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() } - fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) } - fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> { - let prefix = self.dataflow_for(EntryOrExit::Entry, n); - let suffix = self.dataflow_for(EntryOrExit::Exit, n); - let inner_label = self.inner.node_label(n); - inner_label - .prefix_line(dot::LabelText::LabelStr(prefix.into())) - .suffix_line(dot::LabelText::LabelStr(suffix.into())) - } - fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) } -} - -impl<'a, 'tcx> dot::GraphWalk<'a> for DataflowLabeller<'a, 'tcx> { - type Node = Node<'a>; - type Edge = Edge<'a>; - fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() } - fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() } - fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) } - fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) } -} diff --git a/src/librustc_ast_borrowck/lib.rs b/src/librustc_ast_borrowck/lib.rs deleted file mode 100644 index aea97fea1a9fd..0000000000000 --- a/src/librustc_ast_borrowck/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")] - -#![allow(non_camel_case_types)] - -#![feature(in_band_lifetimes)] -#![feature(nll)] - -#![recursion_limit="256"] - -#[macro_use] -extern crate rustc; - -pub use borrowck::check_crate; -pub use borrowck::build_borrowck_dataflow_data_for_fn; - -mod borrowck; - -pub mod graphviz; - -mod dataflow; -pub mod cfg; - -pub use borrowck::provide; From 83dcdd99dd2c18a639fc2bcb0a011942551e73a8 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 25 Sep 2019 23:17:01 +0200 Subject: [PATCH 02/10] Remove librustc_ast_borrowck from driver & interface. --- Cargo.lock | 13 ------------- src/librustc_driver/Cargo.toml | 1 - src/librustc_interface/Cargo.toml | 1 - 3 files changed, 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbaf7d801ca06..40a0b9fe59dbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3337,17 +3337,6 @@ dependencies = [ "core", ] -[[package]] -name = "rustc_ast_borrowck" -version = "0.0.0" -dependencies = [ - "graphviz", - "log", - "rustc", - "rustc_data_structures", - "syntax_pos", -] - [[package]] name = "rustc_codegen_llvm" version = "0.0.0" @@ -3425,7 +3414,6 @@ dependencies = [ "lazy_static 1.3.0", "log", "rustc", - "rustc_ast_borrowck", "rustc_codegen_utils", "rustc_data_structures", "rustc_errors", @@ -3483,7 +3471,6 @@ dependencies = [ "once_cell", "rustc", "rustc-rayon", - "rustc_ast_borrowck", "rustc_codegen_ssa", "rustc_codegen_utils", "rustc_data_structures", diff --git a/src/librustc_driver/Cargo.toml b/src/librustc_driver/Cargo.toml index 25f67b30468cc..d615e5b256d1a 100644 --- a/src/librustc_driver/Cargo.toml +++ b/src/librustc_driver/Cargo.toml @@ -16,7 +16,6 @@ log = "0.4" env_logger = { version = "0.6", default-features = false } rustc = { path = "../librustc" } rustc_target = { path = "../librustc_target" } -rustc_ast_borrowck = { path = "../librustc_ast_borrowck" } rustc_data_structures = { path = "../librustc_data_structures" } errors = { path = "../librustc_errors", package = "rustc_errors" } rustc_metadata = { path = "../librustc_metadata" } diff --git a/src/librustc_interface/Cargo.toml b/src/librustc_interface/Cargo.toml index f6293107a940e..780f7a7ffa9ed 100644 --- a/src/librustc_interface/Cargo.toml +++ b/src/librustc_interface/Cargo.toml @@ -18,7 +18,6 @@ syntax_ext = { path = "../libsyntax_ext" } syntax_pos = { path = "../libsyntax_pos" } rustc_serialize = { path = "../libserialize", package = "serialize" } rustc = { path = "../librustc" } -rustc_ast_borrowck = { path = "../librustc_ast_borrowck" } rustc_incremental = { path = "../librustc_incremental" } rustc_traits = { path = "../librustc_traits" } rustc_data_structures = { path = "../librustc_data_structures" } From 463b19790b1e23a220f2e2e5902a37f00793a3b0 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 25 Sep 2019 23:17:58 +0200 Subject: [PATCH 03/10] don't borrowck::check_crate(tcx) anymore. --- src/librustc_interface/passes.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index 6abc6e32d243b..52d95a04c9a29 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -17,7 +17,6 @@ use rustc::util::common::{time, ErrorReported}; use rustc::session::Session; use rustc::session::config::{self, CrateType, Input, OutputFilenames, OutputType}; use rustc::session::search_paths::PathKind; -use rustc_ast_borrowck as borrowck; use rustc_codegen_ssa::back::link::emit_metadata; use rustc_codegen_utils::codegen_backend::CodegenBackend; use rustc_codegen_utils::link::filename_for_metadata; @@ -769,7 +768,6 @@ pub fn default_provide(providers: &mut ty::query::Providers<'_>) { proc_macro_decls::provide(providers); plugin::build::provide(providers); hir::provide(providers); - borrowck::provide(providers); mir::provide(providers); reachable::provide(providers); resolve_lifetime::provide(providers); @@ -937,12 +935,6 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> { }); }); - time(sess, "borrow checking", || { - if tcx.use_ast_borrowck() { - borrowck::check_crate(tcx); - } - }); - time(sess, "MIR borrow checking", || { tcx.par_body_owners(|def_id| tcx.ensure().mir_borrowck(def_id)); }); From 1eb280e2d8a74b72ac58dbb6939497b4f1dacb84 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 25 Sep 2019 23:18:47 +0200 Subject: [PATCH 04/10] Remove unpretty=flowgraph. --- src/librustc/session/config.rs | 10 -- src/librustc/session/config/tests.rs | 8 -- src/librustc_driver/pretty.rs | 178 +++------------------------ 3 files changed, 14 insertions(+), 182 deletions(-) diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 73b731b07619d..b4fe550067a1f 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1268,14 +1268,6 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, save_analysis: bool = (false, parse_bool, [UNTRACKED], "write syntax and type analysis (in JSON format) information, in \ addition to normal output"), - flowgraph_print_loans: bool = (false, parse_bool, [UNTRACKED], - "include loan analysis data in -Z unpretty flowgraph output"), - flowgraph_print_moves: bool = (false, parse_bool, [UNTRACKED], - "include move analysis data in -Z unpretty flowgraph output"), - flowgraph_print_assigns: bool = (false, parse_bool, [UNTRACKED], - "include assignment analysis data in -Z unpretty flowgraph output"), - flowgraph_print_all: bool = (false, parse_bool, [UNTRACKED], - "include all dataflow analysis data in -Z unpretty flowgraph output"), print_region_graph: bool = (false, parse_bool, [UNTRACKED], "prints region inference graph. \ Use with RUST_REGION_GRAPH=help for more info"), @@ -1424,8 +1416,6 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, valid types are any of the types for `--pretty`, as well as: `expanded`, `expanded,identified`, `expanded,hygiene` (with internal representations), - `flowgraph=` (graphviz formatted flowgraph for node), - `flowgraph,unlabelled=` (unlabelled graphviz formatted flowgraph for node), `everybody_loops` (all function bodies replaced with `loop {}`), `hir` (the HIR), `hir,identified`, `hir,typed` (HIR with types for each node), diff --git a/src/librustc/session/config/tests.rs b/src/librustc/session/config/tests.rs index 9eb68056bfd97..c117418f63699 100644 --- a/src/librustc/session/config/tests.rs +++ b/src/librustc/session/config/tests.rs @@ -589,14 +589,6 @@ fn test_debugging_options_tracking_hash() { assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); opts.debugging_opts.save_analysis = true; assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); - opts.debugging_opts.flowgraph_print_loans = true; - assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); - opts.debugging_opts.flowgraph_print_moves = true; - assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); - opts.debugging_opts.flowgraph_print_assigns = true; - assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); - opts.debugging_opts.flowgraph_print_all = true; - assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); opts.debugging_opts.print_region_graph = true; assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); opts.debugging_opts.parse_only = true; diff --git a/src/librustc_driver/pretty.rs b/src/librustc_driver/pretty.rs index fa9504e22019e..3382b70b35b2d 100644 --- a/src/librustc_driver/pretty.rs +++ b/src/librustc_driver/pretty.rs @@ -2,7 +2,6 @@ use rustc::hir; use rustc::hir::map as hir_map; -use rustc::hir::map::blocks; use rustc::hir::print as pprust_hir; use rustc::hir::def_id::LOCAL_CRATE; use rustc::session::Session; @@ -10,9 +9,6 @@ use rustc::session::config::Input; use rustc::ty::{self, TyCtxt}; use rustc::util::common::ErrorReported; use rustc_interface::util::ReplaceBodyWithLoop; -use rustc_ast_borrowck as borrowck; -use rustc_ast_borrowck::graphviz as borrowck_dot; -use rustc_ast_borrowck::cfg::{self, graphviz::LabelledCFG}; use rustc_mir::util::{write_mir_pretty, write_mir_graphviz}; use syntax::ast; @@ -20,11 +16,9 @@ use syntax::mut_visit::MutVisitor; use syntax::print::{pprust}; use syntax_pos::FileName; -use graphviz as dot; - use std::cell::Cell; use std::fs::File; -use std::io::{self, Write}; +use std::io::Write; use std::option; use std::path::Path; use std::str::FromStr; @@ -48,21 +42,11 @@ pub enum PpSourceMode { PpmTyped, } -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum PpFlowGraphMode { - Default, - /// Drops the labels from the edges in the flowgraph output. This - /// is mostly for use in the -Z unpretty flowgraph run-make tests, - /// since the labels are largely uninteresting in those cases and - /// have become a pain to maintain. - UnlabelledEdges, -} #[derive(Copy, Clone, PartialEq, Debug)] pub enum PpMode { PpmSource(PpSourceMode), PpmHir(PpSourceMode), PpmHirTree(PpSourceMode), - PpmFlowGraph(PpFlowGraphMode), PpmMir, PpmMirCFG, } @@ -80,15 +64,14 @@ impl PpMode { PpmHir(_) | PpmHirTree(_) | PpmMir | - PpmMirCFG | - PpmFlowGraph(_) => true, + PpmMirCFG => true, PpmSource(PpmTyped) => panic!("invalid state"), } } pub fn needs_analysis(&self) -> bool { match *self { - PpmMir | PpmMirCFG | PpmFlowGraph(_) => true, + PpmMir | PpmMirCFG => true, _ => false, } } @@ -114,15 +97,13 @@ pub fn parse_pretty(sess: &Session, ("hir-tree", true) => PpmHirTree(PpmNormal), ("mir", true) => PpmMir, ("mir-cfg", true) => PpmMirCFG, - ("flowgraph", true) => PpmFlowGraph(PpFlowGraphMode::Default), - ("flowgraph,unlabelled", true) => PpmFlowGraph(PpFlowGraphMode::UnlabelledEdges), _ => { if extended { sess.fatal(&format!("argument to `unpretty` must be one of `normal`, \ - `expanded`, `flowgraph[,unlabelled]=`, \ - `identified`, `expanded,identified`, `everybody_loops`, \ - `hir`, `hir,identified`, `hir,typed`, `hir-tree`, \ - `mir` or `mir-cfg`; got {}", + `expanded`, `identified`, `expanded,identified`, \ + `everybody_loops`, `hir`, `hir,identified`, \ + `hir,typed`, `hir-tree`, `mir` or `mir-cfg`; \ + got {}", name)); } else { sess.fatal(&format!("argument to `pretty` must be one of `normal`, `expanded`, \ @@ -501,24 +482,6 @@ impl<'a, 'tcx> pprust_hir::PpAnn for TypedAnnotation<'a, 'tcx> { } } -fn gather_flowgraph_variants(sess: &Session) -> Vec { - let print_loans = sess.opts.debugging_opts.flowgraph_print_loans; - let print_moves = sess.opts.debugging_opts.flowgraph_print_moves; - let print_assigns = sess.opts.debugging_opts.flowgraph_print_assigns; - let print_all = sess.opts.debugging_opts.flowgraph_print_all; - let mut variants = Vec::new(); - if print_all || print_loans { - variants.push(borrowck_dot::Loans); - } - if print_all || print_moves { - variants.push(borrowck_dot::Moves); - } - if print_all || print_assigns { - variants.push(borrowck_dot::Assigns); - } - variants -} - #[derive(Clone, Debug)] pub enum UserIdentifiedItem { ItemViaNode(ast::NodeId), @@ -609,81 +572,6 @@ impl UserIdentifiedItem { } } -fn print_flowgraph<'tcx, W: Write>( - variants: Vec, - tcx: TyCtxt<'tcx>, - code: blocks::Code<'tcx>, - mode: PpFlowGraphMode, - mut out: W, -) -> io::Result<()> { - let body_id = match code { - blocks::Code::Expr(expr) => { - // Find the function this expression is from. - let mut hir_id = expr.hir_id; - loop { - let node = tcx.hir().get(hir_id); - if let Some(n) = hir::map::blocks::FnLikeNode::from_node(node) { - break n.body(); - } - let parent = tcx.hir().get_parent_node(hir_id); - assert_ne!(hir_id, parent); - hir_id = parent; - } - } - blocks::Code::FnLike(fn_like) => fn_like.body(), - }; - let body = tcx.hir().body(body_id); - let cfg = cfg::CFG::new(tcx, &body); - let labelled_edges = mode != PpFlowGraphMode::UnlabelledEdges; - let hir_id = code.id(); - // We have to disassemble the hir_id because name must be ASCII - // alphanumeric. This does not appear in the rendered graph, so it does not - // have to be user friendly. - let name = format!( - "hir_id_{}_{}", - hir_id.owner.index(), - hir_id.local_id.index(), - ); - let lcfg = LabelledCFG { - tcx, - cfg: &cfg, - name, - labelled_edges, - }; - - match code { - _ if variants.is_empty() => { - let r = dot::render(&lcfg, &mut out); - return expand_err_details(r); - } - blocks::Code::Expr(_) => { - tcx.sess.err("--pretty flowgraph with -Z flowgraph-print annotations requires \ - fn-like node id."); - return Ok(()); - } - blocks::Code::FnLike(fn_like) => { - let (bccx, analysis_data) = - borrowck::build_borrowck_dataflow_data_for_fn(tcx, fn_like.body(), &cfg); - - let lcfg = borrowck_dot::DataflowLabeller { - inner: lcfg, - variants, - borrowck_ctxt: &bccx, - analysis_data: &analysis_data, - }; - let r = dot::render(&lcfg, &mut out); - return expand_err_details(r); - } - } - - fn expand_err_details(r: io::Result<()>) -> io::Result<()> { - r.map_err(|ioerr| { - io::Error::new(io::ErrorKind::Other, - format!("graphviz::render failed: {}", ioerr)) - }) - } -} - pub fn visit_crate(sess: &Session, krate: &mut ast::Crate, ppm: PpMode) { if let PpmSource(PpmEveryBodyLoops) = ppm { ReplaceBodyWithLoop::new(sess).visit_crate(krate); @@ -872,55 +760,17 @@ fn print_with_analysis( tcx.analysis(LOCAL_CRATE)?; - let mut print = || match ppm { + match ppm { PpmMir | PpmMirCFG => { - if let Some(nodeid) = nodeid { - let def_id = tcx.hir().local_def_id_from_node_id(nodeid); - match ppm { - PpmMir => write_mir_pretty(tcx, Some(def_id), &mut out), - PpmMirCFG => write_mir_graphviz(tcx, Some(def_id), &mut out), - _ => unreachable!(), - }?; - } else { - match ppm { - PpmMir => write_mir_pretty(tcx, None, &mut out), - PpmMirCFG => write_mir_graphviz(tcx, None, &mut out), - _ => unreachable!(), - }?; - } - Ok(()) - } - PpmFlowGraph(mode) => { - let nodeid = - nodeid.expect("`pretty flowgraph=..` needs NodeId (int) or unique path \ - suffix (b::c::d)"); - let hir_id = tcx.hir().node_to_hir_id(nodeid); - let node = tcx.hir().find(hir_id).unwrap_or_else(|| { - tcx.sess.fatal(&format!("`--pretty=flowgraph` couldn't find ID: {}", nodeid)) - }); - - match blocks::Code::from_node(&tcx.hir(), hir_id) { - Some(code) => { - let variants = gather_flowgraph_variants(tcx.sess); - - let out: &mut dyn Write = &mut out; - - print_flowgraph(variants, tcx, code, mode, out) - } - None => { - let message = format!("`--pretty=flowgraph` needs block, fn, or method; \ - got {:?}", - node); - - let hir_id = tcx.hir().node_to_hir_id(nodeid); - tcx.sess.span_fatal(tcx.hir().span(hir_id), &message) - } + let def_id = nodeid.map(|nid| tcx.hir().local_def_id_from_node_id(nid)); + match ppm { + PpmMir => write_mir_pretty(tcx, def_id, &mut out), + PpmMirCFG => write_mir_graphviz(tcx, def_id, &mut out), + _ => unreachable!(), } } _ => unreachable!(), - }; - - print().unwrap(); + }.unwrap(); write_output(out, ofile); From defd5088d616bb324c92069b2c1129b76bc0ff94 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Sep 2019 00:36:41 +0200 Subject: [PATCH 05/10] cleanup check_match wrt. SignalledError. --- src/librustc/query/mod.rs | 2 +- src/librustc/ty/query/mod.rs | 2 +- src/librustc_mir/hair/pattern/check_match.rs | 21 +++++--------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/librustc/query/mod.rs b/src/librustc/query/mod.rs index 38af38b828238..6de351fa13af4 100644 --- a/src/librustc/query/mod.rs +++ b/src/librustc/query/mod.rs @@ -469,7 +469,7 @@ rustc_queries! { } TypeChecking { - query check_match(key: DefId) -> SignalledError { + query check_match(key: DefId) { cache_on_disk_if { key.is_local() } } diff --git a/src/librustc/ty/query/mod.rs b/src/librustc/ty/query/mod.rs index fb2ad2aa54d7a..f4b99ca368874 100644 --- a/src/librustc/ty/query/mod.rs +++ b/src/librustc/ty/query/mod.rs @@ -4,7 +4,7 @@ use crate::hir::def::{DefKind, Export}; use crate::hir::{self, TraitCandidate, ItemLocalId, CodegenFnAttrs}; use crate::infer::canonical::{self, Canonical}; use crate::lint; -use crate::middle::borrowck::{BorrowCheckResult, SignalledError}; +use crate::middle::borrowck::BorrowCheckResult; use crate::middle::cstore::{ExternCrate, LinkagePreference, NativeLibrary, ForeignModule}; use crate::middle::cstore::{NativeLibraryKind, DepKind, CrateSource}; use crate::middle::privacy::AccessLevels; diff --git a/src/librustc_mir/hair/pattern/check_match.rs b/src/librustc_mir/hair/pattern/check_match.rs index 4572519683d4f..b57e57ba3ec95 100644 --- a/src/librustc_mir/hair/pattern/check_match.rs +++ b/src/librustc_mir/hair/pattern/check_match.rs @@ -4,7 +4,6 @@ use super::_match::WitnessPreference::*; use super::{PatCtxt, PatternError, PatKind}; -use rustc::middle::borrowck::SignalledError; use rustc::session::Session; use rustc::ty::{self, Ty, TyCtxt}; use rustc::ty::subst::{InternalSubsts, SubstsRef}; @@ -21,11 +20,10 @@ use std::slice; use syntax_pos::{Span, DUMMY_SP, MultiSpan}; -crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) -> SignalledError { - let body_id = if let Some(id) = tcx.hir().as_local_hir_id(def_id) { - tcx.hir().body_owned_by(id) - } else { - return SignalledError::NoErrorsSeen; +crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) { + let body_id = match tcx.hir().as_local_hir_id(def_id) { + None => return, + Some(id) => tcx.hir().body_owned_by(id), }; let mut visitor = MatchVisitor { @@ -33,10 +31,8 @@ crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) -> SignalledError { tables: tcx.body_tables(body_id), param_env: tcx.param_env(def_id), identity_substs: InternalSubsts::identity_for_item(tcx, def_id), - signalled_error: SignalledError::NoErrorsSeen, }; visitor.visit_body(tcx.hir().body(body_id)); - visitor.signalled_error } fn create_e0004(sess: &Session, sp: Span, error_message: String) -> DiagnosticBuilder<'_> { @@ -48,7 +44,6 @@ struct MatchVisitor<'a, 'tcx> { tables: &'a ty::TypeckTables<'tcx>, param_env: ty::ParamEnv<'tcx>, identity_substs: SubstsRef<'tcx>, - signalled_error: SignalledError, } impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> { @@ -136,13 +131,7 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { // First, check legality of move bindings. self.check_patterns(arm.guard.is_some(), &arm.pat); - // Second, if there is a guard on each arm, make sure it isn't - // assigning or borrowing anything mutably. - if arm.guard.is_some() { - self.signalled_error = SignalledError::SawSomeError; - } - - // Third, perform some lints. + // Second, perform some lints. check_for_bindings_named_same_as_variants(self, &arm.pat); } From dfd365f3e4fed3df101bd2d3ea01c494f40bd345 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Sep 2019 01:22:16 +0200 Subject: [PATCH 06/10] cleanup dead ast-borrowck / migrate-mode code. --- src/librustc/arena.rs | 1 - src/librustc/infer/mod.rs | 2 + src/librustc/lib.rs | 1 - src/librustc/middle/borrowck.rs | 31 ---------- src/librustc/query/mod.rs | 4 -- src/librustc/session/config.rs | 8 --- src/librustc/ty/context.rs | 6 -- src/librustc/ty/query/mod.rs | 1 - src/librustc_mir/borrow_check/mod.rs | 56 ++++++------------- src/librustc_mir/transform/elaborate_drops.rs | 12 +--- src/librustc_mir/transform/mod.rs | 4 -- 11 files changed, 20 insertions(+), 106 deletions(-) delete mode 100644 src/librustc/middle/borrowck.rs diff --git a/src/librustc/arena.rs b/src/librustc/arena.rs index d4fc1b12830a1..5d06f62f44616 100644 --- a/src/librustc/arena.rs +++ b/src/librustc/arena.rs @@ -86,7 +86,6 @@ macro_rules! arena_types { rustc::infer::canonical::QueryResponse<'tcx, rustc::ty::Ty<'tcx>> >, [few] crate_inherent_impls: rustc::ty::CrateInherentImpls, - [decode] borrowck: rustc::middle::borrowck::BorrowCheckResult, [few] upstream_monomorphizations: rustc::util::nodemap::DefIdMap< rustc_data_structures::fx::FxHashMap< diff --git a/src/librustc/infer/mod.rs b/src/librustc/infer/mod.rs index ca07496afed08..81183dc1f7908 100644 --- a/src/librustc/infer/mod.rs +++ b/src/librustc/infer/mod.rs @@ -93,6 +93,8 @@ impl SuppressRegionErrors { /// checks, so we should ignore errors if NLL is (unconditionally) /// enabled. pub fn when_nll_is_enabled(tcx: TyCtxt<'_>) -> Self { + // FIXME(Centril): Once we actually remove `::Migrate` also make + // this always `true` and then proceed to eliminate the dead code. match tcx.borrowck_mode() { // If we're on Migrate mode, report AST region errors BorrowckMode::Migrate => SuppressRegionErrors { suppressed: false }, diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 7a01ae6b6d9cc..bd9899b644b5e 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -100,7 +100,6 @@ pub mod infer; pub mod lint; pub mod middle { - pub mod borrowck; pub mod expr_use_visitor; pub mod cstore; pub mod dead; diff --git a/src/librustc/middle/borrowck.rs b/src/librustc/middle/borrowck.rs deleted file mode 100644 index 60c24eeae7b64..0000000000000 --- a/src/librustc/middle/borrowck.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::ich::StableHashingContext; - -use rustc_data_structures::stable_hasher::{HashStable, StableHasher, - StableHasherResult}; - -#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)] -pub enum SignalledError { SawSomeError, NoErrorsSeen } - -impl Default for SignalledError { - fn default() -> SignalledError { - SignalledError::NoErrorsSeen - } -} - -impl_stable_hash_for!(enum self::SignalledError { SawSomeError, NoErrorsSeen }); - -#[derive(Debug, Default, RustcEncodable, RustcDecodable)] -pub struct BorrowCheckResult { - pub signalled_any_error: SignalledError, -} - -impl<'a> HashStable> for BorrowCheckResult { - fn hash_stable(&self, - hcx: &mut StableHashingContext<'a>, - hasher: &mut StableHasher) { - let BorrowCheckResult { - ref signalled_any_error, - } = *self; - signalled_any_error.hash_stable(hcx, hasher); - } -} diff --git a/src/librustc/query/mod.rs b/src/librustc/query/mod.rs index 6de351fa13af4..4b1558592aee3 100644 --- a/src/librustc/query/mod.rs +++ b/src/librustc/query/mod.rs @@ -397,10 +397,6 @@ rustc_queries! { } BorrowChecking { - query borrowck(key: DefId) -> &'tcx BorrowCheckResult { - cache_on_disk_if { key.is_local() } - } - /// Borrow-checks the function body. If this is a closure, returns /// additional requirements that the closure's creator must verify. query mir_borrowck(key: DefId) -> mir::BorrowCheckResult<'tcx> { diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index b4fe550067a1f..cbb22f1e44830 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -478,14 +478,6 @@ impl BorrowckMode { BorrowckMode::Migrate => true, } } - - /// Returns whether we should emit the AST-based borrow checker errors. - pub fn use_ast(self) -> bool { - match self { - BorrowckMode::Mir => false, - BorrowckMode::Migrate => false, - } - } } pub enum Input { diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index 3c511cb4d188d..ad3fee171662a 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -1435,12 +1435,6 @@ impl<'tcx> TyCtxt<'tcx> { self.queries.on_disk_cache.serialize(self.global_tcx(), encoder) } - /// If `true`, we should use the AST-based borrowck (we may *also* use - /// the MIR-based borrowck). - pub fn use_ast_borrowck(self) -> bool { - self.borrowck_mode().use_ast() - } - /// If `true`, we should use the MIR-based borrowck, but also /// fall back on the AST borrowck if the MIR-based one errors. pub fn migrate_borrowck(self) -> bool { diff --git a/src/librustc/ty/query/mod.rs b/src/librustc/ty/query/mod.rs index f4b99ca368874..f559cde4b03cf 100644 --- a/src/librustc/ty/query/mod.rs +++ b/src/librustc/ty/query/mod.rs @@ -4,7 +4,6 @@ use crate::hir::def::{DefKind, Export}; use crate::hir::{self, TraitCandidate, ItemLocalId, CodegenFnAttrs}; use crate::infer::canonical::{self, Canonical}; use crate::lint; -use crate::middle::borrowck::BorrowCheckResult; use crate::middle::cstore::{ExternCrate, LinkagePreference, NativeLibrary, ForeignModule}; use crate::middle::cstore::{NativeLibraryKind, DepKind, CrateSource}; use crate::middle::privacy::AccessLevels; diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index 067ab080713c4..70a2526f70a7e 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -1932,48 +1932,26 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } - Reservation(wk @ WriteKind::Move) - | Write(wk @ WriteKind::Move) - | Reservation(wk @ WriteKind::StorageDeadOrDrop) - | Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shared)) - | Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow)) - | Write(wk @ WriteKind::StorageDeadOrDrop) - | Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared)) - | Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow)) => { - if let (Err(place_err), true) = ( + Reservation(WriteKind::Move) + | Write(WriteKind::Move) + | Reservation(WriteKind::StorageDeadOrDrop) + | Reservation(WriteKind::MutableBorrow(BorrowKind::Shared)) + | Reservation(WriteKind::MutableBorrow(BorrowKind::Shallow)) + | Write(WriteKind::StorageDeadOrDrop) + | Write(WriteKind::MutableBorrow(BorrowKind::Shared)) + | Write(WriteKind::MutableBorrow(BorrowKind::Shallow)) => { + if let (Err(_), true) = ( self.is_mutable(place.as_ref(), is_local_mutation_allowed), self.errors_buffer.is_empty() ) { - if self.infcx.tcx.migrate_borrowck() { - // rust-lang/rust#46908: In pure NLL mode this - // code path should be unreachable (and thus - // we signal an ICE in the else branch - // here). But we can legitimately get here - // under borrowck=migrate mode, so instead of - // ICE'ing we instead report a legitimate - // error (which will then be downgraded to a - // warning by the migrate machinery). - error_access = match wk { - WriteKind::MutableBorrow(_) => AccessKind::MutableBorrow, - WriteKind::Move => AccessKind::Move, - WriteKind::StorageDeadOrDrop | - WriteKind::Mutate => AccessKind::Mutate, - }; - self.report_mutability_error( - place, - span, - place_err, - error_access, - location, - ); - } else { - span_bug!( - span, - "Accessing `{:?}` with the kind `{:?}` shouldn't be possible", - place, - kind, - ); - } + // rust-lang/rust#46908: In pure NLL mode this code path should + // be unreachable (and thus we signal an ICE in the else branch here). + span_bug!( + span, + "Accessing `{:?}` with the kind `{:?}` shouldn't be possible", + place, + kind, + ); } return false; } diff --git a/src/librustc_mir/transform/elaborate_drops.rs b/src/librustc_mir/transform/elaborate_drops.rs index a9c66b3c8c6d1..d7b6810a8659a 100644 --- a/src/librustc_mir/transform/elaborate_drops.rs +++ b/src/librustc_mir/transform/elaborate_drops.rs @@ -28,17 +28,7 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops { let param_env = tcx.param_env(src.def_id()).with_reveal_all(); let move_data = match MoveData::gather_moves(body, tcx) { Ok(move_data) => move_data, - Err((move_data, _move_errors)) => { - // The only way we should be allowing any move_errors - // in here is if we are in the migration path for the - // NLL-based MIR-borrowck. - // - // If we are in the migration path, we have already - // reported these errors as warnings to the user. So - // we will just ignore them here. - assert!(tcx.migrate_borrowck()); - move_data - } + Err(_) => bug!("No `move_errors` should be allowed in MIR borrowck"), }; let elaborate_patch = { let body = &*body; diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index ac291c2996d06..0da1f3a1affd1 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -291,10 +291,6 @@ fn optimized_mir(tcx: TyCtxt<'_>, def_id: DefId) -> &Body<'_> { // execute before we can steal. tcx.ensure().mir_borrowck(def_id); - if tcx.use_ast_borrowck() { - tcx.ensure().borrowck(def_id); - } - let (body, _) = tcx.mir_validated(def_id); let mut body = body.steal(); run_optimization_passes(tcx, &mut body, def_id, None); From a07b030998d4359b77aa4721855b05f36cef9b78 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Sep 2019 02:32:50 +0200 Subject: [PATCH 07/10] Remove AccessKind::Move. --- src/librustc_mir/borrow_check/mutability_errors.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/librustc_mir/borrow_check/mutability_errors.rs b/src/librustc_mir/borrow_check/mutability_errors.rs index 33520b6755ca4..d6b91373ab825 100644 --- a/src/librustc_mir/borrow_check/mutability_errors.rs +++ b/src/librustc_mir/borrow_check/mutability_errors.rs @@ -18,7 +18,6 @@ use rustc_errors::Applicability; pub(super) enum AccessKind { MutableBorrow, Mutate, - Move, } impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { @@ -124,7 +123,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { if let Some(desc) = access_place_desc { item_msg = format!("`{}`", desc); reason = match error_access { - AccessKind::Move | AccessKind::Mutate => format!(" which is behind {}", pointer_type), AccessKind::MutableBorrow => { format!(", as it is behind {}", pointer_type) @@ -194,12 +192,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let acted_on; let span = match error_access { - AccessKind::Move => { - err = self.cannot_move_out_of(span, &(item_msg + &reason)); - err.span_label(span, "cannot move"); - err.buffer(&mut self.errors_buffer); - return; - } AccessKind::Mutate => { err = self.cannot_assign(span, &(item_msg + &reason)); act = "assign"; From 0022baae0042b1f052a08f8a06f0015f1dd8a79f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 26 Sep 2019 02:51:30 +0200 Subject: [PATCH 08/10] Inline the remaining hir::Arm::top_pats_hack --- src/librustc/hir/mod.rs | 11 ----------- src/librustc_mir/hair/pattern/check_match.rs | 15 +++++++++++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs index 01cb5cc9bc105..6bfe6de63f5f8 100644 --- a/src/librustc/hir/mod.rs +++ b/src/librustc/hir/mod.rs @@ -1291,17 +1291,6 @@ pub struct Arm { pub body: P, } -impl Arm { - // HACK(or_patterns; Centril | dlrobertson): Remove this and - // correctly handle each case in which this method is used. - pub fn top_pats_hack(&self) -> &[P] { - match &self.pat.kind { - PatKind::Or(pats) => pats, - _ => std::slice::from_ref(&self.pat), - } - } -} - #[derive(RustcEncodable, RustcDecodable, Debug, HashStable)] pub enum Guard { If(P), diff --git a/src/librustc_mir/hair/pattern/check_match.rs b/src/librustc_mir/hair/pattern/check_match.rs index b57e57ba3ec95..5b76d24a4d35c 100644 --- a/src/librustc_mir/hair/pattern/check_match.rs +++ b/src/librustc_mir/hair/pattern/check_match.rs @@ -140,10 +140,17 @@ impl<'tcx> MatchVisitor<'_, 'tcx> { let mut have_errors = false; let inlined_arms : Vec<(Vec<_>, _)> = arms.iter().map(|arm| ( - arm.top_pats_hack().iter().map(|pat| { - let mut patcx = PatCtxt::new(self.tcx, - self.param_env.and(self.identity_substs), - self.tables); + // HACK(or_patterns; Centril | dlrobertson): Remove this and + // correctly handle exhaustiveness checking for nested or-patterns. + match &arm.pat.kind { + hir::PatKind::Or(pats) => pats, + _ => std::slice::from_ref(&arm.pat), + }.iter().map(|pat| { + let mut patcx = PatCtxt::new( + self.tcx, + self.param_env.and(self.identity_substs), + self.tables + ); patcx.include_lint_checks(); let pattern = expand_pattern(cx, patcx.lower_pattern(&pat)); if !patcx.errors.is_empty() { From 99204028acce6b9ec3b93e361eb04667ea8a09c8 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 27 Sep 2019 09:02:18 +0200 Subject: [PATCH 09/10] -Z unpretty message: include expanded,hygiene --- src/librustc_driver/pretty.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustc_driver/pretty.rs b/src/librustc_driver/pretty.rs index 3382b70b35b2d..0de5b700b4faa 100644 --- a/src/librustc_driver/pretty.rs +++ b/src/librustc_driver/pretty.rs @@ -101,9 +101,9 @@ pub fn parse_pretty(sess: &Session, if extended { sess.fatal(&format!("argument to `unpretty` must be one of `normal`, \ `expanded`, `identified`, `expanded,identified`, \ - `everybody_loops`, `hir`, `hir,identified`, \ - `hir,typed`, `hir-tree`, `mir` or `mir-cfg`; \ - got {}", + `expanded,hygiene`, `everybody_loops`, \ + `hir`, `hir,identified`, `hir,typed`, `hir-tree`, \ + `mir` or `mir-cfg`; got {}", name)); } else { sess.fatal(&format!("argument to `pretty` must be one of `normal`, `expanded`, \ From 001357f97197797bec534cc5ec5ae6fabcc01e21 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 27 Sep 2019 20:14:47 +0200 Subject: [PATCH 10/10] --bless --compare-mode=nll --- ...-45696-scribble-on-boxed-borrow.nll.stderr | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/test/ui/issues/issue-45696-scribble-on-boxed-borrow.nll.stderr diff --git a/src/test/ui/issues/issue-45696-scribble-on-boxed-borrow.nll.stderr b/src/test/ui/issues/issue-45696-scribble-on-boxed-borrow.nll.stderr deleted file mode 100644 index db0a1719922c8..0000000000000 --- a/src/test/ui/issues/issue-45696-scribble-on-boxed-borrow.nll.stderr +++ /dev/null @@ -1,33 +0,0 @@ -error[E0713]: borrow may still be in use when destructor runs - --> $DIR/issue-45696-scribble-on-boxed-borrow.rs:34:5 - | -LL | fn scribbled<'a>(s: Scribble<'a>) -> &'a mut u32 { - | -- lifetime `'a` defined here -LL | &mut *s.0 - | ^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a` -LL | } - | - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait - -error[E0713]: borrow may still be in use when destructor runs - --> $DIR/issue-45696-scribble-on-boxed-borrow.rs:39:5 - | -LL | fn boxed_scribbled<'a>(s: Box>) -> &'a mut u32 { - | -- lifetime `'a` defined here -LL | &mut *(*s).0 - | ^^^^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a` -LL | } - | - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait - -error[E0713]: borrow may still be in use when destructor runs - --> $DIR/issue-45696-scribble-on-boxed-borrow.rs:44:5 - | -LL | fn boxed_boxed_scribbled<'a>(s: Box>>) -> &'a mut u32 { - | -- lifetime `'a` defined here -LL | &mut *(**s).0 - | ^^^^^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a` -LL | } - | - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait - -error: aborting due to 3 previous errors - -For more information about this error, try `rustc --explain E0713`.