Version 2.10.0-127.0.dev

Merge commit '809febe06a357dd6225b5620342d86b8304fbb89' into 'dev'
diff --git a/pkg/analyzer/lib/src/dart/element/replace_top_bottom_visitor.dart b/pkg/analyzer/lib/src/dart/element/replace_top_bottom_visitor.dart
index 7e5e457..5dd6961 100644
--- a/pkg/analyzer/lib/src/dart/element/replace_top_bottom_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/element/replace_top_bottom_visitor.dart
@@ -4,6 +4,7 @@
 
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/replacement_visitor.dart';
+import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:meta/meta.dart';
 
@@ -54,6 +55,16 @@
   }
 
   @override
+  DartType visitTypeParameterType(TypeParameterType type) {
+    if (!_isCovariant && _typeSystem.isNonNullableByDefault) {
+      if (_typeSystem.isSubtypeOf2(type, NeverTypeImpl.instance)) {
+        return _typeSystem.objectQuestion;
+      }
+    }
+    return null;
+  }
+
+  @override
   DartType visitVoidType(VoidType type) {
     return _isCovariant ? _bottomType : null;
   }
diff --git a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index 311d391..3c2ce69 100644
--- a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -16,7 +16,6 @@
 import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
 import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
 import 'package:analyzer/src/dart/resolver/property_element_resolver.dart';
-import 'package:analyzer/src/dart/resolver/resolution_result.dart';
 import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
 import 'package:analyzer/src/error/assignment_verifier.dart';
 import 'package:analyzer/src/error/codes.dart';
@@ -203,7 +202,7 @@
       nameErrorEntity: operator,
     );
     node.staticElement = result.getter;
-    if (_shouldReportInvalidMember(leftType, result)) {
+    if (result.needsGetterError) {
       _errorReporter.reportErrorForToken(
         CompileTimeErrorCode.UNDEFINED_OPERATOR,
         operator,
@@ -438,20 +437,6 @@
         break;
     }
   }
-
-  /// Return `true` if we should report an error for the lookup [result] on
-  /// the [type].
-  // TODO(scheglov) this is duplicate
-  bool _shouldReportInvalidMember(DartType type, ResolutionResult result) {
-    if (result.isNone && type != null && !type.isDynamic) {
-      if (_typeSystem.isNonNullableByDefault &&
-          _typeSystem.isPotentiallyNullable(type)) {
-        return false;
-      }
-      return true;
-    }
-    return false;
-  }
 }
 
 class AssignmentExpressionShared {
diff --git a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
index 8addd0d..489aa70 100644
--- a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
@@ -344,7 +344,7 @@
 
     node.staticElement = result.getter;
     node.staticInvokeType = result.getter?.type;
-    if (_shouldReportInvalidMember(leftType, result)) {
+    if (result.needsGetterError) {
       if (leftOperand is SuperExpression) {
         _errorReporter.reportErrorForToken(
           CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR,
@@ -380,18 +380,4 @@
     }
     _inferenceHelper.recordStaticType(node, staticType);
   }
-
-  /// Return `true` if we should report an error for the lookup [result] on
-  /// the [type].
-  ///
-  /// TODO(scheglov) this is duplicate
-  bool _shouldReportInvalidMember(DartType type, ResolutionResult result) {
-    if (result.isNone && type != null && !type.isDynamic) {
-      if (_isNonNullableByDefault && _typeSystem.isPotentiallyNullable(type)) {
-        return false;
-      }
-      return true;
-    }
-    return false;
-  }
 }
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index c462243..77e033b 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -641,11 +641,6 @@
       nameErrorEntity: nameNode,
     );
 
-    if (result.isAmbiguous) {
-      _setDynamicResolution(node);
-      return;
-    }
-
     var target = result.getter;
     if (target != null) {
       nameNode.staticElement = target;
@@ -666,6 +661,10 @@
 
     _setDynamicResolution(node);
 
+    if (!result.needsGetterError) {
+      return;
+    }
+
     String receiverClassName = '<unknown>';
     if (receiverType is InterfaceType) {
       receiverClassName = receiverType.element.name;
diff --git a/pkg/analyzer/lib/src/dart/resolver/postfix_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/postfix_expression_resolver.dart
index 230f66b4..bcebac1 100644
--- a/pkg/analyzer/lib/src/dart/resolver/postfix_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/postfix_expression_resolver.dart
@@ -14,7 +14,6 @@
 import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
 import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
 import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
-import 'package:analyzer/src/dart/resolver/resolution_result.dart';
 import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/resolver.dart';
@@ -161,7 +160,7 @@
       nameErrorEntity: operand,
     );
     node.staticElement = result.getter;
-    if (_shouldReportInvalidMember(receiverType, result)) {
+    if (result.needsGetterError) {
       if (operand is SuperExpression) {
         _errorReporter.reportErrorForToken(
           CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR,
@@ -236,18 +235,4 @@
 
     _flowAnalysis?.flow?.nonNullAssert_end(operand);
   }
-
-  /// Return `true` if we should report an error for the lookup [result] on
-  /// the [type].
-  ///
-  /// TODO(scheglov) this is duplicate
-  bool _shouldReportInvalidMember(DartType type, ResolutionResult result) {
-    if (result.isNone && type != null && !type.isDynamic) {
-      if (_isNonNullableByDefault && _typeSystem.isPotentiallyNullable(type)) {
-        return false;
-      }
-      return true;
-    }
-    return false;
-  }
 }
diff --git a/pkg/analyzer/lib/src/dart/resolver/prefix_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/prefix_expression_resolver.dart
index a723362..76cde43 100644
--- a/pkg/analyzer/lib/src/dart/resolver/prefix_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/prefix_expression_resolver.dart
@@ -14,7 +14,6 @@
 import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
 import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
 import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
-import 'package:analyzer/src/dart/resolver/resolution_result.dart';
 import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/resolver.dart';
@@ -195,7 +194,7 @@
         nameErrorEntity: operand,
       );
       node.staticElement = result.getter;
-      if (_shouldReportInvalidMember(readType, result)) {
+      if (result.needsGetterError) {
         if (operand is SuperExpression) {
           _errorReporter.reportErrorForToken(
             CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR,
@@ -255,19 +254,4 @@
 
     _flowAnalysis?.flow?.logicalNot_end(node, operand);
   }
-
-  /// Return `true` if we should report an error for the lookup [result] on
-  /// the [type].
-  ///
-  /// TODO(scheglov) this is duplicate
-  bool _shouldReportInvalidMember(DartType type, ResolutionResult result) {
-    if (result.isNone && type != null && !type.isDynamic) {
-      if (_typeSystem.isNonNullableByDefault &&
-          _typeSystem.isPotentiallyNullable(type)) {
-        return false;
-      }
-      return true;
-    }
-    return false;
-  }
 }
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index d4ecd63..fd6587a 100644
--- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -190,7 +190,7 @@
             targetType.isDartCoreFunction &&
             propertyName.name == FunctionElement.CALL_METHOD_NAME) {
           // Referencing `.call` on a `Function` type is OK.
-        } else {
+        } else if (result.needsGetterError) {
           _errorReporter.reportErrorForNode(
             CompileTimeErrorCode.UNDEFINED_GETTER,
             propertyName,
@@ -199,7 +199,7 @@
         }
       }
 
-      if (hasWrite && result.setter == null && !result.isAmbiguous) {
+      if (hasWrite && result.needsSetterError) {
         AssignmentVerifier(_definingLibrary, _errorReporter).verify(
           node: propertyName,
           requested: null,
diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
index f87810c..3a19d28 100644
--- a/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/resolution_result.dart
@@ -20,38 +20,48 @@
   /// Return the element that is invoked for reading.
   final ExecutableElement getter;
 
+  /// If `true`, then the [getter] is `null`, and this is an error that has
+  /// not yet been reported, and the client should report it.
+  ///
+  /// If `false`, then the [getter] is valid. Usually this means that the
+  /// correct target has been found. But the [getter] still might be `null`,
+  /// when there was an error, and it has already been reported (e.g. when
+  /// ambiguous extension);  or when `null` is the only possible result (e.g.
+  /// when `dynamicTarget.foo`, or `functionTyped.call`).
+  final bool needsGetterError;
+
   /// Return the element that is invoked for writing.
   final ExecutableElement setter;
 
+  /// If `true`, then the [setter] is `null`, and this is an error that has
+  /// not yet been reported, and the client should report it.
+  ///
+  /// If `false`, then the [setter] is valid. Usually this means that the
+  /// correct target has been found. But the [setter] still might be `null`,
+  /// when there was an error, and it has already been reported (e.g. when
+  /// ambiguous extension);  or when `null` is the only possible result (e.g.
+  /// when `dynamicTarget.foo`).
+  final bool needsSetterError;
+
   /// Initialize a newly created result to represent resolving a single
   /// reading and / or writing result.
-  ResolutionResult({this.getter, this.setter})
-      : assert(getter != null || setter != null),
-        state = _ResolutionResultState.single;
+  ResolutionResult({
+    this.getter,
+    this.needsGetterError,
+    this.setter,
+    this.needsSetterError,
+  }) : state = _ResolutionResultState.single;
 
   /// Initialize a newly created result with no elements and the given [state].
   const ResolutionResult._(this.state)
       : getter = null,
-        setter = null;
+        needsGetterError = true,
+        setter = null,
+        needsSetterError = true;
 
   /// Return `true` if this result represents the case where multiple ambiguous
   /// elements were found.
   bool get isAmbiguous => state == _ResolutionResultState.ambiguous;
-
-  /// Return `true` if this result represents the case where no element was
-  /// found.
-  bool get isNone => state == _ResolutionResultState.none;
-
-  /// Return `true` if this result represents the case where a single element
-  /// was found.
-  bool get isSingle => state == _ResolutionResultState.single;
-
-  /// If this is a property, return `true` is the property is static.
-  /// If this is a function, return `true` is the function is static.
-  /// Otherwise return `false`.
-  bool get isStatic {
-    return getter?.isStatic ?? setter?.isStatic ?? false;
-  }
 }
 
 /// The state of a [ResolutionResult].
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
index 49df1af..989ea2d 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/type_provider.dart';
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
@@ -28,7 +29,15 @@
   SyntacticEntity _nameErrorEntity;
   String _name;
 
-  ResolutionResult _result = ResolutionResult.none;
+  bool _needsGetterError;
+  bool _reportedGetterError;
+  ExecutableElement _getterRequested;
+  ExecutableElement _getterRecovery;
+
+  bool _needsSetterError;
+  bool _reportedSetterError;
+  ExecutableElement _setterRequested;
+  ExecutableElement _setterRecovery;
 
   TypePropertyResolver(this._resolver)
       : _definingLibrary = _resolver.definingLibrary,
@@ -37,6 +46,10 @@
         _typeProvider = _resolver.typeProvider,
         _extensionResolver = _resolver.extensionResolver;
 
+  bool get _hasGetterOrSetter {
+    return _getterRequested != null || _setterRequested != null;
+  }
+
   /// Look up the property with the given [name] in the [receiverType].
   ///
   /// The [receiver] might be `null`, used to identify `super`.
@@ -56,91 +69,133 @@
     _receiver = receiver;
     _name = name;
     _nameErrorEntity = nameErrorEntity;
-    _result = ResolutionResult.none;
+    _resetResult();
 
     receiverType = _resolveTypeParameter(receiverType);
 
+    if (receiverType is DynamicTypeImpl) {
+      _lookupInterfaceType(_typeProvider.objectType);
+      _needsGetterError = false;
+      _needsSetterError = false;
+      return _toResult();
+    }
+
     if (_isNonNullableByDefault &&
         _typeSystem.isPotentiallyNullable(receiverType)) {
+      _lookupInterfaceType(_typeProvider.objectType);
+      if (_hasGetterOrSetter) {
+        return _toResult();
+      }
+
       _lookupExtension(receiverType);
-
-      if (_result.isNone) {
-        _lookupInterfaceType(_typeProvider.objectType);
+      if (_hasGetterOrSetter) {
+        return _toResult();
       }
 
-      if (_result.isNone && !receiverType.isDynamic) {
-        _resolver.nullableDereferenceVerifier.report(
-          receiverErrorNode,
-          receiverType,
-        );
-        // Recovery, get some resolution.
-        _lookupType(receiverType);
+      _resolver.nullableDereferenceVerifier.report(
+        receiverErrorNode,
+        receiverType,
+      );
+      _reportedGetterError = true;
+      _reportedSetterError = true;
+
+      // Recovery, get some resolution.
+      if (receiverType is InterfaceType) {
+        _lookupInterfaceType(receiverType);
       }
 
-      _toLegacy();
-      return _result;
+      return _toResult();
     } else {
-      _lookupType(receiverType);
-
-      if (_result.isNone) {
-        _lookupExtension(receiverType);
+      if (receiverType is InterfaceType) {
+        _lookupInterfaceType(receiverType);
+        if (_hasGetterOrSetter) {
+          return _toResult();
+        }
       }
 
-      if (_result.isNone) {
-        _lookupInterfaceType(_typeProvider.objectType);
+      _lookupExtension(receiverType);
+      if (_hasGetterOrSetter) {
+        return _toResult();
       }
 
-      _toLegacy();
-      return _result;
+      _lookupInterfaceType(_typeProvider.objectType);
+
+      return _toResult();
     }
   }
 
   void _lookupExtension(DartType type) {
-    _result = _extensionResolver.findExtension(type, _nameErrorEntity, _name);
+    var result =
+        _extensionResolver.findExtension(type, _nameErrorEntity, _name);
+    _reportedGetterError = result.isAmbiguous;
+    _reportedSetterError = result.isAmbiguous;
+
+    if (result.getter != null) {
+      _needsGetterError = false;
+      _getterRequested = result.getter;
+    }
+
+    if (result.setter != null) {
+      _needsSetterError = false;
+      _setterRequested = result.setter;
+    }
   }
 
   void _lookupInterfaceType(InterfaceType type) {
     var isSuper = _receiver is SuperExpression;
 
-    ExecutableElement typeGetter;
-    ExecutableElement typeSetter;
-
     if (_name == '[]') {
-      typeGetter = type.lookUpMethod2(
+      _getterRequested = type.lookUpMethod2(
         '[]',
         _definingLibrary,
         concrete: isSuper,
         inherited: isSuper,
       );
+      _needsGetterError = _getterRequested == null;
 
-      typeSetter = type.lookUpMethod2(
+      _setterRequested = type.lookUpMethod2(
         '[]=',
         _definingLibrary,
         concrete: isSuper,
         inherited: isSuper,
       );
+      _needsSetterError = _setterRequested == null;
     } else {
       var classElement = type.element as AbstractClassElementImpl;
-      var getterName = Name(_definingLibrary.source.uri, _name);
-      var setterName = Name(_definingLibrary.source.uri, '$_name=');
-      typeGetter = _resolver.inheritance
-              .getMember(type, getterName, forSuper: isSuper) ??
-          classElement.lookupStaticGetter(_name, _definingLibrary) ??
-          classElement.lookupStaticMethod(_name, _definingLibrary);
-      typeSetter = _resolver.inheritance
-              .getMember(type, setterName, forSuper: isSuper) ??
-          classElement.lookupStaticSetter(_name, _definingLibrary);
-    }
 
-    if (typeGetter != null || typeSetter != null) {
-      _result = ResolutionResult(getter: typeGetter, setter: typeSetter);
+      var getterName = Name(_definingLibrary.source.uri, _name);
+      _getterRequested =
+          _resolver.inheritance.getMember(type, getterName, forSuper: isSuper);
+      _needsGetterError = _getterRequested == null;
+      if (_getterRequested == null) {
+        _getterRecovery ??=
+            classElement.lookupStaticGetter(_name, _definingLibrary) ??
+                classElement.lookupStaticMethod(_name, _definingLibrary);
+        _needsGetterError = _getterRecovery == null;
+      }
+
+      var setterName = Name(_definingLibrary.source.uri, '$_name=');
+      _setterRequested =
+          _resolver.inheritance.getMember(type, setterName, forSuper: isSuper);
+      _needsSetterError = _setterRequested == null;
+      if (_setterRequested == null) {
+        _setterRecovery ??=
+            classElement.lookupStaticSetter(_name, _definingLibrary);
+        _needsSetterError = _setterRecovery == null;
+      }
     }
   }
 
-  void _lookupType(DartType type) {
-    if (type is InterfaceType) {
-      _lookupInterfaceType(type);
-    }
+  void _resetResult() {
+    _needsGetterError = false;
+    _reportedGetterError = false;
+    _getterRequested = null;
+    _getterRecovery = null;
+
+    _needsSetterError = false;
+    _reportedSetterError = false;
+    _setterRequested = null;
+    _setterRecovery = null;
   }
 
   /// If the given [type] is a type parameter, replace it with its bound.
@@ -149,12 +204,21 @@
     return type?.resolveToBound(_typeProvider.objectType);
   }
 
-  void _toLegacy() {
-    if (_result.isSingle) {
-      _result = ResolutionResult(
-        getter: _resolver.toLegacyElement(_result.getter),
-        setter: _resolver.toLegacyElement(_result.setter),
-      );
-    }
+  ResolutionResult _toResult() {
+    _getterRequested = _resolver.toLegacyElement(_getterRequested);
+    _getterRecovery = _resolver.toLegacyElement(_getterRecovery);
+
+    _setterRequested = _resolver.toLegacyElement(_setterRequested);
+    _setterRecovery = _resolver.toLegacyElement(_setterRecovery);
+
+    var getter = _getterRequested ?? _getterRecovery;
+    var setter = _setterRequested ?? _setterRecovery;
+
+    return ResolutionResult(
+      getter: getter,
+      needsGetterError: _needsGetterError && !_reportedGetterError,
+      setter: setter,
+      needsSetterError: _needsSetterError && !_reportedSetterError,
+    );
   }
 }
diff --git a/pkg/analyzer/lib/src/generated/element_resolver.dart b/pkg/analyzer/lib/src/generated/element_resolver.dart
index e7adf8a..5faf315 100644
--- a/pkg/analyzer/lib/src/generated/element_resolver.dart
+++ b/pkg/analyzer/lib/src/generated/element_resolver.dart
@@ -438,12 +438,12 @@
     }
 
     if (isInGetterContext) {
-      _checkForUndefinedIndexOperator(
-          node, target, getterMethodName, result, result.getter, targetType);
+      _checkForUndefinedIndexOperator(node, target, getterMethodName, result,
+          result.getter, result.needsGetterError, targetType);
     }
     if (isInSetterContext) {
-      _checkForUndefinedIndexOperator(
-          node, target, setterMethodName, result, result.setter, targetType);
+      _checkForUndefinedIndexOperator(node, target, setterMethodName, result,
+          result.setter, result.needsSetterError, targetType);
     }
   }
 
@@ -879,6 +879,7 @@
       String methodName,
       ResolutionResult result,
       ExecutableElement element,
+      bool needsError,
       DartType staticType) {
     if (result.isAmbiguous) {
       return;
@@ -921,7 +922,7 @@
         HintCode.RECEIVER_OF_TYPE_NEVER,
         target,
       );
-    } else {
+    } else if (needsError) {
       _errorReporter.reportErrorForOffset(
         CompileTimeErrorCode.UNDEFINED_OPERATOR,
         offset,
diff --git a/pkg/analyzer/test/src/dart/element/replace_top_bottom_test.dart b/pkg/analyzer/test/src/dart/element/replace_top_bottom_test.dart
index e3a64cf..4851abd 100644
--- a/pkg/analyzer/test/src/dart/element/replace_top_bottom_test.dart
+++ b/pkg/analyzer/test/src/dart/element/replace_top_bottom_test.dart
@@ -79,11 +79,22 @@
     // Not contravariant.
     _check(neverNone, 'Never');
 
-    _check(
-      functionTypeNone(returnType: intNone, parameters: [
-        requiredParameter(type: neverNone),
-      ]),
-      'int Function(Object?)',
+    void checkContravariant(DartType type, String expectedStr) {
+      _check(
+        functionTypeNone(returnType: intNone, parameters: [
+          requiredParameter(type: type),
+        ]),
+        'int Function($expectedStr)',
+      );
+    }
+
+    checkContravariant(neverNone, 'Object?');
+
+    checkContravariant(
+      typeParameterTypeNone(
+        typeParameter('T', bound: neverNone),
+      ),
+      'Object?',
     );
   }
 
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index e9936dd..80975a0 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -921,8 +921,9 @@
 }
 ''', [
       if (typeToStringWithNullability)
-        error(CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE, 11, 4),
-      error(CompileTimeErrorCode.UNDEFINED_METHOD, 16, 3),
+        error(CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE, 11, 4)
+      else
+        error(CompileTimeErrorCode.UNDEFINED_METHOD, 16, 3),
     ]);
     _assertUnresolvedMethodInvocation('foo();');
   }
@@ -2452,7 +2453,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 27, 1),
-      error(CompileTimeErrorCode.UNDEFINED_METHOD, 29, 3),
     ]);
 
     assertMethodInvocation2(
@@ -2477,7 +2477,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 65, 1),
-      error(CompileTimeErrorCode.UNDEFINED_METHOD, 67, 3),
     ]);
 
     assertMethodInvocation2(
diff --git a/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart b/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
index 207edcb..d9d9872 100644
--- a/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
@@ -190,7 +190,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 24, 1),
-      error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 25, 3),
     ]);
 
     assertIndexExpression(
@@ -208,8 +207,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 24, 1),
-      error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 25, 3),
-      error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 25, 3),
     ]);
 
     assertIndexExpression(
@@ -229,7 +226,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 24, 1),
-      error(CompileTimeErrorCode.UNDEFINED_OPERATOR, 25, 3),
     ]);
 
     assertIndexExpression(
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_getter_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_getter_test.dart
index 35eb115..baf399c 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_getter_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_getter_test.dart
@@ -235,7 +235,6 @@
 ''',
         expectedErrorsByNullability(nullable: [
           error(CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE, 22, 5),
-          error(CompileTimeErrorCode.UNDEFINED_GETTER, 28, 3),
         ], legacy: [
           error(CompileTimeErrorCode.UNDEFINED_GETTER, 28, 3),
         ]));
diff --git a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
index e59a967..a022829 100644
--- a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
@@ -624,7 +624,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 58, 1),
-      error(CompileTimeErrorCode.UNDEFINED_GETTER, 60, 3),
     ]);
   }
 
@@ -876,7 +875,6 @@
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 54, 1),
-      error(CompileTimeErrorCode.UNDEFINED_METHOD, 56, 3),
     ]);
   }
 
diff --git a/pkg/front_end/test/incremental_compiler_leak_test.dart b/pkg/front_end/test/incremental_compiler_leak_test.dart
index 326c2e2..e8927d1 100644
--- a/pkg/front_end/test/incremental_compiler_leak_test.dart
+++ b/pkg/front_end/test/incremental_compiler_leak_test.dart
@@ -1,11 +1,8 @@
 import 'dart:async';
 import 'dart:io';
 
-import "package:vm_service/vm_service.dart" as vmService;
-import "package:vm_service/vm_service_io.dart" as vmService;
-
-import "vm_service_heap_helper.dart" as helper;
 import "simple_stats.dart";
+import "vm_service_helper.dart" as vmService;
 
 const int limit = 10;
 
@@ -22,7 +19,7 @@
   ]);
 }
 
-class LeakFinder extends helper.LaunchingVMServiceHeapHelper {
+class LeakFinder extends vmService.LaunchingVMServiceHelper {
   @override
   Future<void> run() async {
     vmService.VM vm = await serviceClient.getVM();
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index f858baf..e985d0b 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -129,6 +129,8 @@
 corners
 costly
 cov
+coverage
+coverages
 cp
 csi
 ctrl
@@ -282,6 +284,7 @@
 heuristics
 hi
 hints
+hits
 home
 hoo
 hosted
@@ -403,6 +406,7 @@
 mismatched
 misnamed
 miss
+misses
 misspelled
 mistake
 mistakes
@@ -500,6 +504,7 @@
 response
 result1
 result2
+resuming
 retaining
 retainingpath
 retains
diff --git a/pkg/front_end/test/vm_service_coverage.dart b/pkg/front_end/test/vm_service_coverage.dart
new file mode 100644
index 0000000..b144e3d
--- /dev/null
+++ b/pkg/front_end/test/vm_service_coverage.dart
@@ -0,0 +1,142 @@
+import 'dart:async';
+
+import 'vm_service_helper.dart' as vmService;
+
+main(List<String> args) async {
+  CoverageHelper coverageHelper = new CoverageHelper();
+
+  List<String> allArgs = new List<String>();
+  allArgs.addAll([
+    "--disable-dart-dev",
+    "--enable-asserts",
+    "--pause_isolates_on_exit",
+  ]);
+  allArgs.addAll(args);
+
+  coverageHelper.start(allArgs);
+}
+
+class CoverageHelper extends vmService.LaunchingVMServiceHelper {
+  final bool forceCompilation;
+  final bool printHits;
+
+  CoverageHelper({this.forceCompilation: false, this.printHits: true});
+
+  @override
+  Future<void> run() async {
+    vmService.VM vm = await serviceClient.getVM();
+    if (vm.isolates.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    }
+    vmService.IsolateRef isolateRef = vm.isolates.single;
+    await waitUntilIsolateIsRunnable(isolateRef.id);
+    await serviceClient.resume(isolateRef.id);
+    Completer<String> cTimeout = new Completer();
+    Timer timer = new Timer(new Duration(minutes: 20), () {
+      cTimeout.complete("Timeout");
+      killProcess();
+    });
+
+    Completer<String> cRunDone = new Completer();
+    // ignore: unawaited_futures
+    waitUntilPaused(isolateRef.id).then((value) => cRunDone.complete("Done"));
+
+    await Future.any([cRunDone.future, cTimeout.future, cProcessExited.future]);
+
+    timer.cancel();
+
+    if (!await isPausedAtExit(isolateRef.id)) {
+      killProcess();
+      throw "Expected to be paused at exit, but is just paused!";
+    }
+
+    // Get and process coverage information.
+    Stopwatch stopwatch = new Stopwatch()..start();
+    vmService.SourceReport sourceReport = await serviceClient.getSourceReport(
+        isolateRef.id, [vmService.SourceReportKind.kCoverage],
+        forceCompile: forceCompilation);
+    print("Got source report from VM in ${stopwatch.elapsedMilliseconds} ms");
+    stopwatch.reset();
+    Map<Uri, Coverage> coverages = {};
+    for (vmService.SourceReportRange range in sourceReport.ranges) {
+      vmService.ScriptRef script = sourceReport.scripts[range.scriptIndex];
+      Uri scriptUri = Uri.parse(script.uri);
+      if (!includeCoverageFor(scriptUri)) continue;
+      Coverage coverage = coverages[scriptUri] ??= new Coverage();
+
+      vmService.SourceReportCoverage sourceReportCoverage = range.coverage;
+      if (sourceReportCoverage == null) {
+        // Range not compiled. Record the range if provided.
+        assert(!range.compiled);
+        if (range.startPos >= 0 || range.endPos >= 0) {
+          coverage.notCompiled
+              .add(new StartEndPair(range.startPos, range.endPos));
+        }
+        continue;
+      }
+      coverage.hits.addAll(sourceReportCoverage.hits);
+      coverage.misses.addAll(sourceReportCoverage.misses);
+    }
+    print("Processed source report from VM in "
+        "${stopwatch.elapsedMilliseconds} ms");
+    stopwatch.reset();
+
+    // It's paused at exit, so resuming should allow us to exit.
+    await serviceClient.resume(isolateRef.id);
+
+    for (MapEntry<Uri, Coverage> entry in coverages.entries) {
+      assert(entry.value.hits.intersection(entry.value.misses).isEmpty);
+      if (entry.value.hits.isEmpty &&
+          entry.value.misses.isEmpty &&
+          entry.value.notCompiled.isEmpty) {
+        continue;
+      }
+      print(entry.key);
+      if (printHits) {
+        print("Hits: ${entry.value.hits.toList()..sort()}");
+      }
+      print("Misses: ${entry.value.misses.toList()..sort()}");
+      print("Not compiled: ${entry.value.notCompiled.toList()..sort()}");
+      print("");
+    }
+  }
+
+  Completer<String> cProcessExited = new Completer();
+  void processExited(int exitCode) {
+    cProcessExited.complete("Exit");
+  }
+
+  bool includeCoverageFor(Uri uri) {
+    if (uri.scheme == "dart") {
+      return false;
+    }
+    if (uri.scheme == "package") {
+      return uri.pathSegments.first == "front_end" ||
+          uri.pathSegments.first == "_fe_analyzer_shared" ||
+          uri.pathSegments.first == "kernel";
+    }
+    return true;
+  }
+}
+
+class Coverage {
+  final Set<int> hits = {};
+  final Set<int> misses = {};
+  final Set<StartEndPair> notCompiled = {};
+}
+
+class StartEndPair implements Comparable {
+  final int startPos;
+  final int endPos;
+
+  StartEndPair(this.startPos, this.endPos);
+
+  String toString() => "[$startPos - $endPos]";
+
+  @override
+  int compareTo(Object other) {
+    if (other is! StartEndPair) return -1;
+    StartEndPair o = other;
+    return startPos - o.startPos;
+  }
+}
diff --git a/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart b/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart
new file mode 100644
index 0000000..4cab31b
--- /dev/null
+++ b/pkg/front_end/test/vm_service_coverage_constant_evaluator.dart
@@ -0,0 +1,27 @@
+import 'vm_service_coverage.dart' as helper;
+
+main(List<String> args) async {
+  CoverageHelper coverageHelper = new CoverageHelper();
+
+  List<String> allArgs = new List<String>();
+  allArgs.addAll([
+    "--disable-dart-dev",
+    "--enable-asserts",
+    "--pause_isolates_on_exit",
+  ]);
+  allArgs.addAll(args);
+
+  coverageHelper.start(allArgs);
+}
+
+class CoverageHelper extends helper.CoverageHelper {
+  CoverageHelper() : super(printHits: false);
+
+  bool includeCoverageFor(Uri uri) {
+    if (uri.scheme != "package") return false;
+    if (uri.path.startsWith("front_end/src/fasta/kernel/constant_")) {
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/pkg/front_end/test/vm_service_heap_finder.dart b/pkg/front_end/test/vm_service_heap_finder.dart
index ea3bcea..2e85184 100644
--- a/pkg/front_end/test/vm_service_heap_finder.dart
+++ b/pkg/front_end/test/vm_service_heap_finder.dart
@@ -3,7 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import "dart:io";
-import "vm_service_heap_helper.dart";
+
+import "vm_service_helper.dart" as vmService;
 
 class Foo {
   final String x;
@@ -31,7 +32,7 @@
   foos.add(new Foo("!", 44));
 
   if (connectTo == null) connectTo = ask("Connect to");
-  VMServiceHeapHelperBase vm = VMServiceHeapHelperBase();
+  VMServiceHeapHelperPrinter vm = VMServiceHeapHelperPrinter();
   await vm.connect(Uri.parse(connectTo.trim()));
   String isolateId = await vm.getIsolateId();
   if (classToFind == null) classToFind = ask("Find what class");
@@ -61,3 +62,97 @@
   stdout.write("$question: ");
   return stdin.readLineSync();
 }
+
+class VMServiceHeapHelperPrinter extends vmService.VMServiceHelper {
+  Future<void> printAllocationProfile(String isolateId, {String filter}) async {
+    await waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (filter != null) {
+        if (member.classRef.name != filter) continue;
+      } else {
+        if (member.classRef.name == "") continue;
+        if (member.instancesCurrent == 0) continue;
+      }
+      vmService.Class c =
+          await serviceClient.getObject(isolateId, member.classRef.id);
+      if (c.location?.script?.uri == null) continue;
+      print("${member.classRef.name}: ${member.instancesCurrent}");
+    }
+  }
+
+  Future<void> filterAndPrintInstances(String isolateId, String filter,
+      String fieldName, Set<String> fieldValues) async {
+    await waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (member.classRef.name != filter) continue;
+      vmService.Class c =
+          await serviceClient.getObject(isolateId, member.classRef.id);
+      if (c.location?.script?.uri == null) continue;
+      print("${member.classRef.name}: ${member.instancesCurrent}");
+      print(c.location.script.uri);
+
+      vmService.InstanceSet instances = await serviceClient.getInstances(
+          isolateId, member.classRef.id, 10000);
+      int instanceNum = 0;
+      for (vmService.ObjRef instance in instances.instances) {
+        instanceNum++;
+        vmService.Obj receivedObject =
+            await serviceClient.getObject(isolateId, instance.id);
+        if (receivedObject is! vmService.Instance) continue;
+        vmService.Instance object = receivedObject;
+        for (vmService.BoundField field in object.fields) {
+          if (field.decl.name == fieldName) {
+            if (field.value is vmService.Sentinel) continue;
+            vmService.Obj receivedValue =
+                await serviceClient.getObject(isolateId, field.value.id);
+            if (receivedValue is! vmService.Instance) continue;
+            String value = (receivedValue as vmService.Instance).valueAsString;
+            if (!fieldValues.contains(value)) continue;
+            print("${instanceNum}: ${field.decl.name}: "
+                "${value} --- ${instance.id}");
+          }
+        }
+      }
+    }
+    print("Done!");
+  }
+
+  Future<void> printRetainingPaths(String isolateId, String filter) async {
+    await waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (member.classRef.name != filter) continue;
+      vmService.Class c =
+          await serviceClient.getObject(isolateId, member.classRef.id);
+      print("Found ${c.name} (location: ${c.location})");
+      print("${member.classRef.name}: "
+          "(instancesCurrent: ${member.instancesCurrent})");
+      print("");
+
+      vmService.InstanceSet instances = await serviceClient.getInstances(
+          isolateId, member.classRef.id, 10000);
+      print(" => Got ${instances.instances.length} instances");
+      print("");
+
+      for (vmService.ObjRef instance in instances.instances) {
+        vmService.Obj receivedObject =
+            await serviceClient.getObject(isolateId, instance.id);
+        print("Instance: $receivedObject");
+        vmService.RetainingPath retainingPath =
+            await serviceClient.getRetainingPath(isolateId, instance.id, 1000);
+        print("Retaining path: (length ${retainingPath.length}");
+        for (int i = 0; i < retainingPath.elements.length; i++) {
+          print("  [$i] = ${retainingPath.elements[i]}");
+        }
+
+        print("");
+      }
+    }
+    print("Done!");
+  }
+}
diff --git a/pkg/front_end/test/vm_service_heap_helper.dart b/pkg/front_end/test/vm_service_heap_helper.dart
index d801f3d..497c0b3 100644
--- a/pkg/front_end/test/vm_service_heap_helper.dart
+++ b/pkg/front_end/test/vm_service_heap_helper.dart
@@ -2,288 +2,11 @@
 // 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 "dart:convert";
-import "dart:io";
-
-import "package:vm_service/vm_service.dart" as vmService;
-import "package:vm_service/vm_service_io.dart" as vmService;
-
 import "dijkstras_sssp_algorithm.dart";
-
-class VMServiceHeapHelperBase {
-  vmService.VmService _serviceClient;
-  vmService.VmService get serviceClient => _serviceClient;
-
-  VMServiceHeapHelperBase();
-
-  Future connect(Uri observatoryUri) async {
-    String path = observatoryUri.path;
-    if (!path.endsWith("/")) path += "/";
-    String wsUriString = 'ws://${observatoryUri.authority}${path}ws';
-    _serviceClient = await vmService.vmServiceConnectUri(wsUriString,
-        log: const StdOutLog());
-  }
-
-  Future disconnect() async {
-    await _serviceClient.dispose();
-  }
-
-  Future<bool> waitUntilPaused(String isolateId) async {
-    int nulls = 0;
-    while (true) {
-      bool result = await _isPaused(isolateId);
-      if (result == null) {
-        nulls++;
-        if (nulls > 5) {
-          // We've now asked for the isolate 5 times and in all cases gotten
-          // `Sentinel`. Most likely things aren't working for whatever reason.
-          return false;
-        }
-      } else if (result) {
-        return true;
-      } else {
-        await Future.delayed(const Duration(milliseconds: 100));
-      }
-    }
-  }
-
-  Future<bool> _isPaused(String isolateId) async {
-    dynamic tmp = await _serviceClient.getIsolate(isolateId);
-    if (tmp is vmService.Isolate) {
-      vmService.Isolate isolate = tmp;
-      if (isolate.pauseEvent.kind != "Resume") return true;
-      return false;
-    }
-    return null;
-  }
-
-  Future<bool> _isPausedAtStart(String isolateId) async {
-    dynamic tmp = await _serviceClient.getIsolate(isolateId);
-    if (tmp is vmService.Isolate) {
-      vmService.Isolate isolate = tmp;
-      return isolate.pauseEvent.kind == "PauseStart";
-    }
-    return false;
-  }
-
-  Future<vmService.AllocationProfile> forceGC(String isolateId) async {
-    await waitUntilIsolateIsRunnable(isolateId);
-    int expectGcAfter = new DateTime.now().millisecondsSinceEpoch;
-    while (true) {
-      vmService.AllocationProfile allocationProfile;
-      try {
-        allocationProfile =
-            await _serviceClient.getAllocationProfile(isolateId, gc: true);
-      } catch (e) {
-        print(e.runtimeType);
-        rethrow;
-      }
-      if (allocationProfile.dateLastServiceGC != null &&
-          allocationProfile.dateLastServiceGC >= expectGcAfter) {
-        return allocationProfile;
-      }
-    }
-  }
-
-  Future<bool> isIsolateRunnable(String isolateId) async {
-    dynamic tmp = await _serviceClient.getIsolate(isolateId);
-    if (tmp is vmService.Isolate) {
-      vmService.Isolate isolate = tmp;
-      return isolate.runnable;
-    }
-    return null;
-  }
-
-  Future<void> waitUntilIsolateIsRunnable(String isolateId) async {
-    int nulls = 0;
-    while (true) {
-      bool result = await isIsolateRunnable(isolateId);
-      if (result == null) {
-        nulls++;
-        if (nulls > 5) {
-          // We've now asked for the isolate 5 times and in all cases gotten
-          // `Sentinel`. Most likely things aren't working for whatever reason.
-          return;
-        }
-      } else if (result) {
-        return;
-      } else {
-        await Future.delayed(const Duration(milliseconds: 100));
-      }
-    }
-  }
-
-  Future<void> printAllocationProfile(String isolateId, {String filter}) async {
-    await waitUntilIsolateIsRunnable(isolateId);
-    vmService.AllocationProfile allocationProfile =
-        await _serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
-      if (filter != null) {
-        if (member.classRef.name != filter) continue;
-      } else {
-        if (member.classRef.name == "") continue;
-        if (member.instancesCurrent == 0) continue;
-      }
-      vmService.Class c =
-          await _serviceClient.getObject(isolateId, member.classRef.id);
-      if (c.location?.script?.uri == null) continue;
-      print("${member.classRef.name}: ${member.instancesCurrent}");
-    }
-  }
-
-  Future<void> filterAndPrintInstances(String isolateId, String filter,
-      String fieldName, Set<String> fieldValues) async {
-    await waitUntilIsolateIsRunnable(isolateId);
-    vmService.AllocationProfile allocationProfile =
-        await _serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
-      if (member.classRef.name != filter) continue;
-      vmService.Class c =
-          await _serviceClient.getObject(isolateId, member.classRef.id);
-      if (c.location?.script?.uri == null) continue;
-      print("${member.classRef.name}: ${member.instancesCurrent}");
-      print(c.location.script.uri);
-
-      vmService.InstanceSet instances = await _serviceClient.getInstances(
-          isolateId, member.classRef.id, 10000);
-      int instanceNum = 0;
-      for (vmService.ObjRef instance in instances.instances) {
-        instanceNum++;
-        var receivedObject =
-            await _serviceClient.getObject(isolateId, instance.id);
-        if (receivedObject is! vmService.Instance) continue;
-        vmService.Instance object = receivedObject;
-        for (vmService.BoundField field in object.fields) {
-          if (field.decl.name == fieldName) {
-            if (field.value is vmService.Sentinel) continue;
-            var receivedValue =
-                await _serviceClient.getObject(isolateId, field.value.id);
-            if (receivedValue is! vmService.Instance) continue;
-            String value = (receivedValue as vmService.Instance).valueAsString;
-            if (!fieldValues.contains(value)) continue;
-            print("${instanceNum}: ${field.decl.name}: "
-                "${value} --- ${instance.id}");
-          }
-        }
-      }
-    }
-    print("Done!");
-  }
-
-  Future<void> printRetainingPaths(String isolateId, String filter) async {
-    await waitUntilIsolateIsRunnable(isolateId);
-    vmService.AllocationProfile allocationProfile =
-        await _serviceClient.getAllocationProfile(isolateId);
-    for (vmService.ClassHeapStats member in allocationProfile.members) {
-      if (member.classRef.name != filter) continue;
-      vmService.Class c =
-          await _serviceClient.getObject(isolateId, member.classRef.id);
-      print("Found ${c.name} (location: ${c.location})");
-      print("${member.classRef.name}: "
-          "(instancesCurrent: ${member.instancesCurrent})");
-      print("");
-
-      vmService.InstanceSet instances = await _serviceClient.getInstances(
-          isolateId, member.classRef.id, 10000);
-      print(" => Got ${instances.instances.length} instances");
-      print("");
-
-      for (vmService.ObjRef instance in instances.instances) {
-        var receivedObject =
-            await _serviceClient.getObject(isolateId, instance.id);
-        print("Instance: $receivedObject");
-        vmService.RetainingPath retainingPath =
-            await _serviceClient.getRetainingPath(isolateId, instance.id, 1000);
-        print("Retaining path: (length ${retainingPath.length}");
-        for (int i = 0; i < retainingPath.elements.length; i++) {
-          print("  [$i] = ${retainingPath.elements[i]}");
-        }
-
-        print("");
-      }
-    }
-    print("Done!");
-  }
-
-  Future<String> getIsolateId() async {
-    vmService.VM vm = await _serviceClient.getVM();
-    if (vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${vm.isolates.length}";
-    }
-    vmService.IsolateRef isolateRef = vm.isolates.single;
-    return isolateRef.id;
-  }
-}
-
-abstract class LaunchingVMServiceHeapHelper extends VMServiceHeapHelperBase {
-  Process _process;
-  Process get process => _process;
-
-  bool _started = false;
-
-  void start(List<String> scriptAndArgs,
-      {void stdinReceiver(String line),
-      void stderrReceiver(String line)}) async {
-    if (_started) throw "Already started";
-    _started = true;
-    _process = await Process.start(
-        Platform.resolvedExecutable,
-        ["--pause_isolates_on_start", "--enable-vm-service=0"]
-          ..addAll(scriptAndArgs));
-    _process.stdout
-        .transform(utf8.decoder)
-        .transform(new LineSplitter())
-        .listen((line) {
-      const kObservatoryListening = 'Observatory listening on ';
-      if (line.startsWith(kObservatoryListening)) {
-        Uri observatoryUri =
-            Uri.parse(line.substring(kObservatoryListening.length));
-        _setupAndRun(observatoryUri).catchError((e, st) {
-          // Manually kill the process or it will leak,
-          // see http://dartbug.com/42918
-          killProcess();
-          // This seems to rethrow.
-          throw e;
-        });
-      }
-      if (stdinReceiver != null) {
-        stdinReceiver(line);
-      } else {
-        stdout.writeln("> $line");
-      }
-    });
-    _process.stderr
-        .transform(utf8.decoder)
-        .transform(new LineSplitter())
-        .listen((line) {
-      if (stderrReceiver != null) {
-        stderrReceiver(line);
-      } else {
-        stderr.writeln("> $line");
-      }
-    });
-    // ignore: unawaited_futures
-    _process.exitCode.then((value) {
-      processExited(value);
-    });
-  }
-
-  void processExited(int exitCode) {}
-
-  void killProcess() {
-    _process.kill();
-  }
-
-  Future _setupAndRun(Uri observatoryUri) async {
-    await connect(observatoryUri);
-    await run();
-  }
-
-  Future<void> run();
-}
+import "vm_service_helper.dart" as vmService;
 
 class VMServiceHeapHelperSpecificExactLeakFinder
-    extends LaunchingVMServiceHeapHelper {
+    extends vmService.LaunchingVMServiceHelper {
   final Map<Uri, Map<String, List<String>>> _interests =
       new Map<Uri, Map<String, List<String>>>();
   final Map<Uri, Map<String, List<String>>> _prettyPrints =
@@ -326,7 +49,7 @@
   }
 
   void pause() async {
-    await _serviceClient.pause(_isolateRef.id);
+    await serviceClient.pause(_isolateRef.id);
   }
 
   vmService.VM _vm;
@@ -336,7 +59,7 @@
 
   /// Best effort check if the isolate is idle.
   Future<bool> isIdle() async {
-    dynamic tmp = await _serviceClient.getIsolate(_isolateRef.id);
+    dynamic tmp = await serviceClient.getIsolate(_isolateRef.id);
     if (tmp is vmService.Isolate) {
       vmService.Isolate isolate = tmp;
       return isolate.pauseEvent.topFrame == null;
@@ -346,15 +69,15 @@
 
   @override
   Future<void> run() async {
-    _vm = await _serviceClient.getVM();
+    _vm = await serviceClient.getVM();
     if (_vm.isolates.length != 1) {
       throw "Expected 1 isolate, got ${_vm.isolates.length}";
     }
     _isolateRef = _vm.isolates.single;
     await forceGC(_isolateRef.id);
 
-    assert(await _isPausedAtStart(_isolateRef.id));
-    await _serviceClient.resume(_isolateRef.id);
+    assert(await isPausedAtStart(_isolateRef.id));
+    await serviceClient.resume(_isolateRef.id);
 
     _iterationNumber = 1;
     while (true) {
@@ -364,7 +87,7 @@
 
       vmService.HeapSnapshotGraph heapSnapshotGraph =
           await vmService.HeapSnapshotGraph.getSnapshot(
-              _serviceClient, _isolateRef);
+              serviceClient, _isolateRef);
 
       Set<String> duplicatePrints = {};
       Map<String, List<vmService.HeapSnapshotObject>> groupedByToString = {};
@@ -396,7 +119,7 @@
         }
       }
 
-      await _serviceClient.resume(_isolateRef.id);
+      await serviceClient.resume(_isolateRef.id);
       _iterationNumber++;
     }
   }
@@ -695,20 +418,6 @@
   Interest(this.uri, this.className, this.fieldNames);
 }
 
-class StdOutLog implements vmService.Log {
-  const StdOutLog();
-
-  @override
-  void severe(String message) {
-    print("> SEVERE: $message");
-  }
-
-  @override
-  void warning(String message) {
-    print("> WARNING: $message");
-  }
-}
-
 HeapGraph convertHeapGraph(vmService.HeapSnapshotGraph graph) {
   HeapGraphClassSentinel classSentinel = new HeapGraphClassSentinel();
   List<HeapGraphClassActual> classes =
diff --git a/pkg/front_end/test/vm_service_helper.dart b/pkg/front_end/test/vm_service_helper.dart
new file mode 100644
index 0000000..a80eb5a
--- /dev/null
+++ b/pkg/front_end/test/vm_service_helper.dart
@@ -0,0 +1,215 @@
+// Copyright (c) 2020, 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 "dart:convert";
+import "dart:io";
+
+import "package:vm_service/vm_service.dart" as vmService;
+import "package:vm_service/vm_service_io.dart" as vmService;
+
+export "package:vm_service/vm_service.dart";
+export "package:vm_service/vm_service_io.dart";
+
+class VMServiceHelper {
+  vmService.VmService _serviceClient;
+  vmService.VmService get serviceClient => _serviceClient;
+
+  VMServiceHelper();
+
+  Future connect(Uri observatoryUri) async {
+    String path = observatoryUri.path;
+    if (!path.endsWith("/")) path += "/";
+    String wsUriString = 'ws://${observatoryUri.authority}${path}ws';
+    _serviceClient = await vmService.vmServiceConnectUri(wsUriString,
+        log: const StdOutLog());
+  }
+
+  Future disconnect() async {
+    await _serviceClient.dispose();
+  }
+
+  Future<bool> waitUntilPaused(String isolateId) async {
+    int nulls = 0;
+    while (true) {
+      bool result = await isPaused(isolateId);
+      if (result == null) {
+        nulls++;
+        if (nulls > 5) {
+          // We've now asked for the isolate 5 times and in all cases gotten
+          // `Sentinel`. Most likely things aren't working for whatever reason.
+          return false;
+        }
+      } else if (result) {
+        return true;
+      } else {
+        await Future.delayed(const Duration(milliseconds: 100));
+      }
+    }
+  }
+
+  Future<bool> isPaused(String isolateId) async {
+    dynamic tmp = await _serviceClient.getIsolate(isolateId);
+    if (tmp is vmService.Isolate) {
+      vmService.Isolate isolate = tmp;
+      if (isolate.pauseEvent.kind != "Resume") return true;
+      return false;
+    }
+    return null;
+  }
+
+  Future<bool> isPausedAtStart(String isolateId) async {
+    dynamic tmp = await _serviceClient.getIsolate(isolateId);
+    if (tmp is vmService.Isolate) {
+      vmService.Isolate isolate = tmp;
+      return isolate.pauseEvent.kind == "PauseStart";
+    }
+    return false;
+  }
+
+  Future<bool> isPausedAtExit(String isolateId) async {
+    dynamic tmp = await _serviceClient.getIsolate(isolateId);
+    if (tmp is vmService.Isolate) {
+      vmService.Isolate isolate = tmp;
+      return isolate.pauseEvent.kind == "PauseExit";
+    }
+    return false;
+  }
+
+  Future<vmService.AllocationProfile> forceGC(String isolateId) async {
+    await waitUntilIsolateIsRunnable(isolateId);
+    int expectGcAfter = new DateTime.now().millisecondsSinceEpoch;
+    while (true) {
+      vmService.AllocationProfile allocationProfile;
+      try {
+        allocationProfile =
+            await _serviceClient.getAllocationProfile(isolateId, gc: true);
+      } catch (e) {
+        print(e.runtimeType);
+        rethrow;
+      }
+      if (allocationProfile.dateLastServiceGC != null &&
+          allocationProfile.dateLastServiceGC >= expectGcAfter) {
+        return allocationProfile;
+      }
+    }
+  }
+
+  Future<bool> isIsolateRunnable(String isolateId) async {
+    dynamic tmp = await _serviceClient.getIsolate(isolateId);
+    if (tmp is vmService.Isolate) {
+      vmService.Isolate isolate = tmp;
+      return isolate.runnable;
+    }
+    return null;
+  }
+
+  Future<void> waitUntilIsolateIsRunnable(String isolateId) async {
+    int nulls = 0;
+    while (true) {
+      bool result = await isIsolateRunnable(isolateId);
+      if (result == null) {
+        nulls++;
+        if (nulls > 5) {
+          // We've now asked for the isolate 5 times and in all cases gotten
+          // `Sentinel`. Most likely things aren't working for whatever reason.
+          return;
+        }
+      } else if (result) {
+        return;
+      } else {
+        await Future.delayed(const Duration(milliseconds: 100));
+      }
+    }
+  }
+
+  Future<String> getIsolateId() async {
+    vmService.VM vm = await _serviceClient.getVM();
+    if (vm.isolates.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    }
+    vmService.IsolateRef isolateRef = vm.isolates.single;
+    return isolateRef.id;
+  }
+}
+
+class StdOutLog implements vmService.Log {
+  const StdOutLog();
+
+  @override
+  void severe(String message) {
+    print("> SEVERE: $message");
+  }
+
+  @override
+  void warning(String message) {
+    print("> WARNING: $message");
+  }
+}
+
+abstract class LaunchingVMServiceHelper extends VMServiceHelper {
+  Process _process;
+  Process get process => _process;
+
+  bool _started = false;
+
+  void start(List<String> scriptAndArgs,
+      {void stdinReceiver(String line),
+      void stderrReceiver(String line)}) async {
+    if (_started) throw "Already started";
+    _started = true;
+    _process = await Process.start(
+        Platform.resolvedExecutable,
+        ["--pause_isolates_on_start", "--enable-vm-service=0"]
+          ..addAll(scriptAndArgs));
+    _process.stdout
+        .transform(utf8.decoder)
+        .transform(new LineSplitter())
+        .listen((line) {
+      const kObservatoryListening = 'Observatory listening on ';
+      if (line.startsWith(kObservatoryListening)) {
+        Uri observatoryUri =
+            Uri.parse(line.substring(kObservatoryListening.length));
+        _setupAndRun(observatoryUri).catchError((e, st) {
+          // Manually kill the process or it will leak,
+          // see http://dartbug.com/42918
+          killProcess();
+          // This seems to rethrow.
+          throw e;
+        });
+      }
+      if (stdinReceiver != null) {
+        stdinReceiver(line);
+      } else {
+        stdout.writeln("> $line");
+      }
+    });
+    _process.stderr
+        .transform(utf8.decoder)
+        .transform(new LineSplitter())
+        .listen((line) {
+      if (stderrReceiver != null) {
+        stderrReceiver(line);
+      } else {
+        stderr.writeln("> $line");
+      }
+    });
+    // ignore: unawaited_futures
+    _process.exitCode.then((value) {
+      processExited(value);
+    });
+  }
+
+  void processExited(int exitCode) {}
+
+  void killProcess() {
+    _process.kill();
+  }
+
+  Future _setupAndRun(Uri observatoryUri) async {
+    await connect(observatoryUri);
+    await run();
+  }
+
+  Future<void> run();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 84e9a2e..a242d7e 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 126
+PRERELEASE 127
 PRERELEASE_PATCH 0
\ No newline at end of file