Version 2.14.0-203.0.dev

Merge commit '6ebcabaaf2caad5a78c995a7c6903ec6e152f18a' into 'dev'
diff --git a/pkg/analysis_server/lib/src/computer/computer_overrides.dart b/pkg/analysis_server/lib/src/computer/computer_overrides.dart
index 8bf505e..92f68d0 100644
--- a/pkg/analysis_server/lib/src/computer/computer_overrides.dart
+++ b/pkg/analysis_server/lib/src/computer/computer_overrides.dart
@@ -58,11 +58,13 @@
       var interfaceElements = overridesResult.interfaceElements;
       if (superElements.isNotEmpty || interfaceElements.isNotEmpty) {
         var superMember = superElements.isNotEmpty
-            ? proto.newOverriddenMember_fromEngine(superElements.first,
+            ? proto.newOverriddenMember_fromEngine(
+                superElements.first.nonSynthetic,
                 withNullability: _unit.isNonNullableByDefault)
             : null;
         var interfaceMembers = interfaceElements
-            .map((member) => proto.newOverriddenMember_fromEngine(member,
+            .map((member) => proto.newOverriddenMember_fromEngine(
+                member.nonSynthetic,
                 withNullability: _unit.isNonNullableByDefault))
             .toList();
         _overrides.add(proto.Override(node.offset, node.length,
diff --git a/pkg/analysis_server/lib/src/search/type_hierarchy.dart b/pkg/analysis_server/lib/src/search/type_hierarchy.dart
index d558487..988f96a 100644
--- a/pkg/analysis_server/lib/src/search/type_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/search/type_hierarchy.dart
@@ -82,10 +82,11 @@
       }
       // create a subclass item
       var subMemberElement = _findMemberElement(subElement);
+      var subMemberElementDeclared = subMemberElement?.nonSynthetic;
       subItem = TypeHierarchyItem(
           convertElement(subElement, withNullability: _isNonNullableByDefault),
-          memberElement: subMemberElement != null
-              ? convertElement(subMemberElement,
+          memberElement: subMemberElementDeclared != null
+              ? convertElement(subMemberElementDeclared,
                   withNullability: _isNonNullableByDefault)
               : null,
           superclass: itemId);
@@ -126,12 +127,13 @@
         displayName = classElement.displayName + '<' + typeArgumentsStr + '>';
       }
       var memberElement = _findMemberElement(classElement);
+      var memberElementDeclared = memberElement?.nonSynthetic;
       item = TypeHierarchyItem(
           convertElement(classElement,
               withNullability: _isNonNullableByDefault),
           displayName: displayName,
-          memberElement: memberElement != null
-              ? convertElement(memberElement,
+          memberElement: memberElementDeclared != null
+              ? convertElement(memberElementDeclared,
                   withNullability: _isNonNullableByDefault)
               : null);
       _elementItemMap[classElement] = item;
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
index 15cf6ef..af11a44 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
@@ -435,6 +435,14 @@
   }
 
   @override
+  void visitGenericTypeAlias(GenericTypeAlias node) {
+    if (entity == node.type) {
+      _addSuggestion(Keyword.DYNAMIC);
+      _addSuggestion(Keyword.VOID);
+    }
+  }
+
+  @override
   void visitIfElement(IfElement node) {
     _addCollectionElementKeywords();
     _addExpressionKeywords(node);
diff --git a/pkg/analysis_server/test/search/type_hierarchy_test.dart b/pkg/analysis_server/test/search/type_hierarchy_test.dart
index 4c54acd..2cc6f9f 100644
--- a/pkg/analysis_server/test/search/type_hierarchy_test.dart
+++ b/pkg/analysis_server/test/search/type_hierarchy_test.dart
@@ -539,13 +539,18 @@
   var test = 2;
 }
 ''');
-    var items = await _getTypeHierarchy('test = 2;');
-    var itemB = items[0];
-    var itemA = items[itemB.superclass!];
-    expect(itemA.classElement.name, 'A');
-    expect(itemB.classElement.name, 'B');
-    expect(itemA.memberElement!.location!.offset, findOffset('test = 1;'));
-    expect(itemB.memberElement!.location!.offset, findOffset('test = 2;'));
+
+    void checkItems(List<TypeHierarchyItem> items) {
+      var itemA = items.firstWhere((e) => e.classElement.name == 'A');
+      var itemB = items.firstWhere((e) => e.classElement.name == 'B');
+      var memberA = itemA.memberElement!;
+      var memberB = itemB.memberElement!;
+      expect(memberA.location!.offset, findOffset('test = 1;'));
+      expect(memberB.location!.offset, findOffset('test = 2;'));
+    }
+
+    checkItems(await _getTypeHierarchy('test = 1;'));
+    checkItems(await _getTypeHierarchy('test = 2;'));
   }
 
   Future<void> test_member_fromField_toGetter() async {
@@ -557,13 +562,18 @@
   var test = 2;
 }
 ''');
-    var items = await _getTypeHierarchy('test = 2;');
-    var itemB = items[0];
-    var itemA = items[itemB.superclass!];
-    expect(itemA.classElement.name, 'A');
-    expect(itemB.classElement.name, 'B');
-    expect(itemA.memberElement!.location!.offset, findOffset('test => 1'));
-    expect(itemB.memberElement!.location!.offset, findOffset('test = 2;'));
+
+    void checkItems(List<TypeHierarchyItem> items) {
+      var itemA = items.firstWhere((e) => e.classElement.name == 'A');
+      var itemB = items.firstWhere((e) => e.classElement.name == 'B');
+      var memberA = itemA.memberElement!;
+      var memberB = itemB.memberElement!;
+      expect(memberA.location!.offset, findOffset('test => 1'));
+      expect(memberB.location!.offset, findOffset('test = 2;'));
+    }
+
+    checkItems(await _getTypeHierarchy('test => 1;'));
+    checkItems(await _getTypeHierarchy('test = 2;'));
   }
 
   Future<void> test_member_fromField_toSetter() async {
@@ -575,13 +585,18 @@
   var test = 2;
 }
 ''');
-    var items = await _getTypeHierarchy('test = 2;');
-    var itemB = items[0];
-    var itemA = items[itemB.superclass!];
-    expect(itemA.classElement.name, 'A');
-    expect(itemB.classElement.name, 'B');
-    expect(itemA.memberElement!.location!.offset, findOffset('test(a) {}'));
-    expect(itemB.memberElement!.location!.offset, findOffset('test = 2;'));
+
+    void checkItems(List<TypeHierarchyItem> items) {
+      var itemA = items.firstWhere((e) => e.classElement.name == 'A');
+      var itemB = items.firstWhere((e) => e.classElement.name == 'B');
+      var memberA = itemA.memberElement!;
+      var memberB = itemB.memberElement!;
+      expect(memberA.location!.offset, findOffset('test(a) {}'));
+      expect(memberB.location!.offset, findOffset('test = 2;'));
+    }
+
+    checkItems(await _getTypeHierarchy('test(a) {}'));
+    checkItems(await _getTypeHierarchy('test = 2;'));
   }
 
   Future<void> test_member_fromFinalField_toGetter() async {
@@ -593,13 +608,18 @@
   final test = 2;
 }
 ''');
-    var items = await _getTypeHierarchy('test = 2;');
-    var itemB = items[0];
-    var itemA = items[itemB.superclass!];
-    expect(itemA.classElement.name, 'A');
-    expect(itemB.classElement.name, 'B');
-    expect(itemA.memberElement!.location!.offset, findOffset('test => 1;'));
-    expect(itemB.memberElement!.location!.offset, findOffset('test = 2;'));
+
+    void checkItems(List<TypeHierarchyItem> items) {
+      var itemA = items.firstWhere((e) => e.classElement.name == 'A');
+      var itemB = items.firstWhere((e) => e.classElement.name == 'B');
+      var memberA = itemA.memberElement!;
+      var memberB = itemB.memberElement!;
+      expect(memberA.location!.offset, findOffset('test => 1'));
+      expect(memberB.location!.offset, findOffset('test = 2;'));
+    }
+
+    checkItems(await _getTypeHierarchy('test => 1;'));
+    checkItems(await _getTypeHierarchy('test = 2;'));
   }
 
   Future<void> test_member_fromFinalField_toSetter() async {
@@ -612,10 +632,8 @@
 }
 ''');
     var items = await _getTypeHierarchy('test = 2;');
-    var itemB = items[0];
-    var itemA = items[itemB.superclass!];
-    expect(itemA.classElement.name, 'A');
-    expect(itemB.classElement.name, 'B');
+    var itemA = items.firstWhere((e) => e.classElement.name == 'A');
+    var itemB = items.firstWhere((e) => e.classElement.name == 'B');
     expect(itemA.memberElement, isNull);
     expect(itemB.memberElement!.location!.offset, findOffset('test = 2;'));
   }
diff --git a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
index 3b762f6..df4a4aa 100644
--- a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
+++ b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
@@ -15,6 +15,7 @@
     defineReflectiveTests(ConstructorCompletionTest);
     defineReflectiveTests(ExpressionFunctionBodyTest);
     defineReflectiveTests(ExtensionCompletionTest);
+    defineReflectiveTests(GenericTypeAliasTest);
     defineReflectiveTests(PropertyAccessorCompletionTest);
     defineReflectiveTests(RedirectedConstructorCompletionTest);
     defineReflectiveTests(RedirectingConstructorInvocationCompletionTest);
@@ -293,6 +294,17 @@
 }
 
 @reflectiveTest
+class GenericTypeAliasTest extends CompletionTestCase {
+  Future<void> test_constructor_abstract() async {
+    addTestFile('''
+typedef F = ^
+''');
+    await getSuggestions();
+    assertHasCompletion('void');
+  }
+}
+
+@reflectiveTest
 class PropertyAccessorCompletionTest extends CompletionTestCase {
   Future<void> test_setter_deprecated() async {
     addTestFile('''
diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart
index 22e6421b..7b1db81 100644
--- a/pkg/analyzer/lib/dart/element/element.dart
+++ b/pkg/analyzer/lib/dart/element/element.dart
@@ -611,6 +611,10 @@
   bool get hasUseResult;
 
   /// Return `true` if this element has an annotation of the form
+  /// `@visibleForOverriding`.
+  bool get hasVisibleForOverriding;
+
+  /// Return `true` if this element has an annotation of the form
   /// `@visibleForTemplate`.
   bool get hasVisibleForTemplate;
 
@@ -838,6 +842,10 @@
   bool get isUseResult;
 
   /// Return `true` if this annotation marks the associated member as being
+  /// visible for overriding only.
+  bool get isVisibleForOverriding;
+
+  /// Return `true` if this annotation marks the associated member as being
   /// visible for template files.
   bool get isVisibleForTemplate;
 
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 79c599e..9595fbe 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -553,9 +553,11 @@
   HintCode.INVALID_SEALED_ANNOTATION,
   HintCode.INVALID_USE_OF_INTERNAL_MEMBER,
   HintCode.INVALID_USE_OF_PROTECTED_MEMBER,
+  HintCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER,
   HintCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
   HintCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
   HintCode.INVALID_VISIBILITY_ANNOTATION,
+  HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION,
   HintCode.MISSING_JS_LIB_ANNOTATION,
   HintCode.MISSING_REQUIRED_PARAM,
   HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS,
diff --git a/pkg/analyzer/lib/error/listener.dart b/pkg/analyzer/lib/error/listener.dart
index 1d2df42..5bcacde 100644
--- a/pkg/analyzer/lib/error/listener.dart
+++ b/pkg/analyzer/lib/error/listener.dart
@@ -71,8 +71,8 @@
   /// is used to compute the location of the error.
   void reportErrorForElement(ErrorCode errorCode, Element element,
       [List<Object>? arguments]) {
-    reportErrorForOffset(
-        errorCode, element.nameOffset, element.nameLength, arguments);
+    reportErrorForOffset(errorCode, element.nonSynthetic.nameOffset,
+        element.nameLength, arguments);
   }
 
   /// Report a diagnostic with the given [code] and [arguments]. The
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 9a0e0a0..d354751 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -82,7 +82,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 146;
+  static const int DATA_VERSION = 147;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 1ddfbb1..d23483e 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -1760,6 +1760,10 @@
   /// requiring use.
   static const String _USE_RESULT_VARIABLE_NAME = "useResult";
 
+  /// The name of the top-level variable used to mark a member as being visible
+  /// for overriding only.
+  static const String _VISIBLE_FOR_OVERRIDING_NAME = 'visibleForOverriding';
+
   /// The name of the top-level variable used to mark a method as being
   /// visible for templates.
   static const String _VISIBLE_FOR_TEMPLATE_VARIABLE_NAME =
@@ -1879,6 +1883,10 @@
       _isPackageMetaGetter(_USE_RESULT_VARIABLE_NAME);
 
   @override
+  bool get isVisibleForOverriding =>
+      _isPackageMetaGetter(_VISIBLE_FOR_OVERRIDING_NAME);
+
+  @override
   bool get isVisibleForTemplate => _isTopGetter(
       libraryName: _NG_META_LIB_NAME,
       name: _VISIBLE_FOR_TEMPLATE_VARIABLE_NAME);
@@ -2257,6 +2265,18 @@
   }
 
   @override
+  bool get hasVisibleForOverriding {
+    final metadata = this.metadata;
+    for (var i = 0; i < metadata.length; i++) {
+      var annotation = metadata[i];
+      if (annotation.isVisibleForOverriding) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
   bool get hasVisibleForTemplate {
     final metadata = this.metadata;
     for (var i = 0; i < metadata.length; i++) {
@@ -4384,6 +4404,9 @@
   bool get hasUseResult => false;
 
   @override
+  bool get hasVisibleForOverriding => false;
+
+  @override
   bool get hasVisibleForTemplate => false;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/element/member.dart b/pkg/analyzer/lib/src/dart/element/member.dart
index bd52748..88dd044 100644
--- a/pkg/analyzer/lib/src/dart/element/member.dart
+++ b/pkg/analyzer/lib/src/dart/element/member.dart
@@ -522,6 +522,9 @@
   bool get hasUseResult => _declaration.hasUseResult;
 
   @override
+  bool get hasVisibleForOverriding => _declaration.hasVisibleForOverriding;
+
+  @override
   bool get hasVisibleForTemplate => _declaration.hasVisibleForTemplate;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index 1d0d7d2..586bb59 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -1245,6 +1245,17 @@
 
   /**
    * This hint is generated anywhere where a member annotated with
+   * `@visibleForOverriding` is used for another purpose than overriding it.
+   *
+   * Parameters:
+   * 0: the name of the member
+   */
+  static const HintCode INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER = HintCode(
+      'INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER',
+      "The member '{0}' can only be used for overriding.");
+
+  /**
+   * This hint is generated anywhere where a member annotated with
    * `@visibleForTemplate` is used outside of a "template" Dart file.
    *
    * Parameters:
@@ -1321,6 +1332,15 @@
           "meaningful on declarations of public members.",
       hasPublishedDocs: true);
 
+  /// Hint when an `@visibleForOverriding` annotation is used on something that
+  /// isn't an interface member.
+  static const HintCode INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION = HintCode(
+    'INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION',
+    "The declaration '{0}' is annotated with 'visibleForOverriding'. As '{0}' "
+        "is not an interface member that could be overriden, the annotation is "
+        'meaningless.',
+  );
+
   /**
    * Generate a hint for an element that is annotated with `@JS(...)` whose
    * library declaration is not similarly annotated.
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 285d5f7..cad7cf7 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -221,7 +221,8 @@
             HintCode.INVALID_SEALED_ANNOTATION, node, [node.element!.name]);
       }
     } else if (element.isVisibleForTemplate == true ||
-        element.isVisibleForTesting == true) {
+        element.isVisibleForTesting == true ||
+        element.isVisibleForOverriding == true) {
       if (parent is Declaration) {
         void reportInvalidAnnotation(Element declaredElement) {
           _errorReporter.reportErrorForNode(
@@ -230,23 +231,49 @@
               [declaredElement.name, node.name.name]);
         }
 
+        void reportInvalidVisibleForOverriding(Element declaredElement) {
+          _errorReporter.reportErrorForNode(
+              HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION,
+              node,
+              [declaredElement.name, node.name.name]);
+        }
+
         if (parent is TopLevelVariableDeclaration) {
           for (VariableDeclaration variable in parent.variables.variables) {
-            var element = variable.declaredElement as TopLevelVariableElement;
-            if (Identifier.isPrivateName(element.name)) {
-              reportInvalidAnnotation(element);
+            var variableElement =
+                variable.declaredElement as TopLevelVariableElement;
+
+            if (Identifier.isPrivateName(variableElement.name)) {
+              reportInvalidAnnotation(variableElement);
+            }
+
+            if (element.isVisibleForOverriding == true) {
+              // Top-level variables can't be overridden.
+              reportInvalidVisibleForOverriding(variableElement);
             }
           }
         } else if (parent is FieldDeclaration) {
           for (VariableDeclaration variable in parent.fields.variables) {
-            var element = variable.declaredElement as FieldElement;
-            if (Identifier.isPrivateName(element.name)) {
-              reportInvalidAnnotation(element);
+            var fieldElement = variable.declaredElement as FieldElement;
+            if (parent.isStatic && element.isVisibleForOverriding == true) {
+              reportInvalidVisibleForOverriding(fieldElement);
+            }
+
+            if (Identifier.isPrivateName(fieldElement.name)) {
+              reportInvalidAnnotation(fieldElement);
             }
           }
-        } else if (parent.declaredElement != null &&
-            Identifier.isPrivateName(parent.declaredElement!.name!)) {
-          reportInvalidAnnotation(parent.declaredElement!);
+        } else if (parent.declaredElement != null) {
+          final declaredElement = parent.declaredElement!;
+          if (element.isVisibleForOverriding &&
+              (!declaredElement.isInstanceMember ||
+                  declaredElement.enclosingElement is ExtensionElement)) {
+            reportInvalidVisibleForOverriding(declaredElement);
+          }
+
+          if (Identifier.isPrivateName(declaredElement.name!)) {
+            reportInvalidAnnotation(declaredElement);
+          }
         }
       } else {
         // Something other than a declaration was annotated. Whatever this is,
@@ -413,11 +440,12 @@
         }
 
         final overriddenElement = getOverriddenPropertyAccessor();
-        if (_hasNonVirtualAnnotation(overriddenElement)) {
+        if (overriddenElement != null &&
+            _hasNonVirtualAnnotation(overriddenElement)) {
           _errorReporter.reportErrorForNode(
               HintCode.INVALID_OVERRIDE_OF_NON_VIRTUAL_MEMBER,
               field.name,
-              [field.name, overriddenElement!.enclosingElement.name]);
+              [field.name, overriddenElement.enclosingElement.name]);
         }
         if (!_invalidAccessVerifier._inTestDirectory) {
           _checkForAssignmentOfDoNotStore(field.initializer);
@@ -557,10 +585,6 @@
 
     Name name = Name(_currentLibrary.source.uri, element.name);
 
-    bool elementIsOverride() =>
-        element is ClassMemberElement && enclosingElement is ClassElement
-            ? _inheritanceManager.getOverridden2(enclosingElement, name) != null
-            : false;
     ExecutableElement? getConcreteOverriddenElement() =>
         element is ClassMemberElement && enclosingElement is ClassElement
             ? _inheritanceManager.getMember2(enclosingElement, name,
@@ -583,21 +607,29 @@
       _mustCallSuperVerifier.checkMethodDeclaration(node);
       _checkForUnnecessaryNoSuchMethod(node);
 
-      if (!node.isSetter && !elementIsOverride()) {
+      var elementIsOverride = element is ClassMemberElement &&
+              enclosingElement is ClassElement
+          ? _inheritanceManager.getOverridden2(enclosingElement, name) != null
+          : false;
+
+      if (!node.isSetter && !elementIsOverride) {
         _checkStrictInferenceReturnType(node.returnType, node, node.name.name);
       }
-      _checkStrictInferenceInParameters(node.parameters, body: node.body);
+      if (!elementIsOverride) {
+        _checkStrictInferenceInParameters(node.parameters, body: node.body);
+      }
 
       var overriddenElement = getConcreteOverriddenElement();
       if (overriddenElement == null && (node.isSetter || node.isGetter)) {
         overriddenElement = getOverriddenPropertyAccessor();
       }
 
-      if (_hasNonVirtualAnnotation(overriddenElement)) {
+      if (overriddenElement != null &&
+          _hasNonVirtualAnnotation(overriddenElement)) {
         _errorReporter.reportErrorForNode(
             HintCode.INVALID_OVERRIDE_OF_NON_VIRTUAL_MEMBER,
             node.name,
-            [node.name, overriddenElement!.enclosingElement.name]);
+            [node.name, overriddenElement.enclosingElement.name]);
       }
 
       super.visitMethodDeclaration(node);
@@ -1712,10 +1744,7 @@
     return identifier?.name ?? '';
   }
 
-  static bool _hasNonVirtualAnnotation(ExecutableElement? element) {
-    if (element == null) {
-      return false;
-    }
+  static bool _hasNonVirtualAnnotation(ExecutableElement element) {
     if (element is PropertyAccessorElement && element.isSynthetic) {
       return element.variable.hasNonVirtual;
     }
@@ -1870,6 +1899,8 @@
       }
     }
 
+    bool hasVisibleForOverriding = _hasVisibleForOverriding(element);
+
     // At this point, [identifier] was not cleared as protected access, nor
     // cleared as access for templates or testing. Report a violation for each
     // annotation present.
@@ -1907,6 +1938,11 @@
           node,
           [name, definingClass!.source!.uri]);
     }
+
+    if (hasVisibleForOverriding) {
+      _errorReporter.reportErrorForNode(
+          HintCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER, node, [name]);
+    }
   }
 
   bool _hasInternal(Element? element) {
@@ -1943,6 +1979,19 @@
     return element.thisType.asInstanceOf(superElement) != null;
   }
 
+  bool _hasVisibleForOverriding(Element element) {
+    if (element.hasVisibleForOverriding) {
+      return true;
+    }
+
+    if (element is PropertyAccessorElement &&
+        element.variable.hasVisibleForOverriding) {
+      return true;
+    }
+
+    return false;
+  }
+
   bool _hasVisibleForTemplate(Element? element) {
     if (element == null) {
       return false;
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 5812ae5..ffd5ef7 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -3432,7 +3432,7 @@
         filePath: property.source.fullName,
         message:
             "'$propertyName' refers to a property so it couldn't be promoted",
-        offset: property.nameOffset,
+        offset: property.nonSynthetic.nameOffset,
         length: property.nameLength,
         url: reason.documentationLink);
   }
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_packages.dart b/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
index dc79ac0..226370a 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
@@ -37,6 +37,7 @@
 const Required required = const Required();
 const _Sealed sealed = const _Sealed();
 const UseResult useResult = UseResult();
+const _VisibleForOverriding visibleForOverriding = _VisibleForOverriding();
 const _VisibleForTesting visibleForTesting = const _VisibleForTesting();
 
 class _AlwaysThrows {
@@ -88,6 +89,9 @@
   final String reason;
   const UseResult([this.reason = '']);
 }
+class _VisibleForOverriding {
+  const _VisibleForOverriding();
+}
 class _VisibleForTesting {
   const _VisibleForTesting();
 }
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index cf757a8..03dd0ff 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -835,7 +835,7 @@
     await resolveTestFile();
     {
       var element = findNode.simple('a;').staticElement!;
-      expect(element.nameOffset, 4);
+      expect(element.nonSynthetic.nameOffset, 4);
     }
 
     // New resolver.
@@ -844,7 +844,7 @@
     await resolveTestFile();
     {
       var element = findNode.simple('a;').staticElement!;
-      expect(element.nameOffset, 4);
+      expect(element.nonSynthetic.nameOffset, 4);
     }
   }
 
diff --git a/pkg/analyzer/test/src/diagnostics/inference_failure_on_untyped_parameter_test.dart b/pkg/analyzer/test/src/diagnostics/inference_failure_on_untyped_parameter_test.dart
index a93f67e..1ce6f9d 100644
--- a/pkg/analyzer/test/src/diagnostics/inference_failure_on_untyped_parameter_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/inference_failure_on_untyped_parameter_test.dart
@@ -208,22 +208,20 @@
   }
 
   test_parameter_inOverridingMethod() async {
-    await assertErrorsInCode(r'''
+    await assertNoErrorsInCode(r'''
 class C {
   void fn(int a) => print(a);
 }
 
 class D extends C {
   @override
-  void fn(var a) => print(a);
+  void fn(a) => print(a);
 }
-''', [
-      error(HintCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER, 85, 5),
-    ]);
+''');
   }
 
   test_parameter_inOverridingMethod_withDefault() async {
-    await assertErrorsInCode(r'''
+    await assertNoErrorsInCode(r'''
 class C {
   void fn([int a = 7]) => print(a);
 }
@@ -232,9 +230,7 @@
   @override
   void fn([var a = 7]) => print(a);
 }
-''', [
-      error(HintCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER, 92, 5),
-    ]);
+''');
   }
 
   test_parameter_inOverridingMethod_withDefaultAndType() async {
@@ -263,6 +259,19 @@
 ''');
   }
 
+  test_parameter_inOverridingMethod_withVar() async {
+    await assertNoErrorsInCode(r'''
+class C {
+  void fn(int a) => print(a);
+}
+
+class D extends C {
+  @override
+  void fn(var a) => print(a);
+}
+''');
+  }
+
   test_parameter_inTypedef_withoutType() async {
     await assertErrorsInCode(r'''
 typedef void cb(a);
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_use_of_visible_for_overriding_member_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_use_of_visible_for_overriding_member_test.dart
new file mode 100644
index 0000000..0387086
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/invalid_use_of_visible_for_overriding_member_test.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(InvalidUseOfVisibleForOverridingMemberTest);
+  });
+}
+
+@reflectiveTest
+class InvalidUseOfVisibleForOverridingMemberTest
+    extends PubPackageResolutionTest {
+  @override
+  void setUp() {
+    super.setUp();
+    writeTestPackageConfigWithMeta();
+  }
+
+  test_differentLibrary_invalid() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+import 'package:meta/meta.dart';
+
+class Parent {
+  @visibleForOverriding
+  void foo() {}
+}
+''');
+    await assertErrorsInCode('''
+import 'a.dart';
+
+class Child extends Parent {
+  Child() {
+    foo();
+  }
+}
+''', [
+      error(HintCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER, 63, 3),
+    ]);
+  }
+
+  test_differentLibrary_valid_onlyOverride() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+import 'package:meta/meta.dart';
+
+class Parent {
+  @visibleForOverriding
+  void foo() {}
+}
+''');
+
+    await assertNoErrorsInCode('''
+import 'a.dart';
+
+class Child extends Parent {
+  @override
+  void foo() {}
+}
+''');
+  }
+
+  test_differentLibrary_valid_overrideAndUse() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+import 'package:meta/meta.dart';
+
+class Parent {
+  @visibleForOverriding
+  void foo() {}
+}
+''');
+
+    await assertNoErrorsInCode('''
+import 'a.dart';
+
+class Child extends Parent {
+  @override
+  void foo() {}
+
+  void bar() {
+    foo();
+  }
+}
+''');
+  }
+
+  test_sameLibrary() async {
+    await assertNoErrorsInCode('''
+import 'package:meta/meta.dart';
+class Parent {
+  @visibleForOverriding
+  void foo() {}
+}
+
+class Child extends Parent {
+  Child() {
+    foo();
+  }
+}
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_visible_for_overriding_annotation_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_visible_for_overriding_annotation_test.dart
new file mode 100644
index 0000000..910acf3
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/invalid_visible_for_overriding_annotation_test.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/error/hint_codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(InvalidVisibleForOverridingAnnotationTest);
+  });
+}
+
+@reflectiveTest
+class InvalidVisibleForOverridingAnnotationTest
+    extends PubPackageResolutionTest {
+  @override
+  void setUp() {
+    super.setUp();
+    writeTestPackageConfigWithMeta();
+  }
+
+  test_invalid_class() async {
+    await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+@visibleForOverriding
+class C {}
+''', [error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 33, 21)]);
+  }
+
+  test_invalid_constructor() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+class C {
+  @visibleForOverriding
+  C();
+}
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 45, 21),
+    ]);
+  }
+
+  test_invalid_extensionMember() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+extension E on String {
+  @visibleForOverriding
+  void foo() {}
+}
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 59, 21),
+    ]);
+  }
+
+  test_invalid_staticMember() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+class C {
+  @visibleForOverriding
+  static void m() {}
+}
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 45, 21),
+    ]);
+  }
+
+  test_invalid_topLevelFunction() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+@visibleForOverriding void foo() {}
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 33, 21),
+    ]);
+  }
+
+  test_invalid_topLevelVariable() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+@visibleForOverriding final a = 1;
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 33, 21),
+    ]);
+  }
+
+  test_invalid_topLevelVariable_multi() async {
+    await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+@visibleForOverriding var a = 1, b;
+''', [
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 33, 21),
+      error(HintCode.INVALID_VISIBLE_FOR_OVERRIDING_ANNOTATION, 33, 21),
+    ]);
+  }
+
+  test_valid() async {
+    await assertNoErrorsInCode(r'''
+import 'package:meta/meta.dart';
+
+class C {
+  @visibleForOverriding
+  void m() {}
+  @visibleForOverriding
+  int x = 3;
+  @visibleForOverriding
+  int get y => 5;
+}
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index ff961d3..fefb145 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -354,12 +354,16 @@
     as invalid_use_of_internal_member;
 import 'invalid_use_of_protected_member_test.dart'
     as invalid_use_of_protected_member;
+import 'invalid_use_of_visible_for_overriding_member_test.dart'
+    as invalid_use_of_visible_for_overriding_member;
 import 'invalid_use_of_visible_for_template_member_test.dart'
     as invalid_use_of_visible_for_template_member;
 import 'invalid_use_of_visible_for_testing_member_test.dart'
     as invalid_use_of_visible_for_testing_member;
 import 'invalid_visibility_annotation_test.dart'
     as invalid_visibility_annotation;
+import 'invalid_visible_for_overriding_annotation_test.dart'
+    as invalid_visible_for_overriding_annotation;
 import 'invocation_of_extension_without_call_test.dart'
     as invocation_of_extension_without_call;
 import 'invocation_of_non_function_expression_test.dart'
@@ -937,9 +941,11 @@
     invalid_use_of_covariant_in_extension.main();
     invalid_use_of_internal_member.main();
     invalid_use_of_protected_member.main();
+    invalid_use_of_visible_for_overriding_member.main();
     invalid_use_of_visible_for_template_member.main();
     invalid_use_of_visible_for_testing_member.main();
     invalid_visibility_annotation.main();
+    invalid_visible_for_overriding_annotation.main();
     invocation_of_extension_without_call.main();
     invocation_of_non_function_expression.main();
     label_in_outer_scope.main();
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 85ca410..693b9c0 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -9,8 +9,10 @@
 import 'package:vm_service/vm_service.dart' as vm;
 
 import '../base_debug_adapter.dart';
+import '../exceptions.dart';
 import '../isolate_manager.dart';
 import '../logging.dart';
+import '../protocol_converter.dart';
 import '../protocol_generated.dart';
 import '../protocol_stream.dart';
 
@@ -56,10 +58,13 @@
   final _debuggerInitializedCompleter = Completer<void>();
   final _configurationDoneCompleter = Completer<void>();
 
-  /// Managers VM Isolates and their events, including fanning out any requests
+  /// Manages VM Isolates and their events, including fanning out any requests
   /// to set breakpoints etc. from the client to all Isolates.
   late IsolateManager _isolateManager;
 
+  /// A helper that handlers converting to/from DAP and VM Service types.
+  late ProtocolConverter _converter;
+
   /// All active VM Service subscriptions.
   ///
   /// TODO(dantup): This may be changed to use StreamManager as part of using
@@ -80,6 +85,7 @@
   DartDebugAdapter(ByteStreamServerChannel channel, Logger? logger)
       : super(channel, logger) {
     _isolateManager = IsolateManager(this);
+    _converter = ProtocolConverter(this);
   }
 
   /// Completes when the debugger initialization has completed. Used to delay
@@ -92,19 +98,16 @@
   /// an existing app. This will only be called once (and only one of this or
   /// launchRequest will be called).
   @override
-  FutureOr<void> attachRequest(
+  Future<void> attachRequest(
     Request request,
     T args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   ) async {
     this.args = args;
     isAttach = true;
 
-    // Don't start launching until configurationDone.
-    if (!_configurationDoneCompleter.isCompleted) {
-      logger?.call('Waiting for configurationDone request...');
-      await _configurationDoneCompleter.future;
-    }
+    // Common setup.
+    await _prepareForLaunchOrAttach();
 
     // TODO(dantup): Implement attach support.
     throw UnimplementedError();
@@ -112,7 +115,7 @@
     // Delegate to the sub-class to attach to the process.
     // await attachImpl();
     //
-    // sendResponse(null);
+    // sendResponse();
   }
 
   /// configurationDone is called by the client when it has finished sending
@@ -123,13 +126,13 @@
   /// been sent to ensure we're not still getting breakpoints (which are sent
   /// per-file) while we're launching and initializing over the VM Service.
   @override
-  FutureOr<void> configurationDoneRequest(
+  Future<void> configurationDoneRequest(
     Request request,
     ConfigurationDoneArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   ) async {
     _configurationDoneCompleter.complete();
-    sendResponse(null);
+    sendResponse();
   }
 
   /// Connects to the VM Service at [uri] and initializes debugging.
@@ -219,13 +222,25 @@
     _debuggerInitializedCompleter.complete();
   }
 
+  /// Handles the clients "continue" ("resume") request for the thread in
+  /// [args.threadId].
+  @override
+  Future<void> continueRequest(
+    Request request,
+    ContinueArguments args,
+    void Function(ContinueResponseBody) sendResponse,
+  ) async {
+    await _isolateManager.resumeThread(args.threadId);
+    sendResponse(ContinueResponseBody(allThreadsContinued: false));
+  }
+
   /// Overridden by sub-classes to perform any additional setup after the VM
   /// Service is connected.
-  FutureOr<void> debuggerConnected(vm.VM vmInfo);
+  Future<void> debuggerConnected(vm.VM vmInfo);
 
   /// Overridden by sub-classes to handle when the client sends a
   /// `disconnectRequest` (a forceful request to shut down).
-  FutureOr<void> disconnectImpl();
+  Future<void> disconnectImpl();
 
   /// disconnectRequest is called by the client when it wants to forcefully shut
   /// us down quickly. This comes after the `terminateRequest` which is intended
@@ -237,13 +252,13 @@
   ///
   /// https://microsoft.github.io/debug-adapter-protocol/overview#debug-session-end
   @override
-  FutureOr<void> disconnectRequest(
+  Future<void> disconnectRequest(
     Request request,
     DisconnectArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   ) async {
     await disconnectImpl();
-    sendResponse(null);
+    sendResponse();
   }
 
   /// initializeRequest is the first request send by the client during
@@ -254,7 +269,7 @@
   /// https://microsoft.github.io/debug-adapter-protocol/overview#initialization
   /// with a summary in this classes description.
   @override
-  FutureOr<void> initializeRequest(
+  Future<void> initializeRequest(
     Request request,
     InitializeRequestArguments? args,
     void Function(Capabilities) sendResponse,
@@ -293,30 +308,39 @@
   ///
   /// Sub-classes can use the [args] field to access the arguments provided
   /// to this request.
-  FutureOr<void> launchImpl();
+  Future<void> launchImpl();
 
   /// launchRequest is called by the client when it wants us to to start the app
   /// to be run/debug. This will only be called once (and only one of this or
   /// attachRequest will be called).
   @override
-  FutureOr<void> launchRequest(
+  Future<void> launchRequest(
     Request request,
     T args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   ) async {
     this.args = args;
     isAttach = false;
 
-    // Don't start launching until configurationDone.
-    if (!_configurationDoneCompleter.isCompleted) {
-      logger?.call('Waiting for configurationDone request...');
-      await _configurationDoneCompleter.future;
-    }
+    // Common setup.
+    await _prepareForLaunchOrAttach();
 
     // Delegate to the sub-class to launch the process.
     await launchImpl();
 
-    sendResponse(null);
+    sendResponse();
+  }
+
+  /// Handles the clients "next" ("step over") request for the thread in
+  /// [args.threadId].
+  @override
+  Future<void> nextRequest(
+    Request request,
+    NextArguments args,
+    void Function() sendResponse,
+  ) async {
+    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kOver);
+    sendResponse();
   }
 
   /// Sends an OutputEvent (without a newline, since calls to this method
@@ -337,9 +361,160 @@
     sendOutput(category, '$prefix$indentedMessage\n');
   }
 
+  /// Handles a request from the client to set breakpoints.
+  ///
+  /// This method can be called at any time (before the app is launched or while
+  /// the app is running) and will include the new full set of breakpoints for
+  /// the file URI in [args.source.path].
+  ///
+  /// The VM requires breakpoints to be set per-isolate so these will be passed
+  /// to [_isolateManager] that will fan them out to each isolate.
+  ///
+  /// When new isolates are registered, it is [isolateManager]s responsibility
+  /// to ensure all breakpoints are given to them (and like at startup, this
+  /// must happen before they are resumed).
+  @override
+  Future<void> setBreakpointsRequest(
+    Request request,
+    SetBreakpointsArguments args,
+    void Function(SetBreakpointsResponseBody) sendResponse,
+  ) async {
+    final breakpoints = args.breakpoints ?? [];
+
+    final path = args.source.path;
+    final name = args.source.name;
+    final uri = path != null ? Uri.file(path).toString() : name!;
+
+    await _isolateManager.setBreakpoints(uri, breakpoints);
+
+    // TODO(dantup): Handle breakpoint resolution rather than pretending all
+    // breakpoints are verified immediately.
+    sendResponse(SetBreakpointsResponseBody(
+      breakpoints: breakpoints.map((e) => Breakpoint(verified: true)).toList(),
+    ));
+  }
+
+  /// Handles a request from the client for the call stack for [args.threadId].
+  ///
+  /// This is usually called after we sent a [StoppedEvent] to the client
+  /// notifying it that execution of an isolate has paused and it wants to
+  /// populate the call stack view.
+  ///
+  /// Clients may fetch the frames in batches and VS Code in particular will
+  /// send two requests initially - one for the top frame only, and then one for
+  /// the next 19 frames. For better performance, the first request is satisfied
+  /// entirely from the threads pauseEvent.topFrame so we do not need to
+  /// round-trip to the VM Service.
+  @override
+  Future<void> stackTraceRequest(
+    Request request,
+    StackTraceArguments args,
+    void Function(StackTraceResponseBody) sendResponse,
+  ) async {
+    // We prefer to provide frames in small batches. Rather than tell the client
+    // how many frames there really are (which can be expensive to compute -
+    // especially for web) we just add 20 on to the last frame we actually send,
+    // as described in the spec:
+    //
+    // "Returning monotonically increasing totalFrames values for subsequent
+    //  requests can be used to enforce paging in the client."
+    const stackFrameBatchSize = 20;
+
+    final threadId = args.threadId;
+    final thread = _isolateManager.getThread(threadId);
+    final topFrame = thread?.pauseEvent?.topFrame;
+    final startFrame = args.startFrame ?? 0;
+    final numFrames = args.levels ?? 0;
+    var totalFrames = 1;
+
+    if (thread == null) {
+      throw DebugAdapterException('No thread with threadId $threadId');
+    }
+
+    if (!thread.paused) {
+      throw DebugAdapterException('Thread $threadId is not paused');
+    }
+
+    final stackFrames = <StackFrame>[];
+    // If the request is only for the top frame, we may be able to satisfy it
+    // from the threads `pauseEvent.topFrame`.
+    if (startFrame == 0 && numFrames == 1 && topFrame != null) {
+      totalFrames = 1 + stackFrameBatchSize;
+      final dapTopFrame = await _converter.convertVmToDapStackFrame(
+        thread,
+        topFrame,
+        isTopFrame: true,
+      );
+      stackFrames.add(dapTopFrame);
+    } else {
+      // Otherwise, send the request on to the VM.
+      // The VM doesn't support fetching an arbitrary slice of frames, only a
+      // maximum limit, so if the client asks for frames 20-30 we must send a
+      // request for the first 30 and trim them ourselves.
+      final limit = startFrame + numFrames;
+      final stack = await vmService?.getStack(thread.isolate.id!, limit: limit);
+      final frames = stack?.frames;
+
+      if (stack != null && frames != null) {
+        // When the call stack is truncated, we always add [stackFrameBatchSize]
+        // to the count, indicating to the client there are more frames and
+        // the size of the batch they should request when "loading more".
+        //
+        // It's ok to send a number that runs past the actual end of the call
+        // stack and the client should handle this gracefully:
+        //
+        // "a client should be prepared to receive less frames than requested,
+        //  which is an indication that the end of the stack has been reached."
+        totalFrames = (stack.truncated ?? false)
+            ? frames.length + stackFrameBatchSize
+            : frames.length;
+
+        Future<StackFrame> convert(int index, vm.Frame frame) async {
+          return _converter.convertVmToDapStackFrame(
+            thread,
+            frame,
+            isTopFrame: startFrame == 0 && index == 0,
+          );
+        }
+
+        final frameSubset = frames.sublist(startFrame);
+        stackFrames.addAll(await Future.wait(frameSubset.mapIndexed(convert)));
+      }
+    }
+
+    sendResponse(
+      StackTraceResponseBody(
+        stackFrames: stackFrames,
+        totalFrames: totalFrames,
+      ),
+    );
+  }
+
+  /// Handles the clients "step in" request for the thread in [args.threadId].
+  @override
+  Future<void> stepInRequest(
+    Request request,
+    StepInArguments args,
+    void Function() sendResponse,
+  ) async {
+    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kInto);
+    sendResponse();
+  }
+
+  /// Handles the clients "step out" request for the thread in [args.threadId].
+  @override
+  Future<void> stepOutRequest(
+    Request request,
+    StepOutArguments args,
+    void Function() sendResponse,
+  ) async {
+    await _isolateManager.resumeThread(args.threadId, vm.StepOption.kOut);
+    sendResponse();
+  }
+
   /// Overridden by sub-classes to handle when the client sends a
   /// `terminateRequest` (a request for a graceful shut down).
-  FutureOr<void> terminateImpl();
+  Future<void> terminateImpl();
 
   /// terminateRequest is called by the client when it wants us to gracefully
   /// shut down.
@@ -350,13 +525,13 @@
   ///
   /// https://microsoft.github.io/debug-adapter-protocol/overview#debug-session-end
   @override
-  FutureOr<void> terminateRequest(
+  Future<void> terminateRequest(
     Request request,
     TerminateArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   ) async {
-    terminateImpl();
-    sendResponse(null);
+    await terminateImpl();
+    sendResponse();
   }
 
   void _handleDebugEvent(vm.Event event) {
@@ -378,7 +553,7 @@
     /// Helper to convert to InstanceRef to a String, taking into account
     /// [vm.InstanceKind.kNull] which is the type for the unused fields of a
     /// log event.
-    FutureOr<String?> asString(vm.InstanceRef? ref) {
+    Future<String?> asString(vm.InstanceRef? ref) async {
       if (ref == null || ref.kind == vm.InstanceKind.kNull) {
         return null;
       }
@@ -407,6 +582,21 @@
     }
   }
 
+  /// Performs some setup that is common to both [launchRequest] and
+  /// [attachRequest].
+  Future<void> _prepareForLaunchOrAttach() async {
+    // Don't start launching until configurationDone.
+    if (!_configurationDoneCompleter.isCompleted) {
+      logger?.call('Waiting for configurationDone request...');
+      await _configurationDoneCompleter.future;
+    }
+
+    // Notify IsolateManager if we'll be debugging so it knows whether to set
+    // up breakpoints etc. when isolates are registered.
+    final debug = !(args.noDebug ?? false);
+    _isolateManager.setDebugEnabled(debug);
+  }
+
   /// A wrapper around the same name function from package:vm_service that
   /// allows logging all traffic over the VM Service.
   Future<vm.VmService> _vmServiceConnectUri(
@@ -455,8 +645,37 @@
   final int? vmServicePort;
   final List<String>? vmAdditionalArgs;
   final bool? enableAsserts;
+
+  /// Whether SDK libraries should be marked as debuggable.
+  ///
+  /// Treated as `false` if null, which means "step in" will not step into SDK
+  /// libraries.
   final bool? debugSdkLibraries;
+
+  /// Whether external package libraries should be marked as debuggable.
+  ///
+  /// Treated as `false` if null, which means "step in" will not step into
+  /// libraries in packages that are not either the local package or a path
+  /// dependency. This allows users to debug "just their code" and treat Pub
+  /// packages as block boxes.
+  final bool? debugExternalPackageLibraries;
+
+  /// Whether to evaluate getters in debug views like hovers and the variables
+  /// list.
+  ///
+  /// Invoking getters has a performance cost and may introduce side-effects,
+  /// although users may expected this functionality. null is treated like false
+  /// although clients may have their own defaults (for example Dart-Code sends
+  /// true by default at the time of writing).
   final bool? evaluateGettersInDebugViews;
+
+  /// Whether to call toString() on objects in debug views like hovers and the
+  /// variables list.
+  ///
+  /// Invoking toString() has a performance cost and may introduce side-effects,
+  /// although users may expected this functionality. null is treated like false
+  /// although clients may have their own defaults (for example Dart-Code sends
+  /// true by default at the time of writing).
   final bool? evaluateToStringInDebugViews;
 
   /// Whether to send debug logging to clients in a custom `dart.log` event. This
@@ -476,6 +695,7 @@
     this.vmAdditionalArgs,
     this.enableAsserts,
     this.debugSdkLibraries,
+    this.debugExternalPackageLibraries,
     this.evaluateGettersInDebugViews,
     this.evaluateToStringInDebugViews,
     this.sendLogsToClient,
@@ -490,6 +710,8 @@
         vmAdditionalArgs = (obj['vmAdditionalArgs'] as List?)?.cast<String>(),
         enableAsserts = obj['enableAsserts'] as bool?,
         debugSdkLibraries = obj['debugSdkLibraries'] as bool?,
+        debugExternalPackageLibraries =
+            obj['debugExternalPackageLibraries'] as bool?,
         evaluateGettersInDebugViews =
             obj['evaluateGettersInDebugViews'] as bool?,
         evaluateToStringInDebugViews =
@@ -508,6 +730,8 @@
         if (vmAdditionalArgs != null) 'vmAdditionalArgs': vmAdditionalArgs,
         if (enableAsserts != null) 'enableAsserts': enableAsserts,
         if (debugSdkLibraries != null) 'debugSdkLibraries': debugSdkLibraries,
+        if (debugExternalPackageLibraries != null)
+          'debugExternalPackageLibraries': debugExternalPackageLibraries,
         if (evaluateGettersInDebugViews != null)
           'evaluateGettersInDebugViews': evaluateGettersInDebugViews,
         if (evaluateToStringInDebugViews != null)
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli.dart b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
index 311955e9..007943f 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
@@ -44,7 +44,7 @@
   DartCliDebugAdapter(ByteStreamServerChannel channel, [Logger? logger])
       : super(channel, logger);
 
-  FutureOr<void> debuggerConnected(vm.VM vmInfo) {
+  Future<void> debuggerConnected(vm.VM vmInfo) async {
     if (!isAttach) {
       // Capture the PID from the VM Service so that we can terminate it when
       // cleaning up. Terminating the process might not be enough as it could be
@@ -60,7 +60,7 @@
 
   /// Called by [disconnectRequest] to request that we forcefully shut down the
   /// app being run (or in the case of an attach, disconnect).
-  FutureOr<void> disconnectImpl() {
+  Future<void> disconnectImpl() async {
     // TODO(dantup): In Dart-Code DAP, we first try again with sigint and wait
     // for a few seconds before sending sigkill.
     pidsToTerminate.forEach(
@@ -137,7 +137,7 @@
 
   /// Called by [terminateRequest] to request that we gracefully shut down the
   /// app being run (or in the case of an attach, disconnect).
-  FutureOr<void> terminateImpl() async {
+  Future<void> terminateImpl() async {
     pidsToTerminate.forEach(
       (pid) => Process.killPid(pid, ProcessSignal.sigint),
     );
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 0c00630..9750cd6 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -11,6 +11,12 @@
 
 typedef _FromJsonHandler<T> = T Function(Map<String, Object?>);
 typedef _NullableFromJsonHandler<T> = T? Function(Map<String, Object?>?);
+typedef _RequestHandler<TArg, TResp> = Future<void> Function(
+    Request, TArg, void Function(TResp));
+typedef _VoidArgRequestHandler<TArg> = Future<void> Function(
+    Request, TArg, void Function(void));
+typedef _VoidNoArgRequestHandler<TArg> = Future<void> Function(
+    Request, TArg, void Function());
 
 /// A base class for debug adapters.
 ///
@@ -34,22 +40,28 @@
   /// Dart CLI, Dart tests, Flutter, Flutter tests).
   TLaunchArgs Function(Map<String, Object?>) get parseLaunchArgs;
 
-  FutureOr<void> attachRequest(
+  Future<void> attachRequest(
     Request request,
     TLaunchArgs args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   );
 
-  FutureOr<void> configurationDoneRequest(
+  Future<void> configurationDoneRequest(
     Request request,
     ConfigurationDoneArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   );
 
-  FutureOr<void> disconnectRequest(
+  Future<void> continueRequest(
+    Request request,
+    ContinueArguments args,
+    void Function(ContinueResponseBody) sendResponse,
+  );
+
+  Future<void> disconnectRequest(
     Request request,
     DisconnectArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   );
 
   /// Calls [handler] for an incoming request, using [fromJson] to parse its
@@ -63,9 +75,9 @@
   /// require a body.
   ///
   /// If [handler] throws, its exception will be sent as an error response.
-  FutureOr<void> handle<TArg, TResp>(
+  Future<void> handle<TArg, TResp>(
     Request request,
-    FutureOr<void> Function(Request, TArg, void Function(TResp)) handler,
+    _RequestHandler<TArg, TResp> handler,
     TArg Function(Map<String, Object?>) fromJson,
   ) async {
     final args = request.arguments != null
@@ -109,14 +121,23 @@
     }
   }
 
-  FutureOr<void> initializeRequest(
+  Future<void> initializeRequest(
     Request request,
     InitializeRequestArguments args,
     void Function(Capabilities) sendResponse,
   );
 
-  FutureOr<void> launchRequest(
-      Request request, TLaunchArgs args, void Function(void) sendResponse);
+  Future<void> launchRequest(
+    Request request,
+    TLaunchArgs args,
+    void Function() sendResponse,
+  );
+
+  Future<void> nextRequest(
+    Request request,
+    NextArguments args,
+    void Function() sendResponse,
+  );
 
   /// Sends an event, lookup up the event type based on the runtimeType of
   /// [body].
@@ -140,10 +161,33 @@
     _channel.sendRequest(request);
   }
 
-  FutureOr<void> terminateRequest(
+  Future<void> setBreakpointsRequest(
+      Request request,
+      SetBreakpointsArguments args,
+      void Function(SetBreakpointsResponseBody) sendResponse);
+
+  Future<void> stackTraceRequest(
+    Request request,
+    StackTraceArguments args,
+    void Function(StackTraceResponseBody) sendResponse,
+  );
+
+  Future<void> stepInRequest(
+    Request request,
+    StepInArguments args,
+    void Function() sendResponse,
+  );
+
+  Future<void> stepOutRequest(
+    Request request,
+    StepOutArguments args,
+    void Function() sendResponse,
+  );
+
+  Future<void> terminateRequest(
     Request request,
     TerminateArguments? args,
-    void Function(void) sendResponse,
+    void Function() sendResponse,
   );
 
   /// Wraps a fromJson handler for requests that allow null arguments.
@@ -169,27 +213,44 @@
     if (request.command == 'initialize') {
       handle(request, initializeRequest, InitializeRequestArguments.fromJson);
     } else if (request.command == 'launch') {
-      handle(request, launchRequest, parseLaunchArgs);
+      handle(request, _withVoidResponse(launchRequest), parseLaunchArgs);
     } else if (request.command == 'attach') {
-      handle(request, attachRequest, parseLaunchArgs);
+      handle(request, _withVoidResponse(attachRequest), parseLaunchArgs);
     } else if (request.command == 'terminate') {
       handle(
         request,
-        terminateRequest,
+        _withVoidResponse(terminateRequest),
         _allowNullArg(TerminateArguments.fromJson),
       );
     } else if (request.command == 'disconnect') {
       handle(
         request,
-        disconnectRequest,
+        _withVoidResponse(disconnectRequest),
         _allowNullArg(DisconnectArguments.fromJson),
       );
     } else if (request.command == 'configurationDone') {
       handle(
         request,
-        configurationDoneRequest,
+        _withVoidResponse(configurationDoneRequest),
         _allowNullArg(ConfigurationDoneArguments.fromJson),
       );
+    } else if (request.command == 'setBreakpoints') {
+      handle(request, setBreakpointsRequest, SetBreakpointsArguments.fromJson);
+    } else if (request.command == 'continue') {
+      handle(request, continueRequest, ContinueArguments.fromJson);
+    } else if (request.command == 'next') {
+      handle(request, _withVoidResponse(nextRequest), NextArguments.fromJson);
+    } else if (request.command == 'stepIn') {
+      handle(
+        request,
+        _withVoidResponse(stepInRequest),
+        StepInArguments.fromJson,
+      );
+    } else if (request.command == 'stepOut') {
+      handle(request, _withVoidResponse(stepOutRequest),
+          StepOutArguments.fromJson);
+    } else if (request.command == 'stackTrace') {
+      handle(request, stackTraceRequest, StackTraceArguments.fromJson);
     } else {
       final response = Response(
         success: false,
@@ -206,4 +267,20 @@
     // TODO(dantup): Implement this when the server sends requests to the client
     // (for example runInTerminalRequest).
   }
+
+  /// Helper that converts a handler with no response value to one that has
+  /// passes an unused arg so that `Function()` can be passed to a function
+  /// accepting `Function<T>(T x)` where `T` happens to be `void`.
+  ///
+  /// This allows handlers to simple call sendResponse() where they have no
+  /// return value but need to send a valid response.
+  _VoidArgRequestHandler<TArg> _withVoidResponse<TArg>(
+    _VoidNoArgRequestHandler<TArg> handler,
+  ) {
+    return (request, arg, sendResponse) => handler(
+          request,
+          arg,
+          () => sendResponse(null),
+        );
+  }
 }
diff --git a/pkg/dds/lib/src/dap/exceptions.dart b/pkg/dds/lib/src/dap/exceptions.dart
new file mode 100644
index 0000000..ec05ca5
--- /dev/null
+++ b/pkg/dds/lib/src/dap/exceptions.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.
+
+/// Exception thrown by a debug adapter when a request is not valid, either
+/// because the inputs are not correct or the adapter is not in the correct
+/// state.
+class DebugAdapterException implements Exception {
+  final String message;
+
+  DebugAdapterException(this.message);
+
+  String toString() => 'DebugAdapterException: $message';
+}
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 3bbc226..b2273ed 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -7,6 +7,7 @@
 import 'package:vm_service/vm_service.dart' as vm;
 
 import 'adapters/dart.dart';
+import 'exceptions.dart';
 import 'protocol_generated.dart';
 
 /// Manages state of Isolates (called Threads by the DAP protocol).
@@ -22,6 +23,40 @@
   final Map<int, ThreadInfo> _threadsByThreadId = {};
   int _nextThreadNumber = 1;
 
+  /// Whether debugging is enabled.
+  ///
+  /// This must be set before any isolates are spawned and controls whether
+  /// breakpoints or exception pause modes are sent to the VM.
+  ///
+  /// This is used to support debug sessions that have VM Service connections
+  /// but were run with noDebug: true (for example we may need a VM Service
+  /// connection for a noDebug flutter app in order to support hot reload).
+  bool _debug = false;
+
+  /// Tracks breakpoints last provided by the client so they can be sent to new
+  /// isolates that appear after initial breakpoints were sent.
+  final Map<String, List<SourceBreakpoint>> _clientBreakpointsByUri = {};
+
+  /// Tracks breakpoints created in the VM so they can be removed when the
+  /// editor sends new breakpoints (currently the editor just sends a new list
+  /// and not requests to add/remove).
+  final Map<String, Map<String, List<vm.Breakpoint>>>
+      _vmBreakpointsByIsolateIdAndUri = {};
+
+  /// An incrementing number used as the reference for [_storedData].
+  var _nextStoredDataId = 1;
+
+  /// A store of data indexed by a number that is used for round tripping
+  /// references to the client (which only accepts ints).
+  ///
+  /// For example, when we send a stack frame back to the client we provide only
+  /// a "sourceReference" integer and the client may later ask us for the source
+  /// using that number (via sourceRequest).
+  ///
+  /// Stored data is thread-scoped but the client will not provide the thread
+  /// when asking for data so it's all stored together here.
+  final _storedData = <int, _StoredData>{};
+
   IsolateManager(this._adapter);
 
   /// A list of all current active isolates.
@@ -37,8 +72,10 @@
     return res as T;
   }
 
+  ThreadInfo? getThread(int threadId) => _threadsByThreadId[threadId];
+
   /// Handles Isolate and Debug events
-  FutureOr<void> handleEvent(vm.Event event) async {
+  Future<void> handleEvent(vm.Event event) async {
     final isolateId = event.isolate?.id;
     if (isolateId == null) {
       return;
@@ -63,11 +100,11 @@
     await _isolateRegistrations[isolateId]?.future;
 
     if (eventKind == vm.EventKind.kIsolateExit) {
-      await _handleExit(event);
+      _handleExit(event);
     } else if (eventKind?.startsWith('Pause') ?? false) {
       await _handlePause(event);
     } else if (eventKind == vm.EventKind.kResume) {
-      await _handleResumed(event);
+      _handleResumed(event);
     }
   }
 
@@ -77,7 +114,7 @@
   /// New isolates will be configured with the correct pause-exception behaviour,
   /// libraries will be marked as debuggable if appropriate, and breakpoints
   /// sent.
-  FutureOr<void> registerIsolate(
+  Future<void> registerIsolate(
     vm.IsolateRef isolate,
     String eventKind,
   ) async {
@@ -90,7 +127,7 @@
       isolate.id!,
       () {
         // The first time we see an isolate, start tracking it.
-        final info = ThreadInfo(_nextThreadNumber++, isolate);
+        final info = ThreadInfo(this, _nextThreadNumber++, isolate);
         _threadsByThreadId[info.threadId] = info;
         // And notify the client about it.
         _adapter.sendEvent(
@@ -109,7 +146,7 @@
     }
   }
 
-  FutureOr<void> resumeIsolate(vm.IsolateRef isolateRef,
+  Future<void> resumeIsolate(vm.IsolateRef isolateRef,
       [String? resumeType]) async {
     final isolateId = isolateRef.id;
     if (isolateId == null) {
@@ -135,7 +172,7 @@
   Future<void> resumeThread(int threadId, [String? resumeType]) async {
     final thread = _threadsByThreadId[threadId];
     if (thread == null) {
-      throw 'Thread $threadId was not found';
+      throw DebugAdapterException('Thread $threadId was not found');
     }
 
     // Check this thread hasn't already been resumed by another handler in the
@@ -159,16 +196,58 @@
     }
   }
 
+  /// Records breakpoints for [uri].
+  ///
+  /// [breakpoints] represents the new set and entirely replaces anything given
+  /// before.
+  Future<void> setBreakpoints(
+    String uri,
+    List<SourceBreakpoint> breakpoints,
+  ) async {
+    // Track the breakpoints to get sent to any new isolates that start.
+    _clientBreakpointsByUri[uri] = breakpoints;
+
+    // Send the breakpoints to all existing threads.
+    await Future.wait(_threadsByThreadId.values
+        .map((isolate) => _sendBreakpoints(isolate.isolate, uri: uri)));
+  }
+
+  /// Sets whether debugging is enabled for this session.
+  ///
+  /// If not, requests to send breakpoints or exception pause mode will be
+  /// dropped. Other functionality (handling pause events, resuming, etc.) will
+  /// all still function.
+  ///
+  /// This is used to support debug sessions that have VM Service connections
+  /// but were run with noDebug: true (for example we may need a VM Service
+  /// connection for a noDebug flutter app in order to support hot reload).
+  void setDebugEnabled(bool debug) {
+    _debug = debug;
+  }
+
+  /// Stores some basic data indexed by an integer for use in "reference" fields
+  /// that are round-tripped to the client.
+  int storeData(ThreadInfo thread, Object data) {
+    final id = _nextStoredDataId++;
+    _storedData[id] = _StoredData(thread, data);
+    return id;
+  }
+
   ThreadInfo? threadForIsolate(vm.IsolateRef? isolate) =>
       isolate?.id != null ? _threadsByIsolateId[isolate!.id!] : null;
 
   /// Configures a new isolate, setting it's exception-pause mode, which
   /// libraries are debuggable, and sending all breakpoints.
-  FutureOr<void> _configureIsolate(vm.IsolateRef isolate) async {
-    // TODO(dantup): set library debuggable, exception pause mode, breakpoints
+  Future<void> _configureIsolate(vm.IsolateRef isolate) async {
+    await Future.wait([
+      _sendLibraryDebuggables(isolate),
+      // TODO(dantup): Implement this...
+      // _sendExceptionPauseMode(isolate),
+      _sendBreakpoints(isolate),
+    ], eagerError: true);
   }
 
-  FutureOr<void> _handleExit(vm.Event event) {
+  void _handleExit(vm.Event event) {
     final isolate = event.isolate!;
     final isolateId = isolate.id!;
     final thread = _threadsByIsolateId[isolateId];
@@ -195,7 +274,7 @@
   ///
   /// For all other pause types, the isolate will remain paused and a
   /// corresponding "Stopped" event sent to the editor.
-  FutureOr<void> _handlePause(vm.Event event) async {
+  Future<void> _handlePause(vm.Event event) async {
     final eventKind = event.kind;
     final isolate = event.isolate!;
     final thread = _threadsByIsolateId[isolate.id!];
@@ -211,7 +290,7 @@
     // For PausePostRequest we need to re-send all breakpoints; this happens
     // after a hot restart.
     if (eventKind == vm.EventKind.kPausePostRequest) {
-      _configureIsolate(isolate);
+      await _configureIsolate(isolate);
       await resumeThread(thread.threadId);
     } else if (eventKind == vm.EventKind.kPauseStart) {
       await resumeThread(thread.threadId);
@@ -238,7 +317,7 @@
   }
 
   /// Handles a resume event from the VM, updating our local state.
-  FutureOr<void> _handleResumed(vm.Event event) {
+  void _handleResumed(vm.Event event) {
     final isolate = event.isolate!;
     final thread = _threadsByIsolateId[isolate.id!];
     if (thread != null) {
@@ -247,10 +326,103 @@
       thread.exceptionReference = null;
     }
   }
+
+  bool _isExternalPackageLibrary(vm.LibraryRef library) =>
+      // TODO(dantup): This needs to check if it's _external_, eg.
+      //
+      // - is from the flutter SDK (flutter, flutter_test, ...)
+      // - is from pub/pubcache
+      //
+      // This is intended to match the users idea of "my code". For example
+      // they may wish to debug the current app being run, as well as any other
+      // projects that are references with path: dependencies (which are likely
+      // their own supporting projects).
+      false /*library.uri?.startsWith('package:') ?? false*/;
+
+  bool _isSdkLibrary(vm.LibraryRef library) =>
+      library.uri?.startsWith('dart:') ?? false;
+
+  /// Checks whether a library should be considered debuggable.
+  ///
+  /// This usesthe settings from the launch arguments (debugSdkLibraries
+  /// and debugExternalPackageLibraries) against the type of library given.
+  bool _libaryIsDebuggable(vm.LibraryRef library) {
+    if (_isSdkLibrary(library)) {
+      return _adapter.args.debugSdkLibraries ?? false;
+    } else if (_isExternalPackageLibrary(library)) {
+      return _adapter.args.debugExternalPackageLibraries ?? false;
+    } else {
+      return true;
+    }
+  }
+
+  /// Sets breakpoints for an individual isolate.
+  ///
+  /// If [uri] is provided, only breakpoints for that URI will be sent (used
+  /// when breakpoints are modified for a single file in the editor). Otherwise
+  /// breakpoints for all previously set URIs will be sent (used for
+  /// newly-created isolates).
+  Future<void> _sendBreakpoints(vm.IsolateRef isolate, {String? uri}) async {
+    final service = _adapter.vmService;
+    if (!_debug || service == null) {
+      return;
+    }
+
+    final isolateId = isolate.id!;
+
+    // If we were passed a single URI, we should send breakpoints only for that
+    // (this means the request came from the client), otherwise we should send
+    // all of them (because this is a new/restarting isolate).
+    final uris = uri != null ? [uri] : _clientBreakpointsByUri.keys;
+
+    for (final uri in uris) {
+      // Clear existing breakpoints.
+      final existingBreakpointsForIsolate =
+          _vmBreakpointsByIsolateIdAndUri.putIfAbsent(isolateId, () => {});
+      final existingBreakpointsForIsolateAndUri =
+          existingBreakpointsForIsolate.putIfAbsent(uri, () => []);
+      await Future.forEach<vm.Breakpoint>(existingBreakpointsForIsolateAndUri,
+          (bp) => service.removeBreakpoint(isolateId, bp.id!));
+
+      // Set new breakpoints.
+      final newBreakpoints = _clientBreakpointsByUri[uri] ?? const [];
+      await Future.forEach<SourceBreakpoint>(newBreakpoints, (bp) async {
+        final vmBp = await service.addBreakpointWithScriptUri(
+            isolateId, uri, bp.line,
+            column: bp.column);
+        existingBreakpointsForIsolateAndUri.add(vmBp);
+      });
+    }
+  }
+
+  /// Calls setLibraryDebuggable for all libraries in the given isolate based
+  /// on the debug settings.
+  Future<void> _sendLibraryDebuggables(vm.IsolateRef isolateRef) async {
+    final service = _adapter.vmService;
+    if (!_debug || service == null) {
+      return;
+    }
+
+    final isolateId = isolateRef.id;
+    if (isolateId == null) {
+      return;
+    }
+
+    final isolate = await service.getIsolate(isolateId);
+    final libraries = isolate.libraries;
+    if (libraries == null) {
+      return;
+    }
+    await Future.wait(libraries.map((library) async {
+      final isDebuggable = _libaryIsDebuggable(library);
+      await service.setLibraryDebuggable(isolateId, library.id!, isDebuggable);
+    }));
+  }
 }
 
 /// Holds state for a single Isolate/Thread.
 class ThreadInfo {
+  final IsolateManager _manager;
   final vm.IsolateRef isolate;
   final int threadId;
   var runnable = false;
@@ -261,9 +433,37 @@
   // The most recent pauseEvent for this isolate.
   vm.Event? pauseEvent;
 
+  // A cache of requests (Futures) to fetch scripts, so that multiple requests
+  // that require scripts (for example looking up locations for stack frames from
+  // tokenPos) can share the same response.
+  final _scripts = <String, Future<vm.Script>>{};
+
   /// Whether this isolate has an in-flight resume request that has not yet
   /// been responded to.
   var hasPendingResume = false;
 
-  ThreadInfo(this.threadId, this.isolate);
+  ThreadInfo(this._manager, this.threadId, this.isolate);
+
+  Future<T> getObject<T extends vm.Response>(vm.ObjRef ref) =>
+      _manager.getObject<T>(isolate, ref);
+
+  /// Fetches a script for a given isolate.
+  ///
+  /// Results from this method are cached so that if there are multiple
+  /// concurrent calls (such as when converting multiple stack frames) they will
+  /// all use the same script.
+  Future<vm.Script> getScript(vm.ScriptRef script) {
+    return _scripts.putIfAbsent(script.id!, () => getObject<vm.Script>(script));
+  }
+
+  /// Stores some basic data indexed by an integer for use in "reference" fields
+  /// that are round-tripped to the client.
+  int storeData(Object data) => _manager.storeData(this, data);
+}
+
+class _StoredData {
+  final ThreadInfo thread;
+  final Object data;
+
+  _StoredData(this.thread, this.data);
 }
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
new file mode 100644
index 0000000..1fa00fe
--- /dev/null
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2021, 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:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:vm_service/vm_service.dart' as vm;
+
+import 'adapters/dart.dart';
+import 'isolate_manager.dart';
+import 'protocol_generated.dart' as dap;
+
+/// A helper that handlers converting to/from DAP and VM Service types and to
+/// user-friendly display strings.
+///
+/// This class may call back to the VM Service to fetch additional information
+/// when converting classes - for example when converting a stack frame it may
+/// fetch scripts from the VM Service in order to map token positions back to
+/// line/columns as required by DAP.
+class ProtocolConverter {
+  /// The parent debug adapter, used to access arguments and the VM Service for
+  /// the debug session.
+  final DartDebugAdapter _adapter;
+
+  ProtocolConverter(this._adapter);
+
+  /// Converts an absolute path to one relative to the cwd used to launch the
+  /// application.
+  ///
+  /// If [sourcePath] is outside of the cwd used for launching the application
+  /// then the full absolute path will be returned.
+  String convertToRelativePath(String sourcePath) {
+    final cwd = _adapter.args.cwd;
+    if (cwd == null) {
+      return sourcePath;
+    }
+    final rel = path.relative(sourcePath, from: cwd);
+    return !rel.startsWith('..') ? rel : sourcePath;
+  }
+
+  /// Converts a VM Service stack frame to a DAP stack frame.
+  Future<dap.StackFrame> convertVmToDapStackFrame(
+    ThreadInfo thread,
+    vm.Frame frame, {
+    required bool isTopFrame,
+    int? firstAsyncMarkerIndex,
+  }) async {
+    final frameId = thread.storeData(frame);
+
+    if (frame.kind == vm.FrameKind.kAsyncSuspensionMarker) {
+      return dap.StackFrame(
+        id: frameId,
+        name: '<asynchronous gap>',
+        presentationHint: 'label',
+        line: 0,
+        column: 0,
+      );
+    }
+
+    // The VM may supply frames with a prefix that we don't want to include in
+    // the frame for the user.
+    const unoptimizedPrefix = '[Unoptimized] ';
+    final codeName = frame.code?.name;
+    final frameName = codeName != null
+        ? (codeName.startsWith(unoptimizedPrefix)
+            ? codeName.substring(unoptimizedPrefix.length)
+            : codeName)
+        : '<unknown>';
+
+    // If there's no location, this isn't source a user can debug so use a
+    // subtle hint (which the editor may use to render the frame faded).
+    final location = frame.location;
+    if (location == null) {
+      return dap.StackFrame(
+        id: frameId,
+        name: frameName,
+        presentationHint: 'subtle',
+        line: 0,
+        column: 0,
+      );
+    }
+
+    final scriptRef = location.script;
+    final tokenPos = location.tokenPos;
+    final uri = scriptRef?.uri;
+    final sourcePath = uri != null ? await convertVmUriToSourcePath(uri) : null;
+    var canShowSource = sourcePath != null && File(sourcePath).existsSync();
+
+    // Download the source if from a "dart:" uri.
+    int? sourceReference;
+    if (uri != null &&
+        (uri.startsWith('dart:') || uri.startsWith('org-dartlang-app:')) &&
+        scriptRef != null) {
+      sourceReference = thread.storeData(scriptRef);
+      canShowSource = true;
+    }
+
+    var line = 0, col = 0;
+    if (scriptRef != null && tokenPos != null) {
+      try {
+        final script = await thread.getScript(scriptRef);
+        line = script.getLineNumberFromTokenPos(tokenPos) ?? 0;
+        col = script.getColumnNumberFromTokenPos(tokenPos) ?? 0;
+      } catch (e) {
+        _adapter.logger?.call('Failed to map frame location to line/col: $e');
+      }
+    }
+
+    final source = canShowSource
+        ? dap.Source(
+            name: sourcePath != null ? convertToRelativePath(sourcePath) : uri,
+            path: sourcePath,
+            sourceReference: sourceReference,
+            origin: null,
+            adapterData: location.script)
+        : null;
+
+    // The VM only allows us to restart from frames that are not the top frame,
+    // but since we're also showing asyncCausalFrames any indexes past the first
+    // async boundary will not line up so we cap it there.
+    final canRestart = !isTopFrame &&
+        (firstAsyncMarkerIndex == null || frame.index! < firstAsyncMarkerIndex);
+
+    return dap.StackFrame(
+      id: frameId,
+      name: frameName,
+      source: source,
+      line: line,
+      column: col,
+      canRestart: canRestart,
+    );
+  }
+
+  /// Converts the source path from the VM to a file path.
+  ///
+  /// This is required so that when the user stops (or navigates via a stack
+  /// frame) we open the same file on their local disk. If we downloaded the
+  /// source from the VM, they would end up seeing two copies of files (and they
+  /// would each have their own breakpoints) which can be confusing.
+  Future<String?> convertVmUriToSourcePath(String uri) async {
+    if (uri.startsWith('file://')) {
+      return Uri.parse(uri).toFilePath();
+    } else if (uri.startsWith('package:')) {
+      // TODO(dantup): Handle mapping package: uris ?
+      return null;
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/pkg/dds/lib/src/dap/protocol_stream.dart b/pkg/dds/lib/src/dap/protocol_stream.dart
index a362b93..a3694dc 100644
--- a/pkg/dds/lib/src/dap/protocol_stream.dart
+++ b/pkg/dds/lib/src/dap/protocol_stream.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'exceptions.dart';
 import 'logging.dart';
 import 'protocol_generated.dart';
 import 'protocol_stream_transformers.dart';
@@ -113,7 +114,7 @@
 
   void _sendParseError(String data) {
     // TODO(dantup): Review LSP implementation of this when consolidating classes.
-    throw 'Message does not confirm to DAP spec: $data';
+    throw DebugAdapterException('Message does not confirm to DAP spec: $data');
   }
 
   /// Send [bytes] to [_output].
diff --git a/pkg/dds/lib/src/dap/server.dart b/pkg/dds/lib/src/dap/server.dart
index f110e4e..14de592 100644
--- a/pkg/dds/lib/src/dap/server.dart
+++ b/pkg/dds/lib/src/dap/server.dart
@@ -29,15 +29,16 @@
   String get host => _socket.address.host;
   int get port => _socket.port;
 
-  FutureOr<void> stop() async {
+  Future<void> stop() async {
     _channels.forEach((client) => client.close());
     await _socket.close();
   }
 
   void _acceptConnection(Socket client) {
-    _logger?.call('Accepted connection from ${client.remoteAddress}');
+    final address = client.remoteAddress;
+    _logger?.call('Accepted connection from $address');
     client.done.then((_) {
-      _logger?.call('Connection from ${client.remoteAddress} closed');
+      _logger?.call('Connection from $address closed');
     });
     _createAdapter(client.transform(Uint8ListTransformer()), client, _logger);
   }
@@ -48,7 +49,7 @@
     //   ultimately need to support having a factory passed in to support
     //   tests and/or being used in flutter_tools.
     final channel = ByteStreamServerChannel(_input, _output, logger);
-    final adapter = DartCliDebugAdapter(channel);
+    final adapter = DartCliDebugAdapter(channel, logger);
     _channels.add(channel);
     _adapters.add(adapter);
     unawaited(channel.closed.then((_) {
diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
new file mode 100644
index 0000000..6e1744c
--- /dev/null
+++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
@@ -0,0 +1,218 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'test_client.dart';
+import 'test_support.dart';
+
+main() {
+  testDap((dap) async {
+    group('debug mode breakpoints', () {
+      test('stops at a line breakpoint', () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  print('Hello!'); // BREAKPOINT
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        await client.hitBreakpoint(testFile, breakpointLine);
+      });
+
+      test('stops at a line breakpoint and can be resumed', () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  print('Hello!'); // BREAKPOINT
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        // Hit the initial breakpoint.
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+
+        // Resume and expect termination (as the script will get to the end).
+        await Future.wait([
+          client.event('terminated'),
+          client.continue_(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test('stops at a line breakpoint and can step over (next)', () async {
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  print('Hello!'); // BREAKPOINT
+  print('Hello!'); // STEP
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+        final stepLine = lineWith(testFile, '// STEP');
+
+        // Hit the initial breakpoint.
+        final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
+
+        // Step and expect stopping on the next line with a 'step' stop type.
+        await Future.wait([
+          dap.client.expectStop('step', file: testFile, line: stepLine),
+          dap.client.next(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test(
+          'stops at a line breakpoint and can step over (next) an async boundary',
+          () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+Future<void> main(List<String> args) async {
+  await asyncPrint('Hello!'); // BREAKPOINT
+  await asyncPrint('Hello!'); // STEP
+}
+
+Future<void> asyncPrint(String message) async {
+  await Future.delayed(const Duration(milliseconds: 1));
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+        final stepLine = lineWith(testFile, '// STEP');
+
+        // Hit the initial breakpoint.
+        final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
+
+        // The first step will move from `asyncPrint` to the `await`.
+        await Future.wait([
+          client.expectStop('step', file: testFile, line: breakpointLine),
+          client.next(stop.threadId!),
+        ], eagerError: true);
+
+        // The next step should go over the async boundary and to stepLine (if
+        // we did not correctly send kOverAsyncSuspension we would end up in
+        // the asyncPrint method).
+        await Future.wait([
+          client.expectStop('step', file: testFile, line: stepLine),
+          client.next(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test('stops at a line breakpoint and can step in', () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  log('Hello!'); // BREAKPOINT
+}
+
+void log(String message) { // STEP
+  print(message);
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+        final stepLine = lineWith(testFile, '// STEP');
+
+        // Hit the initial breakpoint.
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+
+        // Step and expect stopping in the inner function with a 'step' stop type.
+        await Future.wait([
+          client.expectStop('step', file: testFile, line: stepLine),
+          client.stepIn(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test('stops at a line breakpoint and can step out', () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  log('Hello!');
+  log('Hello!'); // STEP
+}
+
+void log(String message) {
+  print(message); // BREAKPOINT
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+        final stepLine = lineWith(testFile, '// STEP');
+
+        // Hit the initial breakpoint.
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+
+        // Step and expect stopping in the inner function with a 'step' stop type.
+        await Future.wait([
+          client.expectStop('step', file: testFile, line: stepLine),
+          client.stepOut(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test('does not step into SDK code with debugSdkLibraries=false',
+          () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  print('Hello!'); // BREAKPOINT
+  print('Hello!'); // STEP
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+        final stepLine = lineWith(testFile, '// STEP');
+
+        // Hit the initial breakpoint.
+        final stop = await client.hitBreakpoint(testFile, breakpointLine);
+
+        // Step in and expect stopping on the next line (don't go into print).
+        await Future.wait([
+          client.expectStop('step', file: testFile, line: stepLine),
+          client.stepIn(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test('steps into SDK code with debugSdkLibraries=true', () async {
+        final client = dap.client;
+        final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+  print('Hello!'); // BREAKPOINT
+  print('Hello!');
+}
+    ''');
+        final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+        // Hit the initial breakpoint.
+        final stop = await dap.client.hitBreakpoint(
+          testFile,
+          breakpointLine,
+          launch: () => client.launch(
+            testFile.path,
+            debugSdkLibraries: true,
+          ),
+        );
+
+        // Step in and expect to go into print.
+        await Future.wait([
+          client.expectStop('step', sourceName: 'dart:core/print.dart'),
+          client.stepIn(stop.threadId!),
+        ], eagerError: true);
+      });
+
+      test(
+          'does not step into external package code with debugExternalPackageLibraries=false',
+          () {
+        // TODO(dantup): Support for debugExternalPackageLibraries
+      }, skip: true);
+
+      test(
+          'steps into external package code with debugExternalPackageLibraries=true',
+          () {
+        // TODO(dantup): Support for debugExternalPackageLibraries
+      }, skip: true);
+
+      test('allows changing debug settings during session', () {
+        // TODO(dantup): !
+        // Dart-Code's DAP has a custom method that allows an editor to change
+        // the debug settings (debugSdkLibraries/debugExternalPackageLibraries)
+        // during a debug session.
+      }, skip: true);
+      // These tests can be slow due to starting up the external server process.
+    }, timeout: Timeout.none);
+  });
+}
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index e5fecdc..434bef6 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -10,11 +10,15 @@
 import 'package:dds/src/dap/protocol_generated.dart';
 import 'package:dds/src/dap/protocol_stream.dart';
 import 'package:dds/src/dap/protocol_stream_transformers.dart';
+import 'package:test/test.dart';
 
 import 'test_server.dart';
 
 /// A helper class to simplify acting as a client for interacting with the
 /// [DapTestServer] in tests.
+///
+/// Methods on this class should map directly to protocol methods. Additional
+/// helpers are available in [DapTestClientExtension].
 class DapTestClient {
   final Socket _socket;
   final ByteStreamServerChannel _channel;
@@ -66,6 +70,13 @@
     return outputEventsFuture;
   }
 
+  /// Sends a continue request for the given thread.
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> continue_(int threadId) =>
+      sendRequest(ContinueArguments(threadId: threadId));
+
   Future<Response> disconnect() => sendRequest(DisconnectArguments());
 
   /// Returns a Future that completes with the next [event] event.
@@ -103,6 +114,7 @@
     String? cwd,
     bool? noDebug,
     bool? debugSdkLibraries,
+    bool? debugExternalPackageLibraries,
     bool? evaluateGettersInDebugViews,
     bool? evaluateToStringInDebugViews,
   }) {
@@ -113,6 +125,7 @@
         cwd: cwd,
         args: args,
         debugSdkLibraries: debugSdkLibraries,
+        debugExternalPackageLibraries: debugExternalPackageLibraries,
         evaluateGettersInDebugViews: evaluateGettersInDebugViews,
         evaluateToStringInDebugViews: evaluateToStringInDebugViews,
         // When running out of process, VM Service traffic won't be available
@@ -126,6 +139,13 @@
     );
   }
 
+  /// Sends a next (step over) request for the given thread.
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> next(int threadId) =>
+      sendRequest(NextArguments(threadId: threadId));
+
   /// Sends an arbitrary request to the server.
   ///
   /// Returns a Future that completes when the server returns a corresponding
@@ -142,7 +162,34 @@
     return _logIfSlow('Request "$command"', completer.future);
   }
 
-  FutureOr<void> stop() async {
+  /// Sends a stackTrace request to the server to request the call stack for a
+  /// given thread.
+  ///
+  /// If [startFrame] and/or [numFrames] are supplied, only a slice of the
+  /// frames will be returned.
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> stackTrace(int threadId,
+          {int? startFrame, int? numFrames}) =>
+      sendRequest(StackTraceArguments(
+          threadId: threadId, startFrame: startFrame, levels: numFrames));
+
+  /// Sends a stepIn request for the given thread.
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> stepIn(int threadId) =>
+      sendRequest(StepInArguments(threadId: threadId));
+
+  /// Sends a stepOut request for the given thread.
+  ///
+  /// Returns a Future that completes when the server returns a corresponding
+  /// response.
+  Future<Response> stepOut(int threadId) =>
+      sendRequest(StepOutArguments(threadId: threadId));
+
+  Future<void> stop() async {
     _channel.close();
     await _socket.close();
     await _subscription.cancel();
@@ -194,7 +241,7 @@
 
   /// Creates a [DapTestClient] that connects the server listening on
   /// [host]:[port].
-  static FutureOr<DapTestClient> connect(
+  static Future<DapTestClient> connect(
     int port, {
     String host = 'localhost',
     bool captureVmServiceTraffic = false,
@@ -216,3 +263,71 @@
 
   _OutgoingRequest(this.completer, this.name, this.allowFailure);
 }
+
+/// Additional helper method for tests to simplify interaction with [DapTestClient].
+///
+/// Unlike the methods on [DapTestClient] these methods might not map directly
+/// onto protocol methods. They may call multiple protocol methods and/or
+/// simplify assertion specific conditions/results.
+extension DapTestClientExtension on DapTestClient {
+  /// Sets a breakpoint at [line] in [file] and expects to hit it after running
+  /// the script.
+  ///
+  /// Launch options can be customised by passing a custom [launch] function that
+  /// will be used instead of calling `launch(file.path)`.
+  Future<StoppedEventBody> hitBreakpoint(File file, int line,
+      {Future<Response> Function()? launch}) async {
+    final stop = expectStop('breakpoint', file: file, line: line);
+
+    await Future.wait([
+      initialize(),
+      sendRequest(
+        SetBreakpointsArguments(
+            source: Source(path: file.path),
+            breakpoints: [SourceBreakpoint(line: line)]),
+      ),
+      launch?.call() ?? this.launch(file.path),
+    ], eagerError: true);
+
+    return stop;
+  }
+
+  /// Expects a 'stopped' event for [reason].
+  ///
+  /// If [file] or [line] are provided, they will be checked against the stop
+  /// location for the top stack frame.
+  Future<StoppedEventBody> expectStop(String reason,
+      {File? file, int? line, String? sourceName}) async {
+    final e = await event('stopped');
+    final stop = StoppedEventBody.fromJson(e.body as Map<String, Object?>);
+    expect(stop.reason, equals(reason));
+
+    final result =
+        await getValidStack(stop.threadId!, startFrame: 0, numFrames: 1);
+    expect(result.stackFrames, hasLength(1));
+    final frame = result.stackFrames[0];
+
+    if (file != null) {
+      expect(frame.source!.path, equals(file.path));
+    }
+    if (sourceName != null) {
+      expect(frame.source!.name, equals(sourceName));
+    }
+    if (line != null) {
+      expect(frame.line, equals(line));
+    }
+
+    return stop;
+  }
+
+  /// Fetches a stack trace and asserts it was a valid response.
+  Future<StackTraceResponseBody> getValidStack(int threadId,
+      {required int startFrame, required int numFrames}) async {
+    final response = await stackTrace(threadId,
+        startFrame: startFrame, numFrames: numFrames);
+    expect(response.success, isTrue);
+    expect(response.command, equals('stackTrace'));
+    return StackTraceResponseBody.fromJson(
+        response.body as Map<String, Object?>);
+  }
+}
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index 003e83c..2bfb0a0 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -7,6 +7,7 @@
 import 'dart:io';
 import 'dart:isolate';
 
+import 'package:dds/src/dap/logging.dart';
 import 'package:dds/src/dap/server.dart';
 import 'package:path/path.dart' as path;
 import 'package:pedantic/pedantic.dart';
@@ -14,7 +15,7 @@
 abstract class DapTestServer {
   String get host;
   int get port;
-  FutureOr<void> stop();
+  Future<void> stop();
   List<String> get errorLogs;
 }
 
@@ -33,12 +34,12 @@
   List<String> get errorLogs => const []; // In-proc errors just throw in-line.
 
   @override
-  FutureOr<void> stop() async {
+  Future<void> stop() async {
     await _server.stop();
   }
 
-  static Future<InProcessDapTestServer> create() async {
-    final DapServer server = await DapServer.create();
+  static Future<InProcessDapTestServer> create({Logger? logger}) async {
+    final DapServer server = await DapServer.create(logger: logger);
     return InProcessDapTestServer._(server);
   }
 }
@@ -81,7 +82,7 @@
   }
 
   @override
-  FutureOr<void> stop() async {
+  Future<void> stop() async {
     _isShuttingDown = true;
     await _process.kill();
     await _process.exitCode;
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index 71190fe..124efc3 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -26,10 +26,14 @@
   expect(actual.replaceAll('\r\n', '\n'), equals(expected.join('\n')));
 }
 
+/// Returns the 1-base line in [file] that contains [searchText].
+int lineWith(File file, String searchText) =>
+    file.readAsLinesSync().indexWhere((line) => line.contains(searchText)) + 1;
+
 /// A helper function to wrap all tests in a library with setup/teardown functions
 /// to start a shared server for all tests in the library and an individual
 /// client for each test.
-testDap(FutureOr<void> Function(DapTestSession session) tests) {
+testDap(Future<void> Function(DapTestSession session) tests) {
   final session = DapTestSession();
 
   setUpAll(session.setUpAll);
@@ -59,17 +63,17 @@
     return testFile;
   }
 
-  FutureOr<void> setUp() async {
+  Future<void> setUp() async {
     client = await _startClient(server);
   }
 
-  FutureOr<void> setUpAll() async {
+  Future<void> setUpAll() async {
     server = await _startServer();
   }
 
-  FutureOr<void> tearDown() => client.stop();
+  Future<void> tearDown() => client.stop();
 
-  FutureOr<void> tearDownAll() async {
+  Future<void> tearDownAll() async {
     await server.stop();
 
     // Clean up any temp folders created during the test runs.
@@ -77,7 +81,7 @@
   }
 
   /// Creates and connects a new [DapTestClient] to [server].
-  FutureOr<DapTestClient> _startClient(DapTestServer server) async {
+  Future<DapTestClient> _startClient(DapTestServer server) async {
     // Since we don't get a signal from the DAP server when it's ready and we
     // just started it, add a short retry to connections.
     // Since the bots can be quite slow, it may take 6-7 seconds for the server
@@ -107,7 +111,7 @@
   }
 
   /// Starts a DAP server that can be shared across tests.
-  FutureOr<DapTestServer> _startServer() async {
+  Future<DapTestServer> _startServer() async {
     return useInProcessDap
         ? await InProcessDapTestServer.create()
         : await OutOfProcessDapTestServer.create();
diff --git a/pkg/pkg.status b/pkg/pkg.status
index fa9952ce5..79e9538 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -10,6 +10,7 @@
 */*/packages/*/*: Skip
 */packages/*/*: Skip
 analyzer/test/src/task/strong/checker_test: Slow, Pass
+analyzer/test/verify_diagnostics_test.dart: Slow, Pass
 analyzer_plugin/test/plugin/folding_mixin_test: Slow, Pass
 compiler/test/analyses/analyze_test: Slow, Pass
 compiler/test/analyses/api_dynamic_test: Slow, Pass
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index 00d1c06..cadd093 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -154,12 +154,15 @@
   }
 
   bool _keepAnnotation(Expression annotation) {
-    final constant = (annotation as ConstantExpression).constant;
-    if (constant is InstanceConstant) {
-      final cls = constant.classNode;
-      return (cls == externalNameClass) ||
-          (cls == pragmaClass) ||
-          (protobufHandler != null && protobufHandler.usesAnnotationClass(cls));
+    if (annotation is ConstantExpression) {
+      final constant = annotation.constant;
+      if (constant is InstanceConstant) {
+        final cls = constant.classNode;
+        return (cls == externalNameClass) ||
+            (cls == pragmaClass) ||
+            (protobufHandler != null &&
+                protobufHandler.usesAnnotationClass(cls));
+      }
     }
     return false;
   }
diff --git a/tools/VERSION b/tools/VERSION
index e65c72f..875ad99 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 202
+PRERELEASE 203
 PRERELEASE_PATCH 0
\ No newline at end of file