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