Version 2.16.0-43.0.dev

Merge commit '4c31413f1ac42e471a8ae0da31574ce7b83f6f4a' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 2346ae1..0b6145c 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -370,10 +370,10 @@
     }
 
     var errorCode = diagnostic.errorCode;
+    var codeName = errorCode.name;
     try {
-      var codeName = errorCode.name;
       if (errorCode is LintCode) {
-        var generators = FixProcessor.lintProducerMap[errorCode.name] ?? [];
+        var generators = FixProcessor.lintProducerMap[codeName] ?? [];
         await bulkApply(generators, codeName);
       } else {
         var generators = FixProcessor.nonLintProducerMap[errorCode] ?? [];
@@ -391,9 +391,7 @@
       }
     } catch (e, s) {
       throw CaughtException.withMessage(
-          'Exception generating fix for ${errorCode.name} in ${result.path}',
-          e,
-          s);
+          'Exception generating fix for $codeName in ${result.path}', e, s);
     }
   }
 
@@ -405,7 +403,7 @@
     await _applyProducer(context, producer);
     var newHash = computeChangeHash();
     if (newHash != oldHash) {
-      changeMap.add(context.resolvedResult.path, code);
+      changeMap.add(context.resolvedResult.path, code.toLowerCase());
     }
   }
 
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index b899fbe..3c5f245 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -275,13 +275,8 @@
           performGlobalTypeInference(closedWorldAndIndices.closedWorld);
       var indices = closedWorldAndIndices.indices;
       if (options.writeDataUri != null) {
-        if (options.noClosedWorldInData) {
-          serializationTask.serializeGlobalTypeInference(
-              globalTypeInferenceResults, indices);
-        } else {
-          serializationTask
-              .serializeGlobalTypeInferenceLegacy(globalTypeInferenceResults);
-        }
+        serializationTask.serializeGlobalTypeInference(
+            globalTypeInferenceResults, indices);
         return;
       }
       await generateJavaScriptCode(globalTypeInferenceResults,
@@ -301,12 +296,6 @@
               closedWorldAndIndices);
       await generateJavaScriptCode(globalTypeInferenceResults,
           indices: closedWorldAndIndices.indices);
-    } else if (options.readDataUri != null) {
-      // TODO(joshualitt) delete and clean up after google3 roll
-      var globalTypeInferenceResults =
-          await serializationTask.deserializeGlobalTypeInferenceLegacy(
-              environment, abstractValueStrategy);
-      await generateJavaScriptCode(globalTypeInferenceResults);
     } else {
       KernelResult result = await kernelLoader.load();
       reporter.log("Kernel load complete");
@@ -543,16 +532,6 @@
       if (stopAfterClosedWorld || options.stopAfterProgramSplit) return;
       GlobalTypeInferenceResults globalInferenceResults =
           performGlobalTypeInference(closedWorld);
-      if (options.writeDataUri != null) {
-        // TODO(joshualitt) delete after google3 roll.
-        if (options.noClosedWorldInData) {
-          throw '"no-closed-world-in-data" requires serializing closed world.';
-        } else {
-          serializationTask
-              .serializeGlobalTypeInferenceLegacy(globalInferenceResults);
-        }
-        return;
-      }
       if (options.testMode) {
         globalInferenceResults =
             globalTypeInferenceResultsTestMode(globalInferenceResults);
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index 4c33c5a..156cd9d 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -548,7 +548,7 @@
         setWriteModularAnalysis),
     OptionHandler('${Flags.readData}|${Flags.readData}=.+', setReadData),
     OptionHandler('${Flags.writeData}|${Flags.writeData}=.+', setWriteData),
-    OptionHandler(Flags.noClosedWorldInData, passThrough),
+    OptionHandler(Flags.noClosedWorldInData, ignoreOption),
     OptionHandler('${Flags.readClosedWorld}|${Flags.readClosedWorld}=.+',
         setReadClosedWorld),
     OptionHandler('${Flags.writeClosedWorld}|${Flags.writeClosedWorld}=.+',
@@ -825,9 +825,7 @@
       if (readStrategy == ReadStrategy.fromCodegen) {
         fail("Cannot read and write serialized codegen simultaneously.");
       }
-      // TODO(joshualitt) cleanup after google3 roll.
-      if (readStrategy != ReadStrategy.fromData &&
-          readStrategy != ReadStrategy.fromDataAndClosedWorld) {
+      if (readStrategy != ReadStrategy.fromDataAndClosedWorld) {
         fail("Can only write serialized codegen from serialized data.");
       }
       if (codegenShards == null) {
@@ -855,10 +853,7 @@
       options.add('${Flags.readClosedWorld}=${readClosedWorldUri}');
       break;
     case ReadStrategy.fromData:
-      // TODO(joshualitt): fail after Google3 roll.
-      // fail("Must read from closed world and data.");
-      readDataUri ??= Uri.base.resolve('$scriptName.data');
-      options.add('${Flags.readData}=${readDataUri}');
+      fail("Must read from closed world and data.");
       break;
     case ReadStrategy.fromDataAndClosedWorld:
       readClosedWorldUri ??= Uri.base.resolve('$scriptName.world');
@@ -868,19 +863,6 @@
       break;
     case ReadStrategy.fromCodegen:
     case ReadStrategy.fromCodegenAndData:
-      // TODO(joshualitt): fall through to fail after google3 roll.
-      readDataUri ??= Uri.base.resolve('$scriptName.data');
-      options.add('${Flags.readData}=${readDataUri}');
-      readCodegenUri ??= Uri.base.resolve('$scriptName.code');
-      options.add('${Flags.readCodegen}=${readCodegenUri}');
-      if (codegenShards == null) {
-        fail("Cannot write serialized codegen without setting "
-            "${Flags.codegenShards}.");
-      } else if (codegenShards <= 0) {
-        fail("${Flags.codegenShards} must be a positive integer.");
-      }
-      options.add('${Flags.codegenShards}=$codegenShards');
-      break;
     case ReadStrategy.fromCodegenAndClosedWorld:
       fail("Must read from closed world, data, and codegen");
       break;
@@ -941,13 +923,7 @@
         summary = 'Data files $input and $dataInput ';
         break;
       case ReadStrategy.fromData:
-        // TODO(joshualitt): fail after google3 roll.
-        //fail("Must read from closed world and data.");
-        inputName = 'bytes data';
-        inputSize = inputProvider.dartCharactersRead;
-        String dataInput =
-            fe.relativizeUri(Uri.base, readDataUri, Platform.isWindows);
-        summary = 'Data files $input and $dataInput ';
+        fail("Must read from closed world and data.");
         break;
       case ReadStrategy.fromDataAndClosedWorld:
         inputName = 'bytes data';
@@ -960,16 +936,6 @@
         break;
       case ReadStrategy.fromCodegen:
       case ReadStrategy.fromCodegenAndData:
-        // TODO(joshualitt): Fall through to fail after google3 roll.
-        inputName = 'bytes data';
-        inputSize = inputProvider.dartCharactersRead;
-        String dataInput =
-            fe.relativizeUri(Uri.base, readDataUri, Platform.isWindows);
-        String codeInput =
-            fe.relativizeUri(Uri.base, readCodegenUri, Platform.isWindows);
-        summary = 'Data files $input, $dataInput and '
-            '${codeInput}[0-${codegenShards - 1}] ';
-        break;
       case ReadStrategy.fromCodegenAndClosedWorld:
         fail("Must read from closed world, data, and codegen");
         break;
diff --git a/pkg/compiler/lib/src/serialization/task.dart b/pkg/compiler/lib/src/serialization/task.dart
index 27d75ae..e63f515 100644
--- a/pkg/compiler/lib/src/serialization/task.dart
+++ b/pkg/compiler/lib/src/serialization/task.dart
@@ -66,40 +66,6 @@
       closedWorld.elementMap, closedWorld, globalLocalsMap, inferredData);
 }
 
-void serializeGlobalTypeInferenceResultsToSinkLegacy(
-    GlobalTypeInferenceResults results, DataSink sink) {
-  JsClosedWorld closedWorld = results.closedWorld;
-  GlobalLocalsMap globalLocalsMap = results.globalLocalsMap;
-  InferredData inferredData = results.inferredData;
-  closedWorld.writeToDataSink(sink);
-  globalLocalsMap.writeToDataSink(sink);
-  inferredData.writeToDataSink(sink);
-  results.writeToDataSink(sink, closedWorld.elementMap);
-  sink.close();
-}
-
-GlobalTypeInferenceResults
-    deserializeGlobalTypeInferenceResultsFromSourceLegacy(
-        CompilerOptions options,
-        DiagnosticReporter reporter,
-        Environment environment,
-        AbstractValueStrategy abstractValueStrategy,
-        ir.Component component,
-        DataSource source) {
-  JsClosedWorld newClosedWorld = JsClosedWorld.readFromDataSource(
-      options, reporter, environment, abstractValueStrategy, component, source);
-  GlobalLocalsMap newGlobalLocalsMap = GlobalLocalsMap.readFromDataSource(
-      newClosedWorld.closureDataLookup.getEnclosingMember, source);
-  InferredData newInferredData =
-      InferredData.readFromDataSource(source, newClosedWorld);
-  return GlobalTypeInferenceResults.readFromDataSource(
-      source,
-      newClosedWorld.elementMap,
-      newClosedWorld,
-      newGlobalLocalsMap,
-      newInferredData);
-}
-
 void serializeClosedWorldToSink(JsClosedWorld closedWorld, DataSink sink) {
   closedWorld.writeToDataSink(sink);
   sink.close();
@@ -321,39 +287,6 @@
     });
   }
 
-  // TODO(joshualitt) get rid of legacy functions after Google3 roll.
-  void serializeGlobalTypeInferenceLegacy(GlobalTypeInferenceResults results) {
-    JsClosedWorld closedWorld = results.closedWorld;
-    ir.Component component = closedWorld.elementMap.programEnv.mainComponent;
-    serializeComponent(component);
-
-    measureSubtask('serialize data', () {
-      _reporter.log('Writing data to ${_options.writeDataUri}');
-      api.BinaryOutputSink dataOutput =
-          _outputProvider.createBinarySink(_options.writeDataUri);
-      DataSink sink = BinarySink(BinaryOutputSinkAdapter(dataOutput));
-      serializeGlobalTypeInferenceResultsToSinkLegacy(results, sink);
-    });
-  }
-
-  Future<GlobalTypeInferenceResults> deserializeGlobalTypeInferenceLegacy(
-      Environment environment,
-      AbstractValueStrategy abstractValueStrategy) async {
-    ir.Component component = await deserializeComponentAndUpdateOptions();
-
-    return await measureIoSubtask('deserialize data', () async {
-      _reporter.log('Reading data from ${_options.readDataUri}');
-      api.Input<List<int>> dataInput = await _provider
-          .readFromUri(_options.readDataUri, inputKind: api.InputKind.binary);
-      DataSource source =
-          BinarySourceImpl(dataInput.data, stringInterner: _stringInterner);
-      return deserializeGlobalTypeInferenceResultsFromSourceLegacy(_options,
-          _reporter, environment, abstractValueStrategy, component, source);
-    });
-  }
-
-  // TODO(joshualitt): Investigate whether closed world indices can be shared
-  // with codegen.
   void serializeCodegen(BackendStrategy backendStrategy,
       CodegenResults codegenResults, DataSourceIndices indices) {
     GlobalTypeInferenceResults globalTypeInferenceResults =
diff --git a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
index 2ceac61..ab0b1a6 100644
--- a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
+++ b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
@@ -117,9 +117,24 @@
         .isEmitted;
   }
 
-  HBoundsCheck insertBoundsCheck(HInstruction indexerNode, HInstruction array,
-      HInstruction indexArgument, JClosedWorld closedWorld) {
+  HBoundsCheck insertBoundsCheck(
+      HInvokeDynamic indexerNode,
+      HInstruction array,
+      HInstruction indexArgument,
+      JClosedWorld closedWorld,
+      OptimizationTestLog log) {
     final abstractValueDomain = closedWorld.abstractValueDomain;
+
+    if (abstractValueDomain.isNull(array.instructionType).isPotentiallyTrue) {
+      HNullCheck check = HNullCheck(
+          array, abstractValueDomain.excludeNull(array.instructionType))
+        ..selector = indexerNode.selector
+        ..sourceInformation = indexerNode.sourceInformation;
+      log?.registerNullCheck(indexerNode, check);
+      indexerNode.block.addBefore(indexerNode, check);
+      array = check;
+    }
+
     HGetLength length = HGetLength(array, abstractValueDomain.positiveIntType,
         isAssignable: abstractValueDomain
             .isFixedLengthJsIndexable(array.instructionType)
@@ -212,7 +227,7 @@
     HInstruction checkedIndex = index;
     if (requiresBoundsCheck(instruction, closedWorld)) {
       checkedIndex =
-          insertBoundsCheck(instruction, receiver, index, closedWorld);
+          insertBoundsCheck(instruction, receiver, index, closedWorld, log);
     }
     HIndexAssign converted = HIndexAssign(
         closedWorld.abstractValueDomain, receiver, checkedIndex, value);
@@ -291,7 +306,7 @@
     HInstruction checkedIndex = index;
     if (requiresBoundsCheck(instruction, closedWorld)) {
       checkedIndex =
-          insertBoundsCheck(instruction, receiver, index, closedWorld);
+          insertBoundsCheck(instruction, receiver, index, closedWorld, log);
     }
     HIndex converted = HIndex(receiver, checkedIndex, elementType);
     log?.registerIndex(instruction, converted);
@@ -322,7 +337,7 @@
     if (requiresBoundsCheck(instruction, closedWorld)) {
       HConstant zeroIndex = graph.addConstantInt(0, closedWorld);
       HBoundsCheck check =
-          insertBoundsCheck(instruction, receiver, zeroIndex, closedWorld);
+          insertBoundsCheck(instruction, receiver, zeroIndex, closedWorld, log);
       HInstruction minusOne = graph.addConstantInt(-1, closedWorld);
       check.inputs.add(minusOne);
       minusOne.usedBy.add(check);
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index d600d7e..0475798 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -606,8 +606,20 @@
           .isDefinitelyTrue) {
         resultType = _abstractValueDomain.uint32Type;
       }
+      HInstruction checkedReceiver = actualReceiver;
+      if (actualReceiver.isNull(_abstractValueDomain).isPotentiallyTrue) {
+        // The receiver is potentially `null` so we insert a null receiver
+        // check. This will be folded into the length access later.
+        HNullCheck check = HNullCheck(actualReceiver,
+            _abstractValueDomain.excludeNull(actualReceiver.instructionType))
+          ..selector = node.selector
+          ..sourceInformation = node.sourceInformation;
+        _log?.registerNullCheck(node, check);
+        node.block.addBefore(node, check);
+        checkedReceiver = check;
+      }
       HGetLength result =
-          HGetLength(actualReceiver, resultType, isAssignable: !isFixed);
+          HGetLength(checkedReceiver, resultType, isAssignable: !isFixed);
       return result;
     } else if (actualReceiver.isConstantMap()) {
       HConstant constantInput = actualReceiver;
@@ -2607,12 +2619,14 @@
     if (branch is HIf) {
       if (branch.thenBlock.isLive == branch.elseBlock.isLive) return;
       assert(branch.condition.isConstant());
-      HBasicBlock target =
+      HBasicBlock liveSuccessor =
           branch.thenBlock.isLive ? branch.thenBlock : branch.elseBlock;
-      HInstruction instruction = target.first;
-      while (!instruction.isControlFlow()) {
-        HInstruction next = instruction.next;
+      HInstruction instruction = liveSuccessor.first;
+      // Move instructions up until the final control flow instruction or pinned
+      // HTypeKnown.
+      while (instruction.next != null) {
         if (instruction is HTypeKnown && instruction.isPinned) break;
+        HInstruction next = instruction.next;
         // It might be worth re-running GVN optimizations if we hoisted a
         // GVN-able instructions from [target] into [block].
         newGvnCandidates = newGvnCandidates || instruction.useGvn();
diff --git a/pkg/compiler/test/end_to_end/command_line_test.dart b/pkg/compiler/test/end_to_end/command_line_test.dart
index ec0a78a..f3ecc57 100644
--- a/pkg/compiler/test/end_to_end/command_line_test.dart
+++ b/pkg/compiler/test/end_to_end/command_line_test.dart
@@ -91,14 +91,27 @@
       '--out=foo.dill'
     ], out: 'foo.dill', readClosedWorld: 'foo.world', writeData: 'foo.data');
 
-    await test([Flags.readData, 'foo.dill'],
-        out: 'out.js', readData: 'foo.dill.data');
-    await test([Flags.readData, 'foo.dill', '--out=foo.js'],
-        out: 'foo.js', readData: 'foo.dill.data');
-    await test(['${Flags.readData}=out.data', 'foo.dill'],
-        out: 'out.js', readData: 'out.data');
-    await test(['${Flags.readData}=out.data', 'foo.dill', '--out=foo.js'],
-        out: 'foo.js', readData: 'out.data');
+    await test([Flags.readData, 'foo.dill'], exitCode: 1);
+    await test([Flags.readClosedWorld, Flags.readData, 'foo.dill'],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data');
+    await test(
+        [Flags.readClosedWorld, Flags.readData, 'foo.dill', '--out=foo.js'],
+        out: 'foo.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data');
+    await test([
+      '${Flags.readClosedWorld}=out.world',
+      '${Flags.readData}=out.data',
+      'foo.dill'
+    ], out: 'out.js', readClosedWorld: 'out.world', readData: 'out.data');
+    await test([
+      '${Flags.readClosedWorld}=out.world',
+      '${Flags.readData}=out.data',
+      'foo.dill',
+      '--out=foo.js'
+    ], out: 'foo.js', readClosedWorld: 'out.world', readData: 'out.data');
 
     await test([
       Flags.writeCodegen,
@@ -107,6 +120,7 @@
       '${Flags.codegenShards}=2'
     ], exitCode: 1);
     await test([
+      Flags.readClosedWorld,
       Flags.readData,
       Flags.writeCodegen,
       'foo.dill',
@@ -114,6 +128,7 @@
       '${Flags.codegenShards}=2'
     ],
         out: 'out',
+        readClosedWorld: 'foo.dill.world',
         readData: 'foo.dill.data',
         writeCodegen: 'out.code',
         codegenShard: 0,
@@ -121,16 +136,19 @@
     await test([
       Flags.writeCodegen,
       Flags.readData,
+      Flags.readClosedWorld,
       'foo.dill',
       '${Flags.codegenShard}=1',
       '${Flags.codegenShards}=2'
     ],
         out: 'out',
+        readClosedWorld: 'foo.dill.world',
         readData: 'foo.dill.data',
         writeCodegen: 'out.code',
         codegenShard: 1,
         codegenShards: 2);
     await test([
+      '${Flags.readClosedWorld}=foo.world',
       '${Flags.readData}=foo.data',
       '${Flags.writeCodegen}=foo.code',
       'foo.dill',
@@ -138,6 +156,7 @@
       '${Flags.codegenShards}=3'
     ],
         out: 'out',
+        readClosedWorld: 'foo.world',
         readData: 'foo.data',
         writeCodegen: 'foo.code',
         codegenShard: 0,
@@ -145,12 +164,14 @@
     await test([
       '${Flags.readData}=foo.data',
       '${Flags.writeCodegen}',
+      '${Flags.readClosedWorld}=foo.world',
       'foo.dill',
       '--out=foo.js',
       '${Flags.codegenShard}=0',
       '${Flags.codegenShards}=2'
     ],
         out: 'foo.js',
+        readClosedWorld: 'foo.world',
         readData: 'foo.data',
         writeCodegen: 'foo.js.code',
         codegenShard: 0,
@@ -203,28 +224,126 @@
       '${Flags.codegenShards}=2'
     ], exitCode: 1);
 
-    await test([Flags.readCodegen, 'foo.dill', '${Flags.codegenShards}=2'],
-        out: 'out.js',
-        readData: 'foo.dill.data',
-        readCodegen: 'foo.dill.code',
-        codegenShards: 2);
+    // These three flags should parse in any order, but all are required.
     await test([
-      '${Flags.readCodegen}=foo.code',
-      'foo.dill',
-      '${Flags.codegenShards}=3'
-    ],
-        out: 'out.js',
-        readData: 'foo.dill.data',
-        readCodegen: 'foo.code',
-        codegenShards: 3);
-
-    await test([
+      Flags.readClosedWorld,
       Flags.readData,
       Flags.readCodegen,
       'foo.dill',
       '${Flags.codegenShards}=2'
     ],
         out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readClosedWorld,
+      Flags.readCodegen,
+      Flags.readData,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readCodegen,
+      Flags.readClosedWorld,
+      Flags.readData,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readCodegen,
+      Flags.readData,
+      Flags.readClosedWorld,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readData,
+      Flags.readCodegen,
+      Flags.readClosedWorld,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readData,
+      Flags.readClosedWorld,
+      Flags.readCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      Flags.readClosedWorld,
+      Flags.readCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+    await test([
+      Flags.readData,
+      Flags.readCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ], exitCode: 1);
+
+    await test([
+      Flags.readData,
+      Flags.readClosedWorld,
+      Flags.readCodegen,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
+        readData: 'foo.dill.data',
+        readCodegen: 'foo.dill.code',
+        codegenShards: 2);
+    await test([
+      '${Flags.readCodegen}=foo.code',
+      'foo.dill',
+      '${Flags.codegenShards}=3',
+      Flags.readData,
+      Flags.readClosedWorld,
+    ],
+        out: 'out.js',
+        readData: 'foo.dill.data',
+        readClosedWorld: 'foo.dill.world',
+        readCodegen: 'foo.code',
+        codegenShards: 3);
+
+    await test([
+      Flags.readData,
+      Flags.readCodegen,
+      Flags.readClosedWorld,
+      'foo.dill',
+      '${Flags.codegenShards}=2'
+    ],
+        out: 'out.js',
+        readClosedWorld: 'foo.dill.world',
         readData: 'foo.dill.data',
         readCodegen: 'foo.dill.code',
         codegenShards: 2);
@@ -233,9 +352,11 @@
       '${Flags.readCodegen}=foo.code',
       'foo.dill',
       '${Flags.codegenShards}=3',
+      '${Flags.readClosedWorld}=foo.world',
       '-v'
     ],
         out: 'out.js',
+        readClosedWorld: 'foo.world',
         readData: 'foo.data',
         readCodegen: 'foo.code',
         codegenShards: 3);
diff --git a/pkg/compiler/test/model/cfe_annotations_test.dart b/pkg/compiler/test/model/cfe_annotations_test.dart
index c9bd48e..79220177 100644
--- a/pkg/compiler/test/model/cfe_annotations_test.dart
+++ b/pkg/compiler/test/model/cfe_annotations_test.dart
@@ -434,6 +434,7 @@
                     implicitJsInteropMember:
                         nativeData.isJsInteropClass(classEntity),
                     implicitNativeMember: member is! ir.Constructor &&
+                        !_isConstructorTearOff(member) &&
                         nativeData.isNativeClass(classEntity) &&
                         !nativeData.isJsInteropClass(classEntity));
               }
@@ -465,3 +466,13 @@
     await runTest(useIr: true);
   });
 }
+
+bool _isConstructorTearOff(ir.Member member) {
+  if (member is ir.Procedure) {
+    if (member.kind == ir.ProcedureKind.Method &&
+        member.name.text.endsWith('#tearOff')) {
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/pkg/compiler/tool/modular_test_suite.dart b/pkg/compiler/tool/modular_test_suite.dart
index 1593208..fa6b531 100644
--- a/pkg/compiler/tool/modular_test_suite.dart
+++ b/pkg/compiler/tool/modular_test_suite.dart
@@ -385,7 +385,6 @@
       if (useModularAnalysis)
         '${Flags.readModularAnalysis}=${dataDependencies.join(',')}',
       '${Flags.writeClosedWorld}=${toUri(module, closedWorldId)}',
-      Flags.noClosedWorldInData,
       '--out=${toUri(module, globalUpdatedDillId)}',
     ];
     var result =
@@ -433,8 +432,6 @@
       for (String flag in flags) '--enable-experiment=$flag',
       '${Flags.readClosedWorld}=${toUri(module, closedWorldId)}',
       '${Flags.writeData}=${toUri(module, globalDataId)}',
-      // TODO(joshualitt): delete this flag after google3 roll
-      '${Flags.noClosedWorldInData}',
     ];
     var result =
         await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
@@ -555,153 +552,6 @@
   }
 }
 
-// TODO(joshualitt): delete after google3 roll.
-class LegacyGlobalAnalysisStep implements IOModularStep {
-  @override
-  List<DataId> get resultData => const [globalDataId];
-
-  @override
-  bool get needsSources => false;
-
-  @override
-  List<DataId> get dependencyDataNeeded => const [globalUpdatedDillId];
-
-  @override
-  List<DataId> get moduleDataNeeded =>
-      const [closedWorldId, globalUpdatedDillId];
-
-  @override
-  bool get onlyOnMain => true;
-
-  @override
-  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
-      List<String> flags) async {
-    if (_options.verbose) print("\nstep: dart2js global analysis on $module");
-    List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
-      _dart2jsScript,
-      // TODO(sigmund): remove this dependency on libraries.json
-      if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
-      '${Flags.entryUri}=$fakeRoot${module.mainSource}',
-      '${toUri(module, globalUpdatedDillId)}',
-      for (String flag in flags) '--enable-experiment=$flag',
-      '${Flags.readClosedWorld}=${toUri(module, closedWorldId)}',
-      '${Flags.writeData}=${toUri(module, globalDataId)}',
-    ];
-    var result =
-        await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
-
-    _checkExitCode(result, this, module);
-  }
-
-  @override
-  void notifyCached(Module module) {
-    if (_options.verbose)
-      print("\ncached step: dart2js global analysis on $module");
-  }
-}
-
-// Step that invokes the dart2js code generation on the main module given the
-// results of the global analysis step and produces one shard of the codegen
-// output.
-// Note: Legacy.
-class LegacyDart2jsCodegenStep implements IOModularStep {
-  final ShardDataId codeId;
-
-  LegacyDart2jsCodegenStep(this.codeId);
-
-  @override
-  List<DataId> get resultData => [codeId];
-
-  @override
-  bool get needsSources => false;
-
-  @override
-  List<DataId> get dependencyDataNeeded => const [];
-
-  @override
-  List<DataId> get moduleDataNeeded =>
-      const [globalUpdatedDillId, globalDataId];
-
-  @override
-  bool get onlyOnMain => true;
-
-  @override
-  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
-      List<String> flags) async {
-    if (_options.verbose) print("\nstep: dart2js backend on $module");
-    List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
-      _dart2jsScript,
-      if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
-      '${Flags.entryUri}=$fakeRoot${module.mainSource}',
-      '${toUri(module, globalUpdatedDillId)}',
-      for (String flag in flags) '--enable-experiment=$flag',
-      '${Flags.readData}=${toUri(module, globalDataId)}',
-      '${Flags.writeCodegen}=${toUri(module, codeId.dataId)}',
-      '${Flags.codegenShard}=${codeId.shard}',
-      '${Flags.codegenShards}=${codeId.dataId.shards}',
-    ];
-    var result =
-        await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
-
-    _checkExitCode(result, this, module);
-  }
-
-  @override
-  void notifyCached(Module module) {
-    if (_options.verbose) print("cached step: dart2js backend on $module");
-  }
-}
-
-// Step that invokes the dart2js codegen enqueuer and emitter on the main module
-// given the results of the global analysis step and codegen shards.
-// Note: Legacy.
-class LegacyDart2jsEmissionStep implements IOModularStep {
-  @override
-  List<DataId> get resultData => const [jsId];
-
-  @override
-  bool get needsSources => false;
-
-  @override
-  List<DataId> get dependencyDataNeeded => const [];
-
-  @override
-  List<DataId> get moduleDataNeeded =>
-      const [globalUpdatedDillId, globalDataId, codeId0, codeId1];
-
-  @override
-  bool get onlyOnMain => true;
-
-  @override
-  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
-      List<String> flags) async {
-    if (_options.verbose) print("step: dart2js backend on $module");
-    List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
-      _dart2jsScript,
-      if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
-      '${Flags.entryUri}=$fakeRoot${module.mainSource}',
-      '${toUri(module, globalUpdatedDillId)}',
-      for (String flag in flags) '${Flags.enableLanguageExperiments}=$flag',
-      '${Flags.readData}=${toUri(module, globalDataId)}',
-      '${Flags.readCodegen}=${toUri(module, codeId)}',
-      '${Flags.codegenShards}=${codeId.shards}',
-      '--out=${toUri(module, jsId)}',
-    ];
-    var result =
-        await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
-
-    _checkExitCode(result, this, module);
-  }
-
-  @override
-  void notifyCached(Module module) {
-    if (_options.verbose) print("\ncached step: dart2js backend on $module");
-  }
-}
-
 /// Step that runs the output of dart2js in d8 and saves the output.
 class RunD8 implements IOModularStep {
   @override
diff --git a/pkg/dartdev/lib/src/analysis_server.dart b/pkg/dartdev/lib/src/analysis_server.dart
index 580619b..1666afc 100644
--- a/pkg/dartdev/lib/src/analysis_server.dart
+++ b/pkg/dartdev/lib/src/analysis_server.dart
@@ -8,7 +8,11 @@
 
 import 'package:analysis_server/src/server/driver.dart' show Driver;
 import 'package:analysis_server_client/protocol.dart'
-    show EditBulkFixesResult, ResponseDecoder;
+    show
+        AddContentOverlay,
+        AnalysisUpdateContentParams,
+        EditBulkFixesResult,
+        ResponseDecoder;
 import 'package:args/args.dart';
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
@@ -167,6 +171,12 @@
     });
   }
 
+  /// Send an `analysis.updateContent` request with the given [files].
+  Future<void> updateContent(Map<String, AddContentOverlay> files) async {
+    await _sendCommand('analysis.updateContent',
+        params: AnalysisUpdateContentParams(files).toJson());
+  }
+
   Future<Map<String, dynamic>> _sendCommand(String method,
       {Map<String, dynamic> params}) {
     final String id = (++_id).toString();
diff --git a/pkg/dartdev/lib/src/commands/fix.dart b/pkg/dartdev/lib/src/commands/fix.dart
index c81e467..83c2faa 100644
--- a/pkg/dartdev/lib/src/commands/fix.dart
+++ b/pkg/dartdev/lib/src/commands/fix.dart
@@ -26,6 +26,12 @@
 
 To use the tool, run either ['dart fix --dry-run'] for a preview of the proposed changes for a project, or ['dart fix --apply'] to apply the changes.''';
 
+  /// The maximum number of times that fixes will be requested from the server.
+  static const maxPasses = 4;
+
+  /// A map from the absolute path of a file to the updated content of the file.
+  final Map<String, String> fileContentCache = {};
+
   FixCommand({bool verbose = false}) : super(cmdName, cmdDescription, verbose) {
     argParser.addFlag('dry-run',
         abbr: 'n',
@@ -86,7 +92,7 @@
     var modeText = dryRun ? ' (dry run)' : '';
 
     final projectName = path.basename(dirPath);
-    var progress = log.progress(
+    var computeFixesProgress = log.progress(
         'Computing fixes in ${log.ansi.emphasized(projectName)}$modeText');
 
     var server = AnalysisServer(
@@ -99,53 +105,60 @@
 
     await server.start();
 
-    EditBulkFixesResult fixes;
     server.onExit.then((int exitCode) {
-      if (fixes == null && exitCode != 0) {
-        progress?.cancel();
+      if (computeFixesProgress != null && exitCode != 0) {
+        computeFixesProgress?.cancel();
+        computeFixesProgress = null;
         io.exitCode = exitCode;
       }
     });
 
-    fixes = await server.requestBulkFixes(dirPath, inTestMode);
-    final List<SourceFileEdit> edits = fixes.edits;
+    Future<Map<String, BulkFix>> _applyAllEdits() async {
+      var detailsMap = <String, BulkFix>{};
+      List<SourceFileEdit> edits;
+      var pass = 0;
+      do {
+        var fixes = await server.requestBulkFixes(dirPath, inTestMode);
+        _mergeDetails(detailsMap, fixes.details);
+        edits = fixes.edits;
+        _applyEdits(server, edits);
+        pass++;
+        // TODO(brianwilkerson) Be more intelligent about detecting infinite
+        //  loops so that we can increase [maxPasses].
+      } while (pass < maxPasses && edits.isNotEmpty);
+      return detailsMap;
+    }
 
+    var detailsMap = await _applyAllEdits();
     await server.shutdown();
 
-    progress.finish(showTiming: true);
+    if (computeFixesProgress != null) {
+      computeFixesProgress.finish(showTiming: true);
+      computeFixesProgress = null;
+    }
 
     if (inTestMode) {
-      var result = _compareFixesInDirectory(dir, edits);
+      var result = _compareFixesInDirectory(dir);
       log.stdout('Passed: ${result.passCount}, Failed: ${result.failCount}');
       return result.failCount > 0 ? 1 : 0;
-    } else if (edits.isEmpty) {
+    } else if (detailsMap.isEmpty) {
       log.stdout('Nothing to fix!');
     } else {
-      var details = fixes.details;
-      details.sort((f1, f2) => path
-          .relative(f1.path, from: dirPath)
-          .compareTo(path.relative(f2.path, from: dirPath)));
-
-      var fileCount = 0;
-      var fixCount = 0;
-
-      for (var d in details) {
-        ++fileCount;
-        for (var f in d.fixes) {
-          fixCount += f.occurrences;
-        }
-      }
+      var fileCount = detailsMap.length;
+      var fixCount = detailsMap.values
+          .expand((detail) => detail.fixes)
+          .fold(0, (previousValue, fixes) => previousValue + fixes.occurrences);
 
       if (dryRun) {
         log.stdout('');
         log.stdout('${_format(fixCount)} proposed ${_pluralFix(fixCount)} '
             'in ${_format(fileCount)} ${pluralize("file", fileCount)}.');
-        _printDetails(details, dir);
+        _printDetails(detailsMap, dir);
       } else {
-        progress = log.progress('Applying fixes');
-        _applyFixes(edits);
-        progress.finish(showTiming: true);
-        _printDetails(details, dir);
+        var applyFixesProgress = log.progress('Applying fixes');
+        _writeFiles();
+        applyFixesProgress.finish(showTiming: true);
+        _printDetails(detailsMap, dir);
         log.stdout('${_format(fixCount)} ${_pluralFix(fixCount)} made in '
             '${_format(fileCount)} ${pluralize("file", fileCount)}.');
       }
@@ -154,20 +167,24 @@
     return 0;
   }
 
-  void _applyFixes(List<SourceFileEdit> edits) {
+  void _applyEdits(AnalysisServer server, List<SourceFileEdit> edits) {
+    var overlays = <String, AddContentOverlay>{};
     for (var edit in edits) {
-      var fileName = edit.file;
-      var file = io.File(fileName);
-      var code = file.existsSync() ? file.readAsStringSync() : '';
-      code = SourceEdit.applySequence(code, edit.edits);
-      file.writeAsStringSync(code);
+      var filePath = edit.file;
+      var content = fileContentCache.putIfAbsent(filePath, () {
+        var file = io.File(filePath);
+        return file.existsSync() ? file.readAsStringSync() : '';
+      });
+      var newContent = SourceEdit.applySequence(content, edit.edits);
+      fileContentCache[filePath] = newContent;
+      overlays[filePath] = AddContentOverlay(newContent);
     }
+    server.updateContent(overlays);
   }
 
   /// Return `true` if any of the fixes fail to create the same content as is
   /// found in the golden file.
-  _TestResult _compareFixesInDirectory(
-      io.Directory directory, List<SourceFileEdit> edits) {
+  _TestResult _compareFixesInDirectory(io.Directory directory) {
     var result = _TestResult();
     //
     // Gather the files of interest in this directory and process
@@ -177,7 +194,7 @@
     var expectFileMap = <String, io.File>{};
     for (var child in directory.listSync()) {
       if (child is io.Directory) {
-        var childResult = _compareFixesInDirectory(child, edits);
+        var childResult = _compareFixesInDirectory(child);
         result.passCount += childResult.passCount;
         result.failCount += childResult.failCount;
       } else if (child is io.File) {
@@ -189,10 +206,6 @@
         }
       }
     }
-    var editMap = <String, SourceFileEdit>{};
-    for (var edit in edits) {
-      editMap[edit.file] = edit;
-    }
     for (var originalFile in dartFiles) {
       var filePath = originalFile.path;
       var baseName = path.basename(filePath);
@@ -205,19 +218,17 @@
             'No corresponding expect file for the Dart file at "$filePath".');
         continue;
       }
-      var edit = editMap[filePath];
       try {
-        var originalCode = originalFile.readAsStringSync();
         var expectedCode = expectFile.readAsStringSync();
-        var actualCode = edit == null
-            ? originalCode
-            : SourceEdit.applySequence(originalCode, edit.edits);
+        var actualCode =
+            fileContentCache[filePath] ?? originalFile.readAsStringSync();
         // Use a whitespace insensitive comparison.
         if (_compressWhitespace(actualCode) !=
             _compressWhitespace(expectedCode)) {
           result.failCount++;
+          // TODO(brianwilkerson) Do a better job of displaying the differences.
+          //  It's very hard to see the diff with large files.
           _reportFailure(filePath, actualCode, expectedCode);
-          _printEdits(edits);
         } else {
           result.passCount++;
         }
@@ -243,15 +254,54 @@
   String _compressWhitespace(String code) =>
       code.replaceAll(RegExp(r'\s+'), ' ');
 
+  /// Merge the fixes from the current round's [details] into the [detailsMap].
+  void _mergeDetails(Map<String, BulkFix> detailsMap, List<BulkFix> details) {
+    for (var detail in details) {
+      var previousDetail = detailsMap[detail.path];
+      if (previousDetail != null) {
+        _mergeFixCounts(previousDetail.fixes, detail.fixes);
+      } else {
+        detailsMap[detail.path] = detail;
+      }
+    }
+  }
+
+  void _mergeFixCounts(
+      List<BulkFixDetail> oldFixes, List<BulkFixDetail> newFixes) {
+    var originalOldLength = oldFixes.length;
+    newFixLoop:
+    for (var newFix in newFixes) {
+      var newCode = newFix.code;
+      // Iterate over the original content of the list, not any of the newly
+      // added fixes, because the newly added fixes can't be a match.
+      for (var i = 0; i < originalOldLength; i++) {
+        var oldFix = oldFixes[i];
+        if (oldFix.code == newCode) {
+          oldFix.occurrences += newFix.occurrences;
+          continue newFixLoop;
+        }
+      }
+      oldFixes.add(newFix);
+    }
+  }
+
   String _pluralFix(int count) => count == 1 ? 'fix' : 'fixes';
 
-  void _printDetails(List<BulkFix> details, io.Directory workingDir) {
+  void _printDetails(Map<String, BulkFix> detailsMap, io.Directory workingDir) {
+    String relative(String absolutePath) {
+      return path.relative(absolutePath, from: workingDir.path);
+    }
+
     log.stdout('');
 
     final bullet = log.ansi.bullet;
 
-    for (var detail in details) {
-      log.stdout(path.relative(detail.path, from: workingDir.path));
+    var modifiedFilePaths = detailsMap.keys.toList();
+    modifiedFilePaths
+        .sort((first, second) => relative(first).compareTo(relative(second)));
+    for (var filePath in modifiedFilePaths) {
+      var detail = detailsMap[filePath];
+      log.stdout(relative(detail.path));
       final fixes = detail.fixes.toList();
       fixes.sort((a, b) => a.code.compareTo(b.code));
       for (var fix in fixes) {
@@ -262,16 +312,6 @@
     }
   }
 
-  void _printEdits(List<SourceFileEdit> edits) {
-    log.stdout('Edits returned from server:');
-    for (var fileEdit in edits) {
-      log.stdout('  ${fileEdit.file}');
-      for (var edit in fileEdit.edits) {
-        log.stdout("    ${edit.offset} - ${edit.end}, '${edit.replacement}'");
-      }
-    }
-  }
-
   /// Report that the [actualCode] produced by applying fixes to the content of
   /// [filePath] did not match the [expectedCode].
   void _reportFailure(String filePath, String actualCode, String expectedCode) {
@@ -283,6 +323,14 @@
     log.stdout(actualCode);
   }
 
+  /// Write the modified contents of files in the [fileContentCache] to disk.
+  void _writeFiles() {
+    for (var entry in fileContentCache.entries) {
+      var file = io.File(entry.key);
+      file.writeAsStringSync(entry.value);
+    }
+  }
+
   static String _format(int value) => _numberFormat.format(value);
 }
 
diff --git a/pkg/dartdev/test/commands/fix_test.dart b/pkg/dartdev/test/commands/fix_test.dart
index 036eb7d..1f0dd94 100644
--- a/pkg/dartdev/test/commands/fix_test.dart
+++ b/pkg/dartdev/test/commands/fix_test.dart
@@ -195,6 +195,32 @@
         ]));
   });
 
+  test('--apply (contradictory lints do not loop infinitely)', () async {
+    p = project(
+      mainSrc: '''
+var x = "";
+''',
+      analysisOptions: '''
+linter:
+  rules:
+    - prefer_double_quotes
+    - prefer_single_quotes
+''',
+    );
+    var result = await runFix(['--apply', '.'], workingDir: p.dirPath);
+    expect(result.exitCode, 0);
+    expect(result.stderr, isEmpty);
+    expect(
+        result.stdout,
+        stringContainsInOrder([
+          'Applying fixes...',
+          'lib${Platform.pathSeparator}main.dart',
+          '  prefer_double_quotes $bullet 2 fixes',
+          '  prefer_single_quotes $bullet 2 fixes',
+          '4 fixes made in 1 file.',
+        ]));
+  });
+
   test('--apply (excludes)', () async {
     p = project(
       mainSrc: '''
@@ -233,6 +259,33 @@
     expect(result.stdout, contains('Nothing to fix!'));
   });
 
+  test('--apply (unused imports require a second pass)', () async {
+    p = project(
+      mainSrc: '''
+import 'dart:math';
+
+var x = "";
+''',
+      analysisOptions: '''
+linter:
+  rules:
+    - prefer_single_quotes
+''',
+    );
+    var result = await runFix(['--apply', '.'], workingDir: p.dirPath);
+    expect(result.exitCode, 0);
+    expect(result.stderr, isEmpty);
+    expect(
+        result.stdout,
+        stringContainsInOrder([
+          'Applying fixes...',
+          'lib${Platform.pathSeparator}main.dart',
+          '  prefer_single_quotes $bullet 1 fix',
+          '  unused_import $bullet 1 fix',
+          '2 fixes made in 1 file.',
+        ]));
+  });
+
   group('compare-to-golden', () {
     test('applied fixes do not match expected', () async {
       p = project(
diff --git a/tools/VERSION b/tools/VERSION
index 188507e..9ffa67f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 42
+PRERELEASE 43
 PRERELEASE_PATCH 0
\ No newline at end of file