Initial version of DefiniteAssignmentTracker.

I think it supports all expressions and statements.

Change-Id: I753bd6ce781f54c1dfd0f15f69fca2f15c2a1877
Reviewed-on: https://dart-review.googlesource.com/c/87423
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart b/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart
new file mode 100644
index 0000000..0b1cd1d
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart
@@ -0,0 +1,793 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:meta/meta.dart';
+
+/// Object that tracks "assigned" status for local variables in a function body.
+///
+/// The client should create a new instance of tracker for a function body.
+///
+/// Each declared local variable must be added using [add].  If the variable
+/// is assigned at the declaration, it is not actually tracked.
+///
+/// For each read of a local variable [read] should be invoked, and for each
+/// write - [write].  If there is a  "read" of a variable before it is
+/// definitely written, the variable is added to output [readBeforeWritten].
+///
+/// For each AST node that affects definite assignment the client must invoke
+/// corresponding `beginX` and `endX` methods.  They will combine assignment
+/// facts registered in parts of AST. These invocations are expected to be
+/// performed as a part of the resolution pass over the AST.
+///
+/// In the examples below, Dart code is listed on the left, and the set of
+/// calls that need to be made to [DefiniteAssignmentTracker] are listed on
+/// the right.
+///
+///
+/// --------------------------------------------------
+/// Assignments.
+///
+/// When the LHS is a local variable, and the assignment is pure, i.e. uses
+/// operators `=` and `??=`, only the RHS is executed, and then the
+/// variable on the LHS is marked as definitely assigned.
+///
+/// ```dart
+/// int V1;      // add(V1)
+/// int V2;      // add(V2)
+/// V1 = 0;      // write(V1)
+/// V2 = V2;     // read(V2) => readBeforeWritten; write(V2)
+/// V1;          // read(V1) => OK
+/// V2;          // read(V2) => readBeforeWritten (already)
+/// ```
+///
+/// In compound assignments to a local variable, or assignments where the LHS
+/// is not a simple identifier, the LHS is executed first, and then the RHS.
+///
+/// ```dart
+/// int V1;                 // add(V1)
+/// List<int> V2;           // add(V2)
+/// V1 += 1;                // read(V1) => readBeforeWritten; write(V1)
+/// V2[0] = (V2 = [0])[0];  // read(V2) => readBeforeWritten; write(V2)
+/// V1;                     // read(V1) => readBeforeWritten (already)
+/// V2;                     // read(V2) => readBeforeWritten (already)
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Logical expression.
+///
+/// In logical expressions `a && b` or `a || b` only `a` is always executed,
+/// in the enclosing branch.  The expression `b` might not be executed, so its
+/// results are on the exit from the logical expression.
+///
+/// ```dart
+/// int V1;      // add(V1)
+/// int V2;      // add(V2)
+/// (
+///   V1 = 1     // write(V1)
+/// ) > 0
+/// &&           // beginBinaryExpressionLogicalRight()
+/// (
+///   V2 = V1    // read(V1) => OK; write(V2)
+/// ) > 0
+/// ;            // endBinaryExpressionLogicalRight()
+/// V1;          // read(V1) => OK
+/// V2;          // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// assert(C, M)
+///
+/// The `assert` statement is not guaranteed to execute, so assignments in the
+/// condition and the message are discarded.  But assignments in the condition
+/// are visible in the message.
+///
+/// ```dart
+/// bool V;    // add(V)
+/// assert(    // beginAssertStatement()
+///   V        // read(V) => readBeforeWritten
+/// );         // endAssertExpression()
+/// V          // read(V) => readBeforeWritten
+/// ```
+///
+/// ```dart
+/// bool V;      // add(V)
+/// assert(      // beginAssertExpression()
+///   V = true,  // write(V)
+///   "$V",      // read(V) => OK
+/// );           // endAssertExpression()
+/// V            // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// if (E) {} else {}
+///
+/// The variable must be assigned in the `then` branch, and the `else` branch.
+///
+/// The condition `E` contributes into the current branch.
+///
+/// ```dart
+/// int V1;     // add(V1)
+/// int V2;     // add(V2)
+/// int V3;     // add(V3)
+/// if (E)
+/// {           // beginIfStatementThen()
+///   V1 = 0;   // write(V1)
+///   V2 = 0;   // write(V2)
+///   V1;       // read(V1) => OK
+///   V2;       // read(V2) => OK
+/// } else {    // beginIfStatementElse()
+///   V1 = 0;   // write(V1)
+///   V3 = 0;   // write(V3)
+///   V1;       // read(V1) => OK
+///   V3;       // read(V3) => OK
+/// }           // endIfStatement(hasElse: true)
+/// V1;         // read(V1) => OK
+/// V2;         // read(V2) => readBeforeWritten
+/// V3;         // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// while (E) {}
+///
+/// If `E` is not the `true` literal, which is covered below, then the body of
+/// the loop might be not executed at all.  So, the fork is discarded.
+///
+/// The condition `E` contributes into the current branch.
+///
+/// ```dart
+/// int V;      // add(V)
+/// while (     // beginWhileStatement(labels: [])
+///   E
+/// ) {         // beginWhileStatementBody(isTrue: false)
+///   V = 0;    // write(V)
+///   V;        // read(V) => OK
+/// }           // endWhileStatement()
+/// V;          // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// while (true) { ...break... }
+///
+/// Statements `break` and `continue` in loops make the rest of the enclosing
+/// (or labelled) loop ineligible for marking variables definitely assigned
+/// outside of the loop. However it is still OK to mark variables assigned and
+/// use their values in the rest of the loop.
+///
+/// ```dart
+/// int V;
+/// while (             // beginWhileStatement(labels: [])
+///   true
+/// ) {                 // beginWhileStatementBody(isTrue: true)
+///   if (condition) {  // beginIfStatement(hasElse: false)
+///     break;          // handleBreak(while);
+///   }                 // endIfStatement()
+///   V = 0;            // write(V)
+///   V;                // read(V) => OK
+/// }                   // endWhileStatement()
+/// V;                  // read(V) => readBeforeWritten
+/// ```
+///
+/// Nested loops:
+///
+/// ```dart
+/// int V1, V2;
+/// L1: while (         // beginWhileStatement(node)
+///   true
+/// ) {                 // beginWhileStatementBody(isTrue: true)
+///   while (           // beginWhileStatement(labels: [])
+///     true
+///   ) {               // beginWhileStatementBody()
+///     if (C1) {       // beginIfStatement(hasElse: true)
+///       V1 = 0;       // write(V1)
+///     } else {        // beginIfStatementElse()
+///       if (C2) {     // beginIfStatement(hasElse: false)
+///         break L1;   // handleBreak(L1: while)
+///       }             // endIfStatement()
+///       V1 = 0;       // write(V1)
+///     }               // endIfStatement()
+///     V1;             // read(V1) => OK
+///   }                 // endWhileStatement()
+///   V1;               // read(V1) => OK
+///   while (           // beginWhileStatement(node)
+///     true
+///   ) {               // beginWhileStatementBody(isTrue: true)
+///     V2 = 0;         // write(V2)
+///     break;          // handleBreak(while)
+///   }                 // endWhileStatement()
+///   V1;               // read(V1) => OK
+///   V2;               // read(V2) => OK
+/// }                   // endWhileStatement()
+/// V1;                 // read(V1) => readBeforeWritten
+/// V2;                 // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do {} while (E)
+///
+/// The body and the condition always execute, all assignments contribute to
+/// the current branch.  The body is tracked in its own branch, so that if
+/// it has an interruption (`break` or `continue`), we can discard the branch
+/// after or before the condition.
+///
+/// ```dart
+/// int V1;                  // add(V1)
+/// int V2;                  // add(V2)
+/// do {                     // beginDoWhileStatement(node)
+///   V1 = 0;                // write(V1)
+///   V1;                    // read(V1) => OK
+/// } while                  // beginDoWhileStatementCondition()
+///   ((V2 = 0) >= 0)        // write(V2)
+/// ;                        // endDoWhileStatement()
+/// V1;                      // read(V1) => OK
+/// V2;                      // read(V2) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do { ...break... } while (E)
+///
+/// The `break` statement prevents execution of the rest of the body, and
+/// the condition.  So, the branch ends after the condition.
+///
+/// ```dart
+/// int V1;                  // add(V1)
+/// int V2;                  // add(V2)
+/// int V3;                  // add(V3)
+/// do {                     // beginDoWhileStatement(node)
+///   V1 = 0;                // write(V1)
+///   V1;                    // read(V1) => OK
+///   if (C1) break;         // handleBreak(do)
+///   V2 = 0;                // write(V2)
+///   V2;                    // read(V2) => OK
+/// } while                  // beginDoWhileStatementCondition()
+///   ((V3 = 0) >= 0)        // write(V3)
+/// ;                        // endDoWhileStatement()
+/// V1;                      // read(V1) => OK
+/// V2;                      // read(V2) => readBeforeWritten
+/// V3;                      // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do { ...continue... } while (E)
+///
+/// The `continue` statement prevents execution of the rest of the body, but
+/// the condition is always executed.  So, the branch ends before the condition,
+/// and the condition contributes to the enclosing branch.
+///
+/// ```dart
+/// int V1;                  // add(V1)
+/// int V2;                  // add(V2)
+/// int V3;                  // add(V3)
+/// do {                     // beginDoWhileStatement(node)
+///   V1 = 0;                // write(V1)
+///   V1;                    // read(V1) => OK
+///   if (C1) continue;      // handleContinue(do)
+///   V2 = 0;                // write(V2)
+///   V2;                    // read(V2) => OK
+/// } while                  // beginDoWhileStatementCondition()
+///   ((V3 = 0) >= 0)        // write(V3)
+/// ;                        // endDoWhileStatement()
+/// V1;                      // read(V1) => OK
+/// V2;                      // read(V2) => readBeforeWritten
+/// V3;                      // read(V3) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / catch.
+///
+/// The variable must be assigned in the `try` branch and every `catch` branch.
+///
+/// Note, that an improvement is possible, when some first statements in the
+/// `try` branch can be shown to not throw (e.g. `V = 0;`). We could consider
+/// these statements as definitely executed, so producing definite assignments.
+///
+/// ```dart
+/// int V;        // add(V)
+/// try {         // beginTryStatement()
+///   V = f();    // write(V)
+/// }             // endTryStatementBody()
+/// catch (_) {   // beginTryStatementCatchClause()
+///   V = 0;      // write(V)
+/// }             // endTryStatementCatchClause(); endTryStatementCatchClauses()
+/// V;            // read(V) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / finally.
+///
+/// The `finally` clause is always executed, so it is tracked in the branch
+/// that contains the `try` statement.
+///
+/// Without `catch` clauses the `try` clause is always executed to the end,
+/// so also can be tracked in the branch that contains the `try` statement.
+///
+/// ```dart
+/// int V1;     // add(V1)
+/// int V2;     // add(V2)
+/// try {       // beginTryStatement()
+///   V1 = 0;   // write(V1)
+/// }           // endTryStatementBody(); endTryStatementCatchClauses();
+/// finally {
+///   V2 = 0;   // write(V2)
+/// }
+/// V1;         // read(V1) => OK
+/// V2;         // read(V2) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / catch / finally.
+///
+/// The `finally` clause is always executed, so it is tracked in the branch
+/// that contains the `try` statement.
+///
+/// The `try` and `catch` branches are tracked as without `finally`.
+///
+///
+/// --------------------------------------------------
+/// switch (E) { case E1: ... default: }
+///
+/// The variable must be assigned in every `case` branch and must have the
+/// `default` branch.  If the `default` branch is missing, then the `switch`
+/// does not definitely cover all possible values of `E`, so the variable is
+/// not definitely assigned.
+///
+/// The expression `E` contributes into the current branch.
+///
+/// ```dart
+/// int V;      // add(V)
+/// switch      // beginSwitchStatement()
+/// (E) {       // endSwitchStatementExpression()
+///   case 1:   // beginSwitchStatementMember()
+///     V = 0;  // write(V)
+///     break;  // handleBreak(switch)
+///   default:  // beginSwitchStatementMember()
+///     V = 0;  // write(V); handleBreak(switch)
+/// }           // endSwitchStatement(hasDefault: true)
+/// V;          // read(V) => OK
+/// ```
+///
+/// The presence of a `continue L` statement in switch / case is analyzed in an
+/// approximate way; if a given variable is not written to in all case bodies,
+/// it is considered "not definitely assigned", even though a full basic block
+/// analysis would show that it is definitely assigned.
+///
+/// ```dart
+/// int V;           // add(V)
+/// switch           // beginSwitchStatement()
+/// (E) {            // endSwitchStatementExpression()
+///   L: case 1:     // beginSwitchStatementMember()
+///     V = 0;       // write(V)
+///     break;       // handleBreak(switch)
+///   case 2:        // beginSwitchStatementMember()
+///     continue L;  // handleContinue(switch)
+///   default:       // beginSwitchStatementMember()
+///     V = 0;       // write(V); handleBreak(switch)
+/// }                // endSwitchStatement(hasDefault: true)
+/// V;               // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// For each.
+///
+/// The iterable might be empty, so the body might be not executed, so writes
+/// in the body are discarded.  But writes in the iterable expressions are
+/// always executed.
+///
+/// ```dart
+/// int V1;     // add(V1)
+/// int V2;     // add(V2)
+/// for (var _ in (V1 = [0, 1, 2]))  // beginForEachStatement(node)
+/// {                                // beginForEachStatementBody()
+///   V2 = 0;                        // write(V1)
+/// }                                // endForEachStatement();
+/// V1;         // read(V1) => OK
+/// V2;         // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// For statement.
+///
+/// Very similar to `while` statement.  The initializer and condition parts
+/// are always executed, so contribute to definite assignments.  The body and
+/// updaters might be not executed, so writes in them are discarded.  The
+/// updaters are executed after the body, so writes in the body are visible in
+/// the updaters, correspondingly AST portions should be visited, and
+/// [beginForEachStatementBody] should be before [beginForStatementUpdaters],
+/// and followed with [endForStatement].
+///
+/// ```dart
+/// int V1;     // 1. add(V1)
+/// int V2;     // 2. add(V2)
+/// int V3;     // 3. add(V3)
+/// int V4;     // 4. add(V4)
+/// for (                //  5. beginForStatement(node)
+///   var _ = (V1 = 0);  //  6. write(V1)
+///   (V2 = 0) >= 0;     //  7. write(V2)
+///   V3 = 0             // 10. beginForStatementUpdaters(); write(V3)
+/// ) {                  //  8. beginForStatementBody()
+///   V4 = 0;            //  9. write(V4)
+/// }                    // 11. endForStatement()
+/// V1;         // 12. read(V1) => OK
+/// V2;         // 13. read(V2) => OK
+/// V3;         // 14. read(V3) => readBeforeWritten
+/// V4;         // 15. read(V4) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Function expressions - local functions and closures.
+///
+/// A function expression, e.g. a closure passed as an argument of an
+/// invocation, might be invoked synchronously, asynchronously, or never.
+/// So, all local variables that are read in the function expression must be
+/// definitely assigned, but any writes should be discarded on exit from the
+/// function expression.
+///
+/// ```dart
+/// int V1;     // add(V1)
+/// int V2;     // add(V2)
+/// int V3;     // add(V2)
+/// V1 = 0;     // write(V1)
+/// void f() {  // beginFunctionExpression()
+///   V1;       // read(V1) => OK
+///   V2;       // read(V2) => readBeforeWritten
+///   V3 = 0;   // write(V1)
+/// }           // endFunctionExpression();
+/// V2 = 0;     // write(V2)
+/// f();
+/// V1;         // read(V1) => OK
+/// V2;         // read(V2) => OK
+/// V3;         // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Definite exit.
+///
+/// If a definite exit is reached, e.g. a `return` or a `throw` statement,
+/// then all the variables are vacuously definitely assigned.
+///
+/// ```dart
+/// int V;      // add(V)
+/// if (E) {
+/// {           // beginIfStatementThen()
+///   V = 0;    // write(V)
+/// } else {    // beginIfStatementElse()
+///   return;   // handleExit()
+/// }           // endIfStatement(hasElse: true)
+/// V;          // read(V) => OK
+/// ```
+class DefiniteAssignmentTracker {
+  /// The output list of variables that were read before they were written.
+  final List<LocalVariableElement> readBeforeWritten = [];
+
+  /// The stack of sets of variables that are not definitely assigned.
+  final List<_ElementSet> _stack = [];
+
+  /// The mapping from labeled [Statement]s to the index in the [_stack]
+  /// where the first related element is located.  The number of elements
+  /// is statement specific.
+  final Map<Statement, int> _statementToStackIndex = {};
+
+  /// The current set of variables that are not definitely assigned.
+  _ElementSet _current = _ElementSet.empty;
+
+  @visibleForTesting
+  bool get isRootBranch {
+    return _stack.isEmpty;
+  }
+
+  /// Add a new [variable], which might be already [assigned].
+  void add(LocalVariableElement variable, {bool assigned: false}) {
+    if (!assigned) {
+      _current = _current.add(variable);
+    }
+  }
+
+  void beginAssertStatement() {
+    _stack.add(_current);
+  }
+
+  void beginBinaryExpressionLogicalRight() {
+    _stack.add(_current);
+  }
+
+  void beginConditionalExpressionElse() {
+    var afterCondition = _stack.last;
+    _stack.last = _current; // after then
+    _current = afterCondition;
+  }
+
+  void beginConditionalExpressionThen() {
+    _stack.add(_current); // after condition
+  }
+
+  void beginDoWhileStatement(DoStatement statement) {
+    _statementToStackIndex[statement] = _stack.length;
+    _stack.add(_ElementSet.empty); // break set
+    _stack.add(_ElementSet.empty); // continue set
+  }
+
+  void beginDoWhileStatementCondition() {
+    var continueSet = _stack.removeLast();
+    // If there was a `continue`, use it.
+    // If there was not any, use the current.
+    _current = _current.union(continueSet);
+  }
+
+  void beginForEachStatement(ForEachStatement statement) {
+    // Not strongly necessary, because we discard everything anyway.
+    // Just for consistency, so that `break` is handled without `null`.
+    _statementToStackIndex[statement] = _stack.length;
+  }
+
+  void beginForEachStatementBody() {
+    _stack.add(_current);
+  }
+
+  void beginForStatement(ForStatement statement) {
+    // Not strongly necessary, because we discard everything anyway.
+    // Just for consistency, so that `break` is handled without `null`.
+    _statementToStackIndex[statement] = _stack.length;
+  }
+
+  void beginForStatementBody() {
+    _stack.add(_current); // break set
+    _stack.add(_ElementSet.empty); // continue set
+  }
+
+  void beginForStatementUpdaters() {
+    var continueSet = _stack.last;
+    _current = _current.union(continueSet);
+  }
+
+  void beginFunctionExpression() {
+    _stack.add(_current);
+  }
+
+  void beginIfStatementElse() {
+    var enclosing = _stack.last;
+    _stack.last = _current;
+    _current = enclosing;
+  }
+
+  void beginIfStatementThen() {
+    _stack.add(_current);
+  }
+
+  void beginSwitchStatement(SwitchStatement statement) {
+    _statementToStackIndex[statement] = _stack.length;
+    _stack.add(_ElementSet.empty); // break set
+    _stack.add(_ElementSet.empty); // continue set (placeholder)
+  }
+
+  void beginSwitchStatementMember() {
+    _current = _stack.last; // before cases
+  }
+
+  void beginTryStatement() {
+    _stack.add(_current); // before body, for catches
+  }
+
+  void beginTryStatementCatchClause() {
+    _current = _stack[_stack.length - 2]; // before body
+  }
+
+  void beginWhileStatement(WhileStatement statement) {
+    _statementToStackIndex[statement] = _stack.length;
+  }
+
+  void beginWhileStatementBody(bool isTrue) {
+    _stack.add(isTrue ? _ElementSet.empty : _current); // break set
+    _stack.add(_ElementSet.empty); // continue set
+  }
+
+  void endAssertStatement() {
+    _current = _stack.removeLast();
+  }
+
+  void endBinaryExpressionLogicalRight() {
+    _current = _stack.removeLast();
+  }
+
+  void endConditionalExpression() {
+    var thenSet = _stack.removeLast();
+    var elseSet = _current;
+    _current = thenSet.union(elseSet);
+  }
+
+  void endDoWhileStatement() {
+    var breakSet = _stack.removeLast();
+    // If there was a `break`, use it.
+    // If there was not any, use the current.
+    _current = _current.union(breakSet);
+  }
+
+  void endForEachStatement() {
+    _current = _stack.removeLast();
+  }
+
+  void endForStatement() {
+    _stack.removeLast(); // continue set
+    _current = _stack.removeLast(); // break set, before body
+  }
+
+  void endFunctionExpression() {
+    _current = _stack.removeLast();
+  }
+
+  void endIfStatement(bool hasElse) {
+    if (hasElse) {
+      var thenSet = _stack.removeLast();
+      var elseSet = _current;
+      _current = thenSet.union(elseSet);
+    } else {
+      var afterCondition = _stack.removeLast();
+      _current = afterCondition;
+    }
+  }
+
+  void endSwitchStatement(bool hasDefault) {
+    var beforeCasesSet = _stack.removeLast();
+    _stack.removeLast(); // continue set
+    var breakSet = _stack.removeLast();
+    if (hasDefault) {
+      _current = breakSet;
+    } else {
+      _current = beforeCasesSet;
+    }
+  }
+
+  void endSwitchStatementExpression() {
+    _stack.add(_current); // before cases
+  }
+
+  void endTryStatementBody() {
+    _stack.add(_current); // union of body and catches
+  }
+
+  void endTryStatementCatchClause() {
+    _stack.last = _stack.last.union(_current); // union of body and catches
+  }
+
+  void endTryStatementCatchClauses() {
+    _current = _stack.removeLast(); // union of body and catches
+    _stack.removeLast(); // before body
+  }
+
+  void endWhileStatement() {
+    _stack.removeLast(); // continue set
+    _current = _stack.removeLast(); // break set
+  }
+
+  /// Handle a `break` statement with the given [target].
+  void handleBreak(AstNode target) {
+    var breakSetIndex = _statementToStackIndex[target];
+    if (breakSetIndex != null) {
+      _stack[breakSetIndex] = _stack[breakSetIndex].union(_current);
+    }
+    _current = _ElementSet.empty;
+  }
+
+  /// Handle a `continue` statement with the given [target].
+  void handleContinue(AstNode target) {
+    var breakSetIndex = _statementToStackIndex[target];
+    if (breakSetIndex != null) {
+      var continueSetIndex = breakSetIndex + 1;
+      _stack[continueSetIndex] = _stack[continueSetIndex].union(_current);
+    }
+    _current = _ElementSet.empty;
+  }
+
+  /// Register the fact that the current branch definitely exists, e.g. returns
+  /// from the body, throws an exception, etc.  So, all variables in the branch
+  /// are vacuously definitely assigned.
+  void handleExit() {
+    _current = _ElementSet.empty;
+  }
+
+  /// Register read of the given [variable] in the current branch.
+  void read(LocalVariableElement variable) {
+    if (_current.contains(variable)) {
+      // Add to the list of violating variables, if not there yet.
+      for (var i = 0; i < readBeforeWritten.length; ++i) {
+        var violatingVariable = readBeforeWritten[i];
+        if (identical(violatingVariable, variable)) {
+          return;
+        }
+      }
+      readBeforeWritten.add(variable);
+    }
+  }
+
+  /// Register write of the given [variable] in the current branch.
+  void write(LocalVariableElement variable) {
+    _current = _current.remove(variable);
+  }
+}
+
+/// List based immutable set of elements.
+class _ElementSet {
+  static final empty = _ElementSet._(
+    List<LocalVariableElement>(0),
+  );
+
+  final List<LocalVariableElement> elements;
+
+  _ElementSet._(this.elements);
+
+  _ElementSet add(LocalVariableElement addedElement) {
+    if (contains(addedElement)) {
+      return this;
+    }
+
+    var length = elements.length;
+    var newElements = List<LocalVariableElement>(length + 1);
+    for (var i = 0; i < length; ++i) {
+      newElements[i] = elements[i];
+    }
+    newElements[length] = addedElement;
+    return _ElementSet._(newElements);
+  }
+
+  bool contains(LocalVariableElement element) {
+    var length = elements.length;
+    for (var i = 0; i < length; ++i) {
+      if (identical(elements[i], element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  _ElementSet remove(LocalVariableElement removedElement) {
+    if (!contains(removedElement)) {
+      return this;
+    }
+
+    var length = elements.length;
+    if (length == 1) {
+      return empty;
+    }
+
+    var newElements = List<LocalVariableElement>(length - 1);
+    var newIndex = 0;
+    for (var i = 0; i < length; ++i) {
+      var element = elements[i];
+      if (!identical(element, removedElement)) {
+        newElements[newIndex++] = element;
+      }
+    }
+
+    return _ElementSet._(newElements);
+  }
+
+  _ElementSet union(_ElementSet other) {
+    if (other == null || other.elements.isEmpty) {
+      return this;
+    }
+
+    var result = this;
+    var otherElements = other.elements;
+    for (var i = 0; i < otherElements.length; ++i) {
+      var otherElement = otherElements[i];
+      result = result.add(otherElement);
+    }
+    return result;
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
index 6bf04e5..a2abdd1 100644
--- a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
@@ -65,7 +65,7 @@
     assertType(assignment, 'num'); // num + int = num
 
     SimpleIdentifier left = assignment.leftHandSide;
-    assertElement(left, findElement.localVar('x'));
+    assertElement(left, findElement.localVar('v'));
     assertType(left, 'num');
 
     Expression right = assignment.rightHandSide;
diff --git a/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart
new file mode 100644
index 0000000..c9d840d
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart
@@ -0,0 +1,1533 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/resolver/definite_assignment.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'driver_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(DefiniteAssignmentTrackerTest);
+  });
+}
+
+@reflectiveTest
+class DefiniteAssignmentTrackerTest extends DriverResolutionTest {
+  DefiniteAssignmentTracker tracker;
+
+  /// Assert that only local variables with the given names are marked as read
+  /// before being written.  All the other local variables are implicitly
+  /// considered definitely assigned.
+  void assertReadBeforeWritten(
+      [String name1, String name2, String name3, String name4]) {
+    var expected = [name1, name2, name3, name4]
+        .where((i) => i != null)
+        .map((name) => findElement.localVar(name))
+        .toList();
+    expect(tracker.readBeforeWritten, unorderedEquals(expected));
+  }
+
+  test_assert() async {
+    await trackCode(r'''
+main() {
+  int v;
+  assert((v = 0) >= 0, v);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftExpression() async {
+    await trackCode(r'''
+main() {
+  List<int> v;
+  v[0] = (v = [1, 2])[1];
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_compound() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v += 1;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_compound_assignInRight() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v += (v = v);
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_eq() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v = 0;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_assignment_leftLocal_pure_eq_self() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v = v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_questionEq() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v ??= 0;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_questionEq_self() async {
+    await trackCode(r'''
+main() {
+  int v;
+  v ??= v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_ifNull_left() async {
+    await trackCode(r'''
+main() {
+  int v;
+  (v = 0) ?? 0;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_ifNull_right() async {
+    await trackCode(r'''
+main(int a) {
+  int v;
+  a ?? (v = 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_logicalAnd_left() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  ((v = 0) >= 0) && c;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_logicalAnd_right() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c && ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_logicalOr_left() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  ((v = 0) >= 0) || c;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_logicalOr_right() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c || ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_plus_left() async {
+    await trackCode(r'''
+main() {
+  int v;
+  (v = 0) + 1;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_plus_right() async {
+    await trackCode(r'''
+main() {
+  int v;
+  1 + (v = 0);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_conditionalExpression_both() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c ? (v = 0) : (v = 0);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_conditionalExpression_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  (v = 0) >= 0 ? 1 : 2;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_conditionalExpression_else() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c ? (v = 0) : 2;
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_conditionalExpression_then() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c ? (v = 0) : 2;
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_break_afterAssignment() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  do {
+    v = 0;
+    v;
+    if (c) break;
+  } while (c);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_doWhile_break_beforeAssignment() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  do {
+    if (c) break;
+    v = 0;
+  } while (c);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_breakOuterFromInner() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2, v3;
+  L1: do {
+    do {
+      v1 = 0;
+      if (c) break L1;
+      v2 = 0;
+      v3 = 0;
+    } while (c);
+    v2;
+  } while (c);
+  v1;
+  v3;
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_doWhile_condition() async {
+    await trackCode(r'''
+main() {
+  int v1, v2;
+  do {
+    v1; // assigned in the condition, but not yet
+  } while ((v1 = 0) + (v2 = 0) >= 0);
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1');
+  }
+
+  test_doWhile_condition_break() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  do {
+    if (c) break;
+  } while ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_condition_break_continue() async {
+    await trackCode(r'''
+main(bool c1, bool c2) {
+  int v1, v2, v3, v4, v5, v6;
+  do {
+    v1 = 0; // visible outside, visible to the condition
+    if (c1) break;
+    v2 = 0; // not visible outside, visible to the condition
+    v3 = 0; // not visible outside, visible to the condition
+    if (c2) continue;
+    v4 = 0; // not visible
+    v5 = 0; // not visible
+  } while ((v6 = v1 + v2 + v4) == 0); // has break => v6 is not visible outside
+  v1;
+  v3;
+  v5;
+  v6;
+}
+''');
+    assertReadBeforeWritten('v3', 'v4', 'v5', 'v6');
+  }
+
+  test_doWhile_condition_continue() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2, v3, v4;
+  do {
+    v1 = 0; // visible outside, visible to the condition
+    if (c) continue;
+    v2 = 0; // not visible
+    v3 = 0; // not visible
+  } while ((v4 = v1 + v2) == 0); // no break => v4 visible outside
+  v1;
+  v3;
+  v4;
+}
+''');
+    assertReadBeforeWritten('v2', 'v3');
+  }
+
+  test_doWhile_continue_beforeAssignment() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  do {
+    if (c) continue;
+    v = 0;
+  } while (c);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_for_body() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  for (; c;) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_for_break() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (; c;) {
+    v1 = 0;
+    if (c) break;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_for_break_updaters() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (; c; v1 + v2) {
+    v1 = 0;
+    if (c) break;
+    v2 = 0;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  for (; (v = 0) >= 0;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_continue() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (; c;) {
+    v1 = 0;
+    if (c) continue;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_for_continue_updaters() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (; c; v1 + v2) {
+    v1 = 0;
+    if (c) continue;
+    v2 = 0;
+  }
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_for_initializer_expression() async {
+    await trackCode(r'''
+main() {
+  int v;
+  for (v = 0;;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_initializer_variable() async {
+    await trackCode(r'''
+main() {
+  int v;
+  for (var t = (v = 0);;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_updaters() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2, v3, v4;
+  for (; c; v1 = 0, v2 = 0, v3 = 0, v4) {
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2', 'v4');
+  }
+
+  test_for_updaters_afterBody() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  for (; c; v) {
+    v = 0;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_forEach() async {
+    await trackCode(r'''
+main() {
+  int v1, v2;
+  for (var _ in (v1 = [0, 1, 2])) {
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_forEach_break() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (var _ in [0, 1, 2]) {
+    v1 = 0;
+    if (c) break;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_forEach_continue() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  for (var _ in [0, 1, 2]) {
+    v1 = 0;
+    if (c) continue;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_functionExpression_closure_read() async {
+    await trackCode(r'''
+main() {
+  int v1, v2;
+  
+  v1 = 0;
+  
+  [0, 1, 2].forEach((t) {
+    v1;
+    v2;
+  });
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_functionExpression_closure_write() async {
+    await trackCode(r'''
+main() {
+  int v;
+  
+  [0, 1, 2].forEach((t) {
+    v = t;
+  });
+
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_functionExpression_localFunction_local() async {
+    await trackCode(r'''
+main() {
+  int v;
+
+  v = 0;
+
+  void f() {
+    int v; // 1
+    v;
+  }
+}
+''');
+    var localV = findNode.simple('v; // 1').staticElement;
+    expect(tracker.readBeforeWritten, unorderedEquals([localV]));
+  }
+
+  test_functionExpression_localFunction_local2() async {
+    await trackCode(r'''
+main() {
+  int v1;
+
+  v1 = 0;
+
+  void f() {
+    int v2, v3;
+    v2 = 0;
+    v1;
+    v2;
+    v3;
+  }
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_functionExpression_localFunction_read() async {
+    await trackCode(r'''
+main() {
+  int v1, v2, v3;
+
+  v1 = 0;
+
+  void f() {
+    v1;
+    v2;
+  }
+
+  v2 = 0;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_functionExpression_localFunction_write() async {
+    await trackCode(r'''
+main() {
+  int v;
+
+  void f() {
+    v = 0;
+  }
+
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  if ((v = 0) >= 0) {
+    v;
+  } else {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_then() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_thenElse_all() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+    v;
+  } else {
+    v = 0;
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_thenElse_else() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    // not assigned
+  } else {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_thenElse_then() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  } else {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_thenElse_then_exit_alwaysThrows() async {
+    addMetaPackage();
+    await trackCode(r'''
+import 'package:meta/meta.dart';
+
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  } else {
+    foo();
+  }
+  v;
+}
+
+@alwaysThrows
+void foo() {}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_thenElse_then_exit_return() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  } else {
+    return;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_thenElse_then_exit_throw() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  } else {
+    throw 42;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_switch_case1_default() async {
+    await trackCode(r'''
+main(int e) {
+  int v;
+  switch (e) {
+    case 1:
+      v = 0;
+      break;
+    case 2:
+      // not assigned
+      break;
+    default:
+      v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_switch_case2_default() async {
+    await trackCode(r'''
+main(int e) {
+  int v1, v2;
+  switch (e) {
+    case 1:
+      v1 = 0;
+      v2 = 0;
+      v1;
+      break;
+    default:
+      v1 = 0;
+      v1;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_switch_case_default_break() async {
+    await trackCode(r'''
+main(int e, bool c) {
+  int v1, v2;
+  switch (e) {
+    case 1:
+      v1 = 0;
+      if (c) break;
+      v2 = 0;
+      break;
+    default:
+      v1 = 0;
+      if (c) break;
+      v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_switch_case_default_continue() async {
+    await trackCode(r'''
+main(int e) {
+  int v;
+  switch (e) {
+    L: case 1:
+      v = 0;
+      break;
+    case 2:
+      continue L;
+    default:
+      v = 0;
+  }
+  v;
+}
+''');
+    // We don't analyze to which `case` we go from `continue L`,
+    // but we don't have to. If all cases assign, then the variable is
+    // not in the `breakSet`. And if there is a case when it is not assigned,
+    // we the variable will be left in the `breakSet`.
+    assertReadBeforeWritten();
+  }
+
+  test_switch_case_noDefault() async {
+    await trackCode(r'''
+main(int e) {
+  int v;
+  switch (e) {
+    case 1:
+      v = 0;
+      break;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_switch_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  switch (v = 0) {}
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryCatch_all() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    f();
+    v = 0;
+  } catch (_) {
+    v = 0;
+  }
+  v;
+}
+
+void f() {}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryCatch_catch() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatch_try() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    v = 0;
+  } catch (_) {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatchFinally_catch() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    v = 0;
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatchFinally_finally() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    // not assigned
+  } finally {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryCatchFinally_try() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    v = 0;
+  } catch (_) {
+    // not assigned
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryFinally_finally() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    // not assigned
+  } finally {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryFinally_try() async {
+    await trackCode(r'''
+main() {
+  int v;
+  try {
+    v = 0;
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  while ((v = 0) >= 0) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_condition_notTrue() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  while (c) {
+    v1 = 0;
+    v2 = 0;
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_while_true_break_afterAssignment() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  while (true) {
+    v1 = 0;
+    v1;
+    if (c) break;
+    v1;
+    v2 = 0;
+    v2;
+  }
+  v1;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_beforeAssignment() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  while (true) {
+    if (c) break;
+    v1 = 0;
+    v2 = 0;
+    v2;
+  }
+  v1;
+}
+''');
+    assertReadBeforeWritten('v1');
+  }
+
+  test_while_true_break_if() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  while (true) {
+    if (c) {
+      v = 0;
+      break;
+    } else {
+      v = 0;
+      break;
+    }
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_if2() async {
+    await trackCode(r'''
+main(bool c) {
+  var v;
+  while (true) {
+    if (c) {
+      break;
+    } else {
+      v = 0;
+    }
+    v;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_if3() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2;
+  while (true) {
+    if (c) {
+      v1 = 0;
+      v2 = 0;
+      if (c) break;
+    } else {
+      if (c) break;
+      v1 = 0;
+      v2 = 0;
+    }
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_while_true_breakOuterFromInner() async {
+    await trackCode(r'''
+main(bool c) {
+  int v1, v2, v3;
+  L1: while (true) {
+    L2: while (true) {
+      v1 = 0;
+      if (c) break L1;
+      v2 = 0;
+      v3 = 0;
+      if (c) break L2;
+    }
+    v2;
+  }
+  v1;
+  v3;
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_while_true_continue() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  while (true) {
+    if (c) continue;
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_noBreak() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  while (true) {
+    // No assignment, but not break.
+    // So, we don't exit the loop.
+    // So, all variables are assigned.
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  /// Resolve the given [code] and track assignments in the unit.
+  Future<void> trackCode(String code) async {
+    addTestFile(code);
+    await resolveTestFile();
+
+    tracker = DefiniteAssignmentTracker();
+
+    var visitor = _AstVisitor(tracker);
+    result.unit.accept(visitor);
+  }
+}
+
+/// [AstVisitor] that drives the [tracker] in the way we expect the resolver
+/// will do in production.
+class _AstVisitor extends RecursiveAstVisitor<void> {
+  final DefiniteAssignmentTracker tracker;
+
+  _AstVisitor(this.tracker);
+
+  @override
+  void visitAssertStatement(AssertStatement node) {
+    tracker.beginAssertStatement();
+    super.visitAssertStatement(node);
+    tracker.endAssertStatement();
+  }
+
+  @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    var left = node.leftHandSide;
+    var right = node.rightHandSide;
+
+    LocalVariableElement localElement;
+    if (left is SimpleIdentifier) {
+      var element = left.staticElement;
+      if (element is LocalVariableElement) {
+        localElement = element;
+      }
+    }
+
+    if (localElement != null) {
+      var isPure = node.operator.type == TokenType.EQ;
+      if (!isPure) {
+        tracker.read(localElement);
+      }
+      right.accept(this);
+      tracker.write(localElement);
+    } else {
+      left.accept(this);
+      right.accept(this);
+    }
+  }
+
+  @override
+  void visitBinaryExpression(BinaryExpression node) {
+    var left = node.leftOperand;
+    var right = node.rightOperand;
+
+    var operator = node.operator.type;
+    var isLogical = operator == TokenType.AMPERSAND_AMPERSAND ||
+        operator == TokenType.BAR_BAR ||
+        operator == TokenType.QUESTION_QUESTION;
+
+    left.accept(this);
+
+    if (isLogical) {
+      tracker.beginBinaryExpressionLogicalRight();
+    }
+
+    right.accept(this);
+
+    if (isLogical) {
+      tracker.endBinaryExpressionLogicalRight();
+    }
+  }
+
+  @override
+  void visitBreakStatement(BreakStatement node) {
+    var target = _getLabelTarget(node, node.label?.staticElement);
+    tracker.handleBreak(target);
+    super.visitBreakStatement(node);
+  }
+
+  @override
+  void visitConditionalExpression(ConditionalExpression node) {
+    var condition = node.condition;
+    var thenExpression = node.thenExpression;
+    var elseExpression = node.elseExpression;
+
+    condition.accept(this);
+
+    tracker.beginConditionalExpressionThen();
+    thenExpression.accept(this);
+
+    tracker.beginConditionalExpressionElse();
+    elseExpression.accept(this);
+
+    tracker.endConditionalExpression();
+  }
+
+  @override
+  void visitContinueStatement(ContinueStatement node) {
+    var target = _getLabelTarget(node, node.label?.staticElement);
+    tracker.handleContinue(target);
+    super.visitContinueStatement(node);
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    var body = node.body;
+    var condition = node.condition;
+
+    tracker.beginDoWhileStatement(node);
+    body.accept(this);
+
+    tracker.beginDoWhileStatementCondition();
+    condition.accept(this);
+
+    tracker.endDoWhileStatement();
+  }
+
+  @override
+  void visitForEachStatement(ForEachStatement node) {
+    var iterable = node.iterable;
+    var body = node.body;
+
+    tracker.beginForEachStatement(node);
+    iterable.accept(this);
+
+    tracker.beginForEachStatementBody();
+    body.accept(this);
+
+    tracker.endForEachStatement();
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    var variables = node.variables;
+    var initialization = node.initialization;
+
+    var condition = node.condition;
+    var updaters = node.updaters;
+    var body = node.body;
+
+    tracker.beginForStatement(node);
+
+    variables?.accept(this);
+    initialization?.accept(this);
+    condition?.accept(this);
+
+    tracker.beginForStatementBody();
+    body?.accept(this);
+
+    tracker.beginForStatementUpdaters();
+    updaters?.accept(this);
+
+    tracker.endForStatement();
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    super.visitFunctionDeclaration(node);
+    if (node.parent is CompilationUnit) {
+      expect(tracker.isRootBranch, isTrue);
+    }
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    tracker.beginFunctionExpression();
+    super.visitFunctionExpression(node);
+    tracker.endFunctionExpression();
+  }
+
+  @override
+  void visitIfStatement(IfStatement node) {
+    var condition = node.condition;
+    var thenStatement = node.thenStatement;
+    var elseStatement = node.elseStatement;
+
+    condition.accept(this);
+
+    tracker.beginIfStatementThen();
+    thenStatement.accept(this);
+
+    if (elseStatement != null) {
+      tracker.beginIfStatementElse();
+      elseStatement.accept(this);
+    }
+
+    tracker.endIfStatement(elseStatement != null);
+  }
+
+  @override
+  void visitMethodInvocation(MethodInvocation node) {
+    super.visitMethodInvocation(node);
+    var element = node.methodName.staticElement;
+    if (element != null && element.hasAlwaysThrows) {
+      tracker.handleExit();
+    }
+  }
+
+  @override
+  void visitReturnStatement(ReturnStatement node) {
+    super.visitReturnStatement(node);
+    tracker.handleExit();
+  }
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    var element = node.staticElement;
+    if (element is LocalVariableElement) {
+      if (node.inGetterContext()) {
+        tracker.read(element);
+      }
+    }
+
+    super.visitSimpleIdentifier(node);
+  }
+
+  @override
+  void visitSwitchStatement(SwitchStatement node) {
+    tracker.beginSwitchStatement(node);
+
+    node.expression.accept(this);
+    tracker.endSwitchStatementExpression();
+
+    var members = node.members;
+    var membersLength = members.length;
+    var hasDefault = false;
+    for (var i = 0; i < membersLength; i++) {
+      var member = members[i];
+      tracker.beginSwitchStatementMember();
+      member.accept(this);
+      // Implicit `break` at the end of `default`.
+      if (member is SwitchDefault) {
+        hasDefault = true;
+        tracker.handleBreak(node);
+      }
+    }
+
+    tracker.endSwitchStatement(hasDefault);
+  }
+
+  @override
+  void visitThrowExpression(ThrowExpression node) {
+    super.visitThrowExpression(node);
+    tracker.handleExit();
+  }
+
+  @override
+  void visitTryStatement(TryStatement node) {
+    var body = node.body;
+    var catchClauses = node.catchClauses;
+
+    tracker.beginTryStatement();
+
+    body.accept(this);
+    tracker.endTryStatementBody();
+
+    var catchLength = catchClauses.length;
+    for (var i = 0; i < catchLength; ++i) {
+      var catchClause = catchClauses[i];
+      tracker.beginTryStatementCatchClause();
+      catchClause.accept(this);
+      tracker.endTryStatementCatchClause();
+    }
+
+    tracker.endTryStatementCatchClauses();
+
+    node.finallyBlock?.accept(this);
+  }
+
+  @override
+  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+    var variables = node.variables.variables;
+    for (var i = 0; i < variables.length; ++i) {
+      var variable = variables[i];
+      tracker.add(variable.declaredElement,
+          assigned: variable.initializer != null);
+    }
+
+    super.visitVariableDeclarationStatement(node);
+  }
+
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    var condition = node.condition;
+    var body = node.body;
+
+    tracker.beginWhileStatement(node);
+    condition.accept(this);
+
+    var conditionIsLiteralTrue = condition is BooleanLiteral && condition.value;
+    tracker.beginWhileStatementBody(conditionIsLiteralTrue);
+    body.accept(this);
+
+    tracker.endWhileStatement();
+  }
+
+  /// This code has OK performance for tests, but think if there is something
+  /// better when using in production.
+  AstNode _getLabelTarget(AstNode node, LabelElement element) {
+    for (; node != null; node = node.parent) {
+      if (node is DoStatement ||
+          node is ForEachStatement ||
+          node is ForStatement ||
+          node is SwitchStatement ||
+          node is WhileStatement) {
+        if (element == null) {
+          return node;
+        }
+        var parent = node.parent;
+        if (parent is LabeledStatement) {
+          for (var nodeLabel in parent.labels) {
+            if (identical(nodeLabel.label.staticElement, element)) {
+              return node;
+            }
+          }
+        }
+      }
+      if (element != null && node is SwitchStatement) {
+        for (var member in node.members) {
+          for (var nodeLabel in member.labels) {
+            if (identical(nodeLabel.label.staticElement, element)) {
+              return node;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart b/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
index dd1137b..081ef0e 100644
--- a/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
@@ -32,6 +32,14 @@
   /// Override this to change the analysis options for a given set of tests.
   AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl();
 
+  void addMetaPackage() {
+    newFile('/.pub-cache/meta/lib/meta.dart', content: r'''
+library meta;
+
+const alwaysThrows = const Object();
+''');
+  }
+
   @override
   Future<TestAnalysisResult> resolveFile(String path) async {
     var result = await driver.getResult(path);
@@ -52,6 +60,7 @@
       'test': [getFolder('/test/lib')],
       'aaa': [getFolder('/aaa/lib')],
       'bbb': [getFolder('/bbb/lib')],
+      'meta': [getFolder('/.pub-cache/meta/lib')],
     };
 
     driver = new AnalysisDriver(
diff --git a/pkg/analyzer/test/src/dart/resolution/find_element.dart b/pkg/analyzer/test/src/dart/resolution/find_element.dart
index cbf7def..d0ac7bf 100644
--- a/pkg/analyzer/test/src/dart/resolution/find_element.dart
+++ b/pkg/analyzer/test/src/dart/resolution/find_element.dart
@@ -225,7 +225,7 @@
     unit.accept(new FunctionAstVisitor(
       variableDeclaration: (node) {
         var element = node.declaredElement;
-        if (element is LocalVariableElement) {
+        if (element is LocalVariableElement && element.name == name) {
           if (result != null) {
             fail('Not unique: $name');
           }
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index 52e8428..7e7c6b3 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -284,15 +284,6 @@
         invocationImpl.methodNameType, expectedMethodNameType);
   }
 
-  void assertPropertyAccess(
-    PropertyAccess access,
-    Element expectedElement,
-    String expectedType,
-  ) {
-    assertElement(access.propertyName, expectedElement);
-    assertType(access, expectedType);
-  }
-
   void assertNamedParameterRef(String search, String name) {
     var ref = findNode.simple(search);
     assertElement(ref, findElement.parameter(name));
@@ -303,6 +294,15 @@
     assertTestErrors(const <ErrorCode>[]);
   }
 
+  void assertPropertyAccess(
+    PropertyAccess access,
+    Element expectedElement,
+    String expectedType,
+  ) {
+    assertElement(access.propertyName, expectedElement);
+    assertType(access, expectedType);
+  }
+
   void assertSuperExpression(SuperExpression superExpression) {
     // TODO(scheglov) I think `super` does not have type itself.
     // It is just a signal to look for implemented method in the supertype.