Version 2.17.0-30.0.dev
Merge commit 'b449fe4398f5188a54694bfcea8f2dc8f645e45e' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/data_driven.dart b/pkg/analysis_server/lib/src/services/correction/dart/data_driven.dart
index 029d157..f7c9e13 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/data_driven.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/data_driven.dart
@@ -33,15 +33,17 @@
importedUris.add(Uri.parse(uri));
}
}
- var matcher = ElementMatcher.forNode(node);
- if (matcher == null) {
- // The node doesn't represent an element that can be transformed.
+ var matchers = ElementMatcher.matchersForNode(node);
+ if (matchers.isEmpty) {
+ // The node doesn't represent any element that can be transformed.
return;
}
for (var set in _availableTransformSetsForLibrary(library)) {
- for (var transform
- in set.transformsFor(matcher, applyingBulkFixes: applyingBulkFixes)) {
- yield DataDrivenFix(transform);
+ for (var matcher in matchers) {
+ for (var transform in set.transformsFor(matcher,
+ applyingBulkFixes: applyingBulkFixes)) {
+ yield DataDrivenFix(transform);
+ }
}
}
}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
index 837025a..252ccf6 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
@@ -26,13 +26,14 @@
final List<ElementKind> validKinds;
/// Initialize a newly created matcher representing a reference to an element
- /// with the given [name] in a library that imports the [importedUris].
+ /// whose name matches the given [components] and element [kinds] in a library
+ /// that imports the [importedUris].
ElementMatcher(
{required this.importedUris,
required this.components,
- List<ElementKind>? kinds})
+ required List<ElementKind> kinds})
: assert(components.isNotEmpty),
- validKinds = kinds ?? const [];
+ validKinds = kinds;
/// Return `true` if this matcher matches the given [element].
bool matches(ElementDescriptor element) {
@@ -93,25 +94,68 @@
return false;
}
- /// Return an element matcher that will match the element that is, or should
- /// be, associated with the given [node], or `null` if there is no appropriate
- /// matcher for the node.
- static ElementMatcher? forNode(AstNode? node) {
+ /// Return a list of element matchers that will match the element that is, or
+ /// should be, associated with the given [node]. The list will be empty if
+ /// there are no appropriate matchers for the [node].
+ static List<ElementMatcher> matchersForNode(AstNode? node) {
if (node == null) {
- return null;
+ return const <ElementMatcher>[];
}
var importedUris = _importElementsForNode(node);
if (importedUris == null) {
+ return const <ElementMatcher>[];
+ }
+ var builder = _MatcherBuilder(importedUris);
+ builder.buildMatchersForNode(node);
+ return builder.matchers.toList();
+ }
+
+ /// Return the URIs of the imports in the library containing the [node], or
+ /// `null` if the imports can't be determined.
+ static List<Uri>? _importElementsForNode(AstNode node) {
+ var root = node.root;
+ if (root is! CompilationUnit) {
return null;
}
+ var importedUris = <Uri>[];
+ var library = root.declaredElement?.library;
+ if (library == null) {
+ return null;
+ }
+ for (var importElement in library.imports) {
+ // TODO(brianwilkerson) Filter based on combinators to help avoid making
+ // invalid suggestions.
+ var uri = importElement.importedLibrary?.source.uri;
+ if (uri != null) {
+ // The [uri] is `null` if the literal string is not a valid URI.
+ importedUris.add(uri);
+ }
+ }
+ return importedUris;
+ }
+}
+
+/// A helper class used to build a list of element matchers.
+class _MatcherBuilder {
+ final List<ElementMatcher> matchers = [];
+
+ final List<Uri> importedUris;
+
+ _MatcherBuilder(this.importedUris);
+
+ void buildMatchersForNode(AstNode? node) {
var components = _componentsForNode(node);
if (components == null) {
- return null;
+ return;
}
- return ElementMatcher(
- importedUris: importedUris,
- components: components,
- kinds: _kindsForNode(node));
+ var kinds = _kindsForNode(node) ?? [];
+ _addMatcher(components: components, kinds: kinds);
+ }
+
+ void _addMatcher(
+ {required List<String> components, required List<ElementKind> kinds}) {
+ matchers.add(ElementMatcher(
+ importedUris: importedUris, components: components, kinds: kinds));
}
/// Return the components of the path of the element associated with the given
@@ -244,30 +288,6 @@
return null;
}
- /// Return the URIs of the imports in the library containing the [node], or
- /// `null` if the imports can't be determined.
- static List<Uri>? _importElementsForNode(AstNode node) {
- var root = node.root;
- if (root is! CompilationUnit) {
- return null;
- }
- var importedUris = <Uri>[];
- var library = root.declaredElement?.library;
- if (library == null) {
- return null;
- }
- for (var importElement in library.imports) {
- // TODO(brianwilkerson) Filter based on combinators to help avoid making
- // invalid suggestions.
- var uri = importElement.importedLibrary?.source.uri;
- if (uri != null) {
- // The [uri] is `null` if the literal string is not a valid URI.
- importedUris.add(uri);
- }
- }
- return importedUris;
- }
-
/// Return `true` if the [node] is a prefix
static bool _isPrefix(AstNode? node) {
return node is SimpleIdentifier && node.staticElement is PrefixElement;
@@ -312,6 +332,7 @@
}
return const [
ElementKind.classKind,
+// ElementKind.constructorKind,
ElementKind.enumKind,
ElementKind.mixinKind,
ElementKind.typedefKind
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
index 16ec4ae..716cd55 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
@@ -23,7 +23,9 @@
List<ElementKind>? expectedKinds,
List<String>? expectedUris}) {
var node = findNode.any(search);
- var matcher = ElementMatcher.forNode(node)!;
+ var matchers = ElementMatcher.matchersForNode(node);
+ expect(matchers, hasLength(1));
+ var matcher = matchers[0];
if (expectedUris != null) {
expect(matcher.importedUris,
unorderedEquals(expectedUris.map((uri) => Uri.parse(uri))));
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
index e8d6d1b..ce50e6b 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
@@ -706,6 +706,117 @@
''');
}
+ @failingTest
+ Future<void> test_material_FlatButton_deprecated() async {
+ setPackageContent('''
+@deprecated
+class FlatButton {
+ factory FlatButton.icon({
+ Key? key,
+ required VoidCallback? onPressed,
+ VoidCallback? onLongPress,
+ ValueChanged<bool>? onHighlightChanged,
+ ButtonTextTheme? textTheme,
+ Color? highlightColor,
+ Brightness? colorBrightness,
+ Clip? clipBehavior,
+ FocusNode? focusNode,
+ bool autofocus = true,
+ required Widget icon,
+ required Widget label,
+ }) => FlatButton();
+ FlatButton();
+}
+class Key {}
+class UniqueKey extends Key {}
+typedef VoidCallback = void Function();
+typedef ValueChanged<T> = void Function(T value);
+class ButtonTextTheme {}
+class Color {}
+class Colors {
+ static Color blue = Color();
+}
+class Brightness {
+ static Brightness dark = Brightness();
+}
+class Clip {
+ static Clip hardEdge = Clip();
+}
+class FocusNode {}
+class Widget {}
+class Icon extends Widget {
+ Icon(String icon);
+}
+class Text extends Widget {
+ Text(String content);
+}
+class Icons {
+ static String ten_k_outlined = '';
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ # Changes made in https://github.com/flutter/flutter/pull/73352
+ - title: "Migrate to 'TextButton.icon'"
+ date: 2021-01-08
+ element:
+ uris: [ '$importUri' ]
+ constructor: 'icon'
+ inClass: 'FlatButton'
+ changes:
+ - kind: 'removeParameter'
+ name: 'onHighlightChanged'
+ - kind: 'removeParameter'
+ name: 'textTheme'
+ - kind: 'removeParameter'
+ name: 'highlightColor'
+ - kind: 'removeParameter'
+ name: 'colorBrightness'
+ - kind: 'replacedBy'
+ newElement:
+ uris: [ '$importUri' ]
+ constructor: 'icon'
+ inClass: 'TextButton'
+''');
+ await resolveTestCode('''
+import '$importUri';
+
+void f() {
+ FlatButton.icon(
+ key: UniqueKey(),
+ icon: Icon(Icons.ten_k_outlined),
+ label: Text('FlatButton'),
+ onPressed: (){},
+ onLongPress: (){},
+ clipBehavior: Clip.hardEdge,
+ focusNode: FocusNode(),
+ autofocus: true,
+ onHighlightChanged: (_) {},
+ textTheme: ButtonTextTheme(),
+ highlightColor: Colors.blue,
+ colorBrightness: Brightness.dark,
+ );
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ TextButton.icon(
+ key: UniqueKey(),
+ icon: Icon(Icons.ten_k_outlined),
+ label: Text('FlatButton'),
+ onPressed: (){},
+ onLongPress: (){},
+ clipBehavior: Clip.hardEdge,
+ focusNode: FocusNode(),
+ autofocus: true,
+ );
+}
+''');
+ }
+
Future<void>
test_material_InputDecoration_defaultConstructor_matchFirstCase_deprecated() async {
setPackageContent('''
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
index 95055f4..c3d8722 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
@@ -784,7 +784,7 @@
(transform.changesSelector as UnconditionalChangesSelector).changes;
ElementMatcher _matcher(String name) =>
- ElementMatcher(importedUris: uris, components: [name]);
+ ElementMatcher(importedUris: uris, components: [name], kinds: const []);
List<Transform> _transforms(String name) =>
result!.transformsFor(_matcher(name), applyingBulkFixes: false);
diff --git a/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart b/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
index 168cc9a..6195260 100644
--- a/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
+++ b/pkg/analyzer/lib/src/dart/element/inheritance_manager3.dart
@@ -863,6 +863,7 @@
result.parameters = resultType.parameters;
var field = FieldElementImpl(variableName, -1);
+ field.enclosingElement = targetClass;
if (firstAccessor.isGetter) {
field.getter = result;
field.type = result.returnType;
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart
index 0bbf444..d3dc572 100644
--- a/pkg/analyzer/lib/src/error/codes.g.dart
+++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -3483,9 +3483,10 @@
*/
// #### Description
//
- // The analyzer produces this diagnostic when there's more than one field
- // formal parameter for the same field in a constructor's parameter list. It
- // isn't useful to assign a value that will immediately be overwritten.
+ // The analyzer produces this diagnostic when there's more than one
+ // initializing formal parameter for the same field in a constructor's
+ // parameter list. It isn't useful to assign a value that will immediately be
+ // overwritten.
//
// #### Example
//
@@ -3502,7 +3503,7 @@
//
// #### Common fixes
//
- // Remove one of the field formal parameters:
+ // Remove one of the initializing formal parameters:
//
// ```dart
// class C {
@@ -4751,7 +4752,8 @@
// #### Example
//
// The following code produces this diagnostic because the field `f` is
- // initialized both by a field formal parameter and in the initializer list:
+ // initialized both by an initializing formal parameter and in the
+ // initializer list:
//
// ```dart
// class C {
@@ -4809,14 +4811,14 @@
*/
// #### Description
//
- // The analyzer produces this diagnostic when a factory constructor has a
- // field formal parameter. Factory constructors can't assign values to fields
- // because no instance is created; hence, there is no field to assign.
+ // The analyzer produces this diagnostic when a factory constructor has an
+ // initializing formal parameter. Factory constructors can't assign values to
+ // fields because no instance is created; hence, there is no field to assign.
//
// #### Example
//
// The following code produces this diagnostic because the factory constructor
- // uses a field formal parameter:
+ // uses an initializing formal parameter:
//
// ```dart
// class C {
@@ -4828,7 +4830,7 @@
//
// #### Common fixes
//
- // Replace the field formal parameter with a normal parameter:
+ // Replace the initializing formal parameter with a normal parameter:
//
// ```dart
// class C {
@@ -4924,8 +4926,8 @@
// #### Examples
//
// The following code produces this diagnostic because the constructor
- // `C.zero`, which redirects to the constructor `C`, has a field formal
- // parameter that initializes the field `f`:
+ // `C.zero`, which redirects to the constructor `C`, has an initializing
+ // formal parameter that initializes the field `f`:
//
// ```dart
// class C {
@@ -4953,8 +4955,8 @@
//
// #### Common fixes
//
- // If the initialization is done by a field formal parameter, then use a
- // normal parameter:
+ // If the initialization is done by an initializing formal parameter, then
+ // use a normal parameter:
//
// ```dart
// class C {
@@ -4994,14 +4996,16 @@
*/
// #### Description
//
- // The analyzer produces this diagnostic when the type of a field formal
- // parameter isn't assignable to the type of the field being initialized.
+ // The analyzer produces this diagnostic when the type of an initializing
+ // formal parameter isn't assignable to the type of the field being
+ // initialized.
//
// #### Example
//
- // The following code produces this diagnostic because the field formal
- // parameter has the type `String`, but the type of the field is `int`. The
- // parameter must have a type that is a subtype of the field's type.
+ // The following code produces this diagnostic because the initializing
+ // formal parameter has the type `String`, but the type of the field is
+ // `int`. The parameter must have a type that is a subtype of the field's
+ // type.
//
// ```dart
// class C {
@@ -5037,8 +5041,8 @@
// ```
//
// If the types of both the field and the parameter are correct, then use an
- // initializer rather than a field formal parameter to convert the parameter
- // value into a value of the correct type:
+ // initializer rather than an initializing formal parameter to convert the
+ // parameter value into a value of the correct type:
//
// ```dart
// class C {
@@ -5139,7 +5143,7 @@
//
// For instance fields, you can add an initializer as shown in the previous
// example, or you can initialize the field in every constructor. You can
- // initialize the field by using a field formal parameter:
+ // initialize the field by using an initializing formal parameter:
//
// ```dart
// class C {
@@ -5191,8 +5195,8 @@
//
// #### Common fixes
//
- // If the value should be passed in to the constructor directly, then use a
- // field formal parameter to initialize the field `value`:
+ // If the value should be passed in to the constructor directly, then use an
+ // initializing formal parameter to initialize the field `value`:
//
// ```dart
// class C {
@@ -6199,14 +6203,14 @@
*/
// #### Description
//
- // The analyzer produces this diagnostic when a static field is initialized in
- // a constructor using either a field formal parameter or an assignment in the
- // initializer list.
+ // The analyzer produces this diagnostic when a static field is initialized
+ // in a constructor using either an initializing formal parameter or an
+ // assignment in the initializer list.
//
// #### Example
//
- // The following code produces this diagnostic because the static field `a` is
- // being initialized by the field formal parameter `this.a`:
+ // The following code produces this diagnostic because the static field `a`
+ // is being initialized by the initializing formal parameter `this.a`:
//
// ```dart
// class C {
@@ -6264,10 +6268,10 @@
*/
// #### Description
//
- // The analyzer produces this diagnostic when a field formal parameter is
- // found in a constructor in a class that doesn't declare the field being
- // initialized. Constructors can't initialize fields that aren't declared and
- // fields that are inherited from superclasses.
+ // The analyzer produces this diagnostic when an initializing formal
+ // parameter is found in a constructor in a class that doesn't declare the
+ // field being initialized. Constructors can't initialize fields that aren't
+ // declared and fields that are inherited from superclasses.
//
// #### Example
//
@@ -7016,7 +7020,7 @@
static const CompileTimeErrorCode INVALID_CAST_LITERAL_MAP =
CompileTimeErrorCode(
'INVALID_CAST_LITERAL_MAP',
- "The map literal type '{0}' isn't of expected type '{1}'. The maps's type "
+ "The map literal type '{0}' isn't of expected type '{1}'. The map's type "
"can be changed with an explicit generic type arguments or by changing "
"the key and value types.",
);
diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart
index 3c46109..d2ef4a6 100644
--- a/pkg/analyzer/lib/src/summary2/element_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/element_builder.dart
@@ -214,32 +214,17 @@
var reference = _enclosingContext.addEnum(name, element);
_libraryBuilder.localScope.declare(name, reference);
- var holder = _EnclosingContext(reference, element);
- _withEnclosing(holder, () {
- var typeParameters = node.typeParameters;
- if (typeParameters != null) {
- typeParameters.accept(this);
- element.typeParameters = holder.typeParameters;
- }
- });
-
- var accessors = <PropertyAccessorElement>[];
- var fields = <FieldElementImpl>[];
- var methods = <MethodElement>[];
+ var holder = _EnclosingContext(
+ reference,
+ element,
+ hasConstConstructor: true,
+ );
// Build the 'index' field.
- ConstFieldElementImpl indexField;
- {
- indexField = ConstFieldElementImpl('index', -1)
- ..isSynthetic = true
- ..isFinal = true;
- indexField.bindReference(
- reference.getChild('@field').getChild('index'),
- );
- fields.add(indexField);
- accessors.add(PropertyAccessorElementImpl_ImplicitGetter(indexField,
- reference: reference.getChild('@getter').getChild('index')));
- }
+ var indexField = ConstFieldElementImpl('index', -1)
+ ..isSynthetic = true
+ ..isFinal = true;
+ holder.addNonSyntheticField(indexField);
var constructorIndexParameter = FieldFormalParameterElementImpl(
name: 'index',
@@ -272,13 +257,11 @@
element.constructors = [constructor];
// Build fields for all enum constants.
- var containerRef = reference.getChild('@field');
var constants = node.constants;
var valuesElements = <Expression>[];
for (var i = 0; i < constants.length; ++i) {
var constant = constants[i];
var name = constant.name.name;
- var reference = containerRef.getChild(name);
var field = ConstFieldElementImpl(name, constant.name.offset)
..hasImplicitType = true
..isConst = true
@@ -287,7 +270,6 @@
..type = DynamicTypeImpl.instance;
_setCodeRange(field, constant);
_setDocumentation(field, constant);
- field.reference = reference;
field.metadata = _buildAnnotationsWithUnit(
_unitElement,
constant.metadata,
@@ -334,9 +316,7 @@
_linker.elementNodes[field] = variableDeclaration;
field.constantInitializer = initializer;
- field.createImplicitAccessors(containerRef.parent!, name);
- fields.add(field);
- accessors.add(field.getter as PropertyAccessorElementImpl);
+ holder.addNonSyntheticField(field);
valuesElements.add(
astFactory.simpleIdentifier(
StringToken(TokenType.STRING, name, -1),
@@ -391,9 +371,7 @@
);
_linker.elementNodes[valuesField] = variableDeclaration;
- fields.add(valuesField);
- accessors.add(PropertyAccessorElementImpl_ImplicitGetter(valuesField,
- reference: reference.getChild('@getter').getChild('values')));
+ holder.addNonSyntheticField(valuesField);
}
// TODO(scheglov) implement
@@ -401,23 +379,16 @@
// node.withClause?.accept(this);
// node.implementsClause?.accept(this);
- // TODO(scheglov) don't create a duplicate
- {
- var holder2 = _buildClassMembers(element, node.members);
- fields.addAll(
- holder2.properties.whereType<FieldElementImpl>(),
- );
- accessors.addAll(holder2.propertyAccessors);
- methods.addAll(holder2.methods);
- }
+ _withEnclosing(holder, () {
+ node.typeParameters?.accept(this);
+ _visitPropertyFirst<FieldDeclaration>(node.members);
+ });
- // TODO(scheglov) only if no explicit
- MethodElementImpl toStringMethod;
- {
- toStringMethod = MethodElementImpl('toString', -1)..isSynthetic = true;
- methods.add(toStringMethod);
- toStringMethod.reference =
- reference.getChild('@method').getChild('toString');
+ MethodElementImpl? syntheticToStringMethod;
+ if (holder.getMethod('toString').element == null) {
+ syntheticToStringMethod = MethodElementImpl('toString', -1)
+ ..isSynthetic = true;
+ holder.addMethod(name, syntheticToStringMethod);
}
_libraryBuilder.implicitEnumNodes.add(
@@ -428,13 +399,14 @@
valuesField: valuesField,
constructorIndexParameter: constructorIndexParameter,
constructorNameParameter: constructorNameParameter,
- syntheticToStringMethod: toStringMethod,
+ syntheticToStringMethod: syntheticToStringMethod,
),
);
- element.accessors = accessors;
- element.fields = fields;
- element.methods = methods;
+ element.typeParameters = holder.typeParameters;
+ element.accessors = holder.propertyAccessors;
+ element.fields = holder.properties.whereType<FieldElementImpl>().toList();
+ element.methods = holder.methods;
// TODO(scheglov) resolve field formals
}
@@ -486,8 +458,12 @@
}
});
+ // TODO(scheglov) don't create a duplicate
{
- var holder = _buildClassMembers(element, node.members);
+ var holder = _EnclosingContext(reference, element);
+ _withEnclosing(holder, () {
+ _visitPropertyFirst<FieldDeclaration>(node.members);
+ });
element.accessors = holder.propertyAccessors;
element.fields = holder.properties.whereType<FieldElement>().toList();
element.methods = holder.methods;
@@ -500,8 +476,6 @@
void visitFieldDeclaration(
covariant FieldDeclarationImpl node,
) {
- var enclosingRef = _enclosingContext.reference;
-
var metadata = _buildAnnotations(node.metadata);
for (var variable in node.fields.variables) {
var nameNode = variable.name as SimpleIdentifierImpl;
@@ -533,21 +507,10 @@
element.type = DynamicTypeImpl.instance;
}
- element.createImplicitAccessors(enclosingRef, name);
+ _enclosingContext.addNonSyntheticField(element);
_linker.elementNodes[element] = variable;
- _enclosingContext.addField(name, element);
nameNode.staticElement = element;
-
- var getter = element.getter;
- if (getter is PropertyAccessorElementImpl) {
- _enclosingContext.addGetter(name, getter);
- }
-
- var setter = element.setter;
- if (setter is PropertyAccessorElementImpl) {
- _enclosingContext.addSetter(name, setter);
- }
}
_buildType(node.fields.type);
}
@@ -1165,23 +1128,17 @@
return _buildAnnotationsWithUnit(_unitElement, nodeList);
}
- _EnclosingContext _buildClassMembers(
- ElementImpl element, List<ClassMember> members) {
- var hasConstConstructor = members.any((e) {
+ void _buildClassOrMixin(ClassOrMixinDeclaration node) {
+ var element = node.declaredElement as ClassElementImpl;
+ var hasConstConstructor = node.members.any((e) {
return e is ConstructorDeclaration && e.constKeyword != null;
});
+ // TODO(scheglov) don't create a duplicate
var holder = _EnclosingContext(element.reference!, element,
hasConstConstructor: hasConstConstructor);
_withEnclosing(holder, () {
- _visitPropertyFirst<FieldDeclaration>(members);
+ _visitPropertyFirst<FieldDeclaration>(node.members);
});
- return holder;
- }
-
- void _buildClassOrMixin(ClassOrMixinDeclaration node) {
- var element = node.declaredElement as ClassElementImpl;
- // TODO(scheglov) don't create a duplicate
- var holder = _buildClassMembers(element, node.members);
element.accessors = holder.propertyAccessors;
element.fields = holder.properties.whereType<FieldElement>().toList();
element.methods = holder.methods;
@@ -1483,6 +1440,23 @@
return _bindReference('@mixin', name, element);
}
+ void addNonSyntheticField(FieldElementImpl element) {
+ var name = element.name;
+ element.createImplicitAccessors(reference, name);
+
+ addField(name, element);
+
+ var getter = element.getter;
+ if (getter is PropertyAccessorElementImpl) {
+ addGetter(name, getter);
+ }
+
+ var setter = element.setter;
+ if (setter is PropertyAccessorElementImpl) {
+ addSetter(name, setter);
+ }
+ }
+
Reference? addParameter(String? name, ParameterElementImpl element) {
parameters.add(element);
if (name == null) {
@@ -1513,6 +1487,10 @@
this.element.encloseElement(element);
}
+ Reference getMethod(String name) {
+ return reference.getChild('@method').getChild(name);
+ }
+
Reference _bindReference(
String containerName,
String name,
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index c26e6f7..e8b40c2 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -27,7 +27,7 @@
final ConstFieldElementImpl valuesField;
final ParameterElementImpl constructorIndexParameter;
final ParameterElementImpl constructorNameParameter;
- final MethodElementImpl syntheticToStringMethod;
+ final MethodElementImpl? syntheticToStringMethod;
ImplicitEnumNodes({
required this.element,
@@ -149,7 +149,7 @@
enum_.valuesField.type = valuesType;
enum_.constructorIndexParameter.type = typeProvider.intType;
enum_.constructorNameParameter.type = typeProvider.stringType;
- enum_.syntheticToStringMethod.returnType = typeProvider.stringType;
+ enum_.syntheticToStringMethod?.returnType = typeProvider.stringType;
}
}
diff --git a/pkg/analyzer/lib/src/test_utilities/find_element.dart b/pkg/analyzer/lib/src/test_utilities/find_element.dart
index 8535a39..25dfccc 100644
--- a/pkg/analyzer/lib/src/test_utilities/find_element.dart
+++ b/pkg/analyzer/lib/src/test_utilities/find_element.dart
@@ -502,13 +502,6 @@
}
}
- for (var extension_ in unitElement.extensions) {
- if (of != null && extension_.name != of) {
- continue;
- }
- findIn(extension_.methods);
- }
-
for (var class_ in unitElement.classes) {
if (of != null && class_.name != of) {
continue;
@@ -516,6 +509,20 @@
findIn(class_.methods);
}
+ for (var enum_ in unitElement.enums) {
+ if (of != null && enum_.name != of) {
+ continue;
+ }
+ findIn(enum_.methods);
+ }
+
+ for (var extension_ in unitElement.extensions) {
+ if (of != null && extension_.name != of) {
+ continue;
+ }
+ findIn(extension_.methods);
+ }
+
for (var mixin in unitElement.mixins) {
if (of != null && mixin.name != of) {
continue;
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 4a3af55..8507f19 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -3203,9 +3203,10 @@
documentation: |-
#### Description
- The analyzer produces this diagnostic when there's more than one field
- formal parameter for the same field in a constructor's parameter list. It
- isn't useful to assign a value that will immediately be overwritten.
+ The analyzer produces this diagnostic when there's more than one
+ initializing formal parameter for the same field in a constructor's
+ parameter list. It isn't useful to assign a value that will immediately be
+ overwritten.
#### Example
@@ -3222,7 +3223,7 @@
#### Common fixes
- Remove one of the field formal parameters:
+ Remove one of the initializing formal parameters:
```dart
class C {
@@ -4169,7 +4170,8 @@
#### Example
The following code produces this diagnostic because the field `f` is
- initialized both by a field formal parameter and in the initializer list:
+ initialized both by an initializing formal parameter and in the
+ initializer list:
```dart
class C {
@@ -4221,14 +4223,14 @@
documentation: |-
#### Description
- The analyzer produces this diagnostic when a factory constructor has a
- field formal parameter. Factory constructors can't assign values to fields
- because no instance is created; hence, there is no field to assign.
+ The analyzer produces this diagnostic when a factory constructor has an
+ initializing formal parameter. Factory constructors can't assign values to
+ fields because no instance is created; hence, there is no field to assign.
#### Example
The following code produces this diagnostic because the factory constructor
- uses a field formal parameter:
+ uses an initializing formal parameter:
```dart
class C {
@@ -4240,7 +4242,7 @@
#### Common fixes
- Replace the field formal parameter with a normal parameter:
+ Replace the initializing formal parameter with a normal parameter:
```dart
class C {
@@ -4331,8 +4333,8 @@
#### Examples
The following code produces this diagnostic because the constructor
- `C.zero`, which redirects to the constructor `C`, has a field formal
- parameter that initializes the field `f`:
+ `C.zero`, which redirects to the constructor `C`, has an initializing
+ formal parameter that initializes the field `f`:
```dart
class C {
@@ -4360,8 +4362,8 @@
#### Common fixes
- If the initialization is done by a field formal parameter, then use a
- normal parameter:
+ If the initialization is done by an initializing formal parameter, then
+ use a normal parameter:
```dart
class C {
@@ -4396,14 +4398,16 @@
documentation: |-
#### Description
- The analyzer produces this diagnostic when the type of a field formal
- parameter isn't assignable to the type of the field being initialized.
+ The analyzer produces this diagnostic when the type of an initializing
+ formal parameter isn't assignable to the type of the field being
+ initialized.
#### Example
- The following code produces this diagnostic because the field formal
- parameter has the type `String`, but the type of the field is `int`. The
- parameter must have a type that is a subtype of the field's type.
+ The following code produces this diagnostic because the initializing
+ formal parameter has the type `String`, but the type of the field is
+ `int`. The parameter must have a type that is a subtype of the field's
+ type.
```dart
class C {
@@ -4439,8 +4443,8 @@
```
If the types of both the field and the parameter are correct, then use an
- initializer rather than a field formal parameter to convert the parameter
- value into a value of the correct type:
+ initializer rather than an initializing formal parameter to convert the
+ parameter value into a value of the correct type:
```dart
class C {
@@ -4530,7 +4534,7 @@
For instance fields, you can add an initializer as shown in the previous
example, or you can initialize the field in every constructor. You can
- initialize the field by using a field formal parameter:
+ initialize the field by using an initializing formal parameter:
```dart
class C {
@@ -4579,8 +4583,8 @@
#### Common fixes
- If the value should be passed in to the constructor directly, then use a
- field formal parameter to initialize the field `value`:
+ If the value should be passed in to the constructor directly, then use an
+ initializing formal parameter to initialize the field `value`:
```dart
class C {
@@ -5380,14 +5384,14 @@
documentation: |-
#### Description
- The analyzer produces this diagnostic when a static field is initialized in
- a constructor using either a field formal parameter or an assignment in the
- initializer list.
+ The analyzer produces this diagnostic when a static field is initialized
+ in a constructor using either an initializing formal parameter or an
+ assignment in the initializer list.
#### Example
- The following code produces this diagnostic because the static field `a` is
- being initialized by the field formal parameter `this.a`:
+ The following code produces this diagnostic because the static field `a`
+ is being initialized by the initializing formal parameter `this.a`:
```dart
class C {
@@ -5440,10 +5444,10 @@
documentation: |-
#### Description
- The analyzer produces this diagnostic when a field formal parameter is
- found in a constructor in a class that doesn't declare the field being
- initialized. Constructors can't initialize fields that aren't declared and
- fields that are inherited from superclasses.
+ The analyzer produces this diagnostic when an initializing formal
+ parameter is found in a constructor in a class that doesn't declare the
+ field being initialized. Constructors can't initialize fields that aren't
+ declared and fields that are inherited from superclasses.
#### Example
@@ -6076,7 +6080,7 @@
0: the type of the list literal
1: the expected type
INVALID_CAST_LITERAL_MAP:
- problemMessage: "The map literal type '{0}' isn't of expected type '{1}'. The maps's type can be changed with an explicit generic type arguments or by changing the key and value types."
+ problemMessage: "The map literal type '{0}' isn't of expected type '{1}'. The map's type can be changed with an explicit generic type arguments or by changing the key and value types."
comment: |-
Parameters:
0: the type of the map literal
diff --git a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
index ba5aa89..73da833 100644
--- a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
+++ b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
@@ -1425,6 +1425,7 @@
if (element is PropertyAccessorElement) {
var variable = element.variable;
+ expect(variable.enclosingElement, same(element.enclosingElement));
expect(variable.name, element.displayName);
if (element.isGetter) {
expect(variable.type, element.returnType);
diff --git a/pkg/analyzer/test/src/dart/resolution/enum_test.dart b/pkg/analyzer/test/src/dart/resolution/enum_test.dart
index 7fe267d..1e71eb6 100644
--- a/pkg/analyzer/test/src/dart/resolution/enum_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/enum_test.dart
@@ -15,6 +15,20 @@
@reflectiveTest
class EnumDriverResolutionTest extends PubPackageResolutionTest {
+ test_field() async {
+ await assertNoErrorsInCode(r'''
+enum E<T> {
+ v;
+ final foo = 42;
+}
+''');
+
+ assertElement(
+ findNode.variableDeclaration('foo ='),
+ findElement.field('foo', of: 'E'),
+ );
+ }
+
test_inference_listLiteral() async {
await assertNoErrorsInCode(r'''
enum E1 {a, b}
@@ -72,6 +86,20 @@
);
}
+ test_method_toString() async {
+ await assertNoErrorsInCode(r'''
+enum E<T> {
+ v;
+ String toString() => 'E';
+}
+''');
+
+ assertElement(
+ findNode.methodDeclaration('toString'),
+ findElement.method('toString', of: 'E'),
+ );
+ }
+
test_value_underscore() async {
await assertNoErrorsInCode(r'''
enum E { _ }
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index a61787f..4c29cd7 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -17582,6 +17582,90 @@
''');
}
+ test_enum_field() async {
+ var library = await checkLibrary(r'''
+enum E {
+ v;
+ final foo = 42;
+}
+''');
+ checkElementText(library, r'''
+library
+ definingUnit
+ enums
+ enum E @5
+ supertype: Enum
+ fields
+ synthetic final index @-1
+ type: int
+ static const enumConstant v @11
+ type: E
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ arguments
+ IntegerLiteral
+ literal: 0 @0
+ staticType: int
+ SimpleStringLiteral
+ literal: 'v' @0
+ leftParenthesis: ( @0
+ rightParenthesis: ) @0
+ constructorName: ConstructorName
+ name: SimpleIdentifier
+ staticElement: self::@enum::E::@constructor::_
+ staticType: null
+ token: _ @-1
+ period: . @0
+ staticElement: self::@enum::E::@constructor::_
+ type: NamedType
+ name: SimpleIdentifier
+ staticElement: self::@enum::E
+ staticType: null
+ token: E @-1
+ type: E
+ staticType: E
+ synthetic static const values @-1
+ type: List<E>
+ constantInitializer
+ ListLiteral
+ elements
+ SimpleIdentifier
+ staticElement: self::@enum::E::@getter::v
+ staticType: E
+ token: v @-1
+ leftBracket: [ @0
+ rightBracket: ] @0
+ staticType: List<E>
+ final foo @22
+ type: int
+ constantInitializer
+ IntegerLiteral
+ literal: 42 @28
+ staticType: int
+ constructors
+ synthetic const _ @-1
+ parameters
+ requiredPositional final this.index @-1
+ type: int
+ field: self::@enum::E::@field::index
+ requiredPositional name @-1
+ type: String
+ accessors
+ synthetic get index @-1
+ returnType: int
+ synthetic static get v @-1
+ returnType: E
+ synthetic static get values @-1
+ returnType: List<E>
+ synthetic get foo @-1
+ returnType: int
+ methods
+ synthetic toString @-1
+ returnType: String
+''');
+ }
+
test_enum_method() async {
var library = await checkLibrary(r'''
enum E<T> {
@@ -17674,6 +17758,82 @@
''');
}
+ test_enum_method_toString() async {
+ var library = await checkLibrary(r'''
+enum E {
+ v;
+ String toString() => 'E';
+}
+''');
+ checkElementText(library, r'''
+library
+ definingUnit
+ enums
+ enum E @5
+ supertype: Enum
+ fields
+ synthetic final index @-1
+ type: int
+ static const enumConstant v @11
+ type: E
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ arguments
+ IntegerLiteral
+ literal: 0 @0
+ staticType: int
+ SimpleStringLiteral
+ literal: 'v' @0
+ leftParenthesis: ( @0
+ rightParenthesis: ) @0
+ constructorName: ConstructorName
+ name: SimpleIdentifier
+ staticElement: self::@enum::E::@constructor::_
+ staticType: null
+ token: _ @-1
+ period: . @0
+ staticElement: self::@enum::E::@constructor::_
+ type: NamedType
+ name: SimpleIdentifier
+ staticElement: self::@enum::E
+ staticType: null
+ token: E @-1
+ type: E
+ staticType: E
+ synthetic static const values @-1
+ type: List<E>
+ constantInitializer
+ ListLiteral
+ elements
+ SimpleIdentifier
+ staticElement: self::@enum::E::@getter::v
+ staticType: E
+ token: v @-1
+ leftBracket: [ @0
+ rightBracket: ] @0
+ staticType: List<E>
+ constructors
+ synthetic const _ @-1
+ parameters
+ requiredPositional final this.index @-1
+ type: int
+ field: self::@enum::E::@field::index
+ requiredPositional name @-1
+ type: String
+ accessors
+ synthetic get index @-1
+ returnType: int
+ synthetic static get v @-1
+ returnType: E
+ synthetic static get values @-1
+ returnType: List<E>
+ methods
+ toString @23
+ returnType: String
+''');
+ }
+
test_enum_typeParameters() async {
var library = await checkLibrary('''
enum E<T> {
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index 0b6ebfb..282257c 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -3501,9 +3501,10 @@
#### Description
-The analyzer produces this diagnostic when there's more than one field
-formal parameter for the same field in a constructor's parameter list. It
-isn't useful to assign a value that will immediately be overwritten.
+The analyzer produces this diagnostic when there's more than one
+initializing formal parameter for the same field in a constructor's
+parameter list. It isn't useful to assign a value that will immediately be
+overwritten.
#### Example
@@ -3520,7 +3521,7 @@
#### Common fixes
-Remove one of the field formal parameters:
+Remove one of the initializing formal parameters:
{% prettify dart tag=pre+code %}
class C {
@@ -4718,7 +4719,8 @@
#### Example
The following code produces this diagnostic because the field `f` is
-initialized both by a field formal parameter and in the initializer list:
+initialized both by an initializing formal parameter and in the
+initializer list:
{% prettify dart tag=pre+code %}
class C {
@@ -4769,14 +4771,14 @@
#### Description
-The analyzer produces this diagnostic when a factory constructor has a
-field formal parameter. Factory constructors can't assign values to fields
-because no instance is created; hence, there is no field to assign.
+The analyzer produces this diagnostic when a factory constructor has an
+initializing formal parameter. Factory constructors can't assign values to
+fields because no instance is created; hence, there is no field to assign.
#### Example
The following code produces this diagnostic because the factory constructor
-uses a field formal parameter:
+uses an initializing formal parameter:
{% prettify dart tag=pre+code %}
class C {
@@ -4788,7 +4790,7 @@
#### Common fixes
-Replace the field formal parameter with a normal parameter:
+Replace the initializing formal parameter with a normal parameter:
{% prettify dart tag=pre+code %}
class C {
@@ -4862,8 +4864,8 @@
#### Examples
The following code produces this diagnostic because the constructor
-`C.zero`, which redirects to the constructor `C`, has a field formal
-parameter that initializes the field `f`:
+`C.zero`, which redirects to the constructor `C`, has an initializing
+formal parameter that initializes the field `f`:
{% prettify dart tag=pre+code %}
class C {
@@ -4891,8 +4893,8 @@
#### Common fixes
-If the initialization is done by a field formal parameter, then use a
-normal parameter:
+If the initialization is done by an initializing formal parameter, then
+use a normal parameter:
{% prettify dart tag=pre+code %}
class C {
@@ -4923,14 +4925,16 @@
#### Description
-The analyzer produces this diagnostic when the type of a field formal
-parameter isn't assignable to the type of the field being initialized.
+The analyzer produces this diagnostic when the type of an initializing
+formal parameter isn't assignable to the type of the field being
+initialized.
#### Example
-The following code produces this diagnostic because the field formal
-parameter has the type `String`, but the type of the field is `int`. The
-parameter must have a type that is a subtype of the field's type.
+The following code produces this diagnostic because the initializing
+formal parameter has the type `String`, but the type of the field is
+`int`. The parameter must have a type that is a subtype of the field's
+type.
{% prettify dart tag=pre+code %}
class C {
@@ -4966,8 +4970,8 @@
{% endprettify %}
If the types of both the field and the parameter are correct, then use an
-initializer rather than a field formal parameter to convert the parameter
-value into a value of the correct type:
+initializer rather than an initializing formal parameter to convert the
+parameter value into a value of the correct type:
{% prettify dart tag=pre+code %}
class C {
@@ -5052,7 +5056,7 @@
For instance fields, you can add an initializer as shown in the previous
example, or you can initialize the field in every constructor. You can
-initialize the field by using a field formal parameter:
+initialize the field by using an initializing formal parameter:
{% prettify dart tag=pre+code %}
class C {
@@ -5102,8 +5106,8 @@
#### Common fixes
-If the value should be passed in to the constructor directly, then use a
-field formal parameter to initialize the field `value`:
+If the value should be passed in to the constructor directly, then use an
+initializing formal parameter to initialize the field `value`:
{% prettify dart tag=pre+code %}
class C {
@@ -5880,14 +5884,14 @@
#### Description
-The analyzer produces this diagnostic when a static field is initialized in
-a constructor using either a field formal parameter or an assignment in the
-initializer list.
+The analyzer produces this diagnostic when a static field is initialized
+in a constructor using either an initializing formal parameter or an
+assignment in the initializer list.
#### Example
-The following code produces this diagnostic because the static field `a` is
-being initialized by the field formal parameter `this.a`:
+The following code produces this diagnostic because the static field `a`
+is being initialized by the initializing formal parameter `this.a`:
{% prettify dart tag=pre+code %}
class C {
@@ -5936,10 +5940,10 @@
#### Description
-The analyzer produces this diagnostic when a field formal parameter is
-found in a constructor in a class that doesn't declare the field being
-initialized. Constructors can't initialize fields that aren't declared and
-fields that are inherited from superclasses.
+The analyzer produces this diagnostic when an initializing formal
+parameter is found in a constructor in a class that doesn't declare the
+field being initialized. Constructors can't initialize fields that aren't
+declared and fields that are inherited from superclasses.
#### Example
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index 3e75181..f3d60c4 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -11,7 +11,6 @@
import 'package:args/command_runner.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:dart_style/src/cli/format_command.dart';
-import 'package:devtools_server/devtools_server.dart';
import 'package:meta/meta.dart';
import 'package:pub/pub.dart';
import 'package:usage/usage.dart';
@@ -21,6 +20,7 @@
import 'src/commands/compile.dart';
import 'src/commands/create.dart';
import 'src/commands/debug_adapter.dart';
+import 'src/commands/devtools.dart';
import 'src/commands/doc.dart';
import 'src/commands/fix.dart';
import 'src/commands/language_server.dart';
@@ -30,7 +30,6 @@
import 'src/core.dart';
import 'src/events.dart';
import 'src/experiments.dart';
-import 'src/sdk.dart';
import 'src/utils.dart';
import 'src/vm_interop_handler.dart';
@@ -118,7 +117,6 @@
addCommand(DocCommand(verbose: verbose));
addCommand(DevToolsCommand(
verbose: verbose,
- customDevToolsPath: sdk.devToolsBinaries,
));
addCommand(FixCommand(verbose: verbose));
addCommand(FormatCommand(verbose: verbose));
diff --git a/pkg/dartdev/lib/src/commands/devtools.dart b/pkg/dartdev/lib/src/commands/devtools.dart
new file mode 100644
index 0000000..e01c6bb
--- /dev/null
+++ b/pkg/dartdev/lib/src/commands/devtools.dart
@@ -0,0 +1,257 @@
+// Copyright (c) 2022, 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:dds/devtools_server.dart';
+import 'package:dds/src/devtools/utils.dart';
+import 'package:path/path.dart' as path;
+
+import '../core.dart';
+import '../sdk.dart';
+
+class DevToolsCommand extends DartdevCommand {
+ static const commandDescription =
+ 'Open DevTools (optionally connecting to an existing application).';
+
+ static const protocolVersion = '1.1.0';
+ static const argHelp = 'help';
+ static const argVmUri = 'vm-uri';
+ static const argEnableNotifications = 'enable-notifications';
+ static const argAllowEmbedding = 'allow-embedding';
+ static const argAppSizeBase = 'appSizeBase';
+ static const argAppSizeTest = 'appSizeTest';
+ static const argHeadlessMode = 'headless';
+ static const argDebugMode = 'debug';
+ static const argLaunchBrowser = 'launch-browser';
+ static const argMachine = 'machine';
+ static const argHost = 'host';
+ static const argPort = 'port';
+ static const argProfileMemory = 'record-memory-profile';
+ static const argTryPorts = 'try-ports';
+ static const argVerbose = 'verbose';
+ static const argVersion = 'version';
+ static const launchDevToolsService = 'launchDevTools';
+
+ DevToolsCommand({
+ this.customDevToolsPath,
+ bool verbose = false,
+ }) : super(
+ 'devtools',
+ commandDescription,
+ verbose,
+ ) {
+ argParser
+ ..addFlag(
+ argVersion,
+ negatable: false,
+ help: 'Prints the DevTools version.',
+ )
+ ..addFlag(
+ argVerbose,
+ negatable: false,
+ abbr: 'v',
+ help: 'Output more informational messages.',
+ )
+ ..addOption(
+ argHost,
+ valueHelp: 'host',
+ help: 'Hostname to serve DevTools on (defaults to localhost).',
+ )
+ ..addOption(
+ argPort,
+ defaultsTo: '9100',
+ valueHelp: 'port',
+ help: 'Port to serve DevTools on; specify 0 to automatically use any '
+ 'available port.',
+ )
+ ..addFlag(
+ argLaunchBrowser,
+ help:
+ 'Launches DevTools in a browser immediately at start.\n(defaults to on unless in --machine mode)',
+ )
+ ..addFlag(
+ argMachine,
+ negatable: false,
+ help: 'Sets output format to JSON for consumption in tools.',
+ )
+ ..addSeparator('Memory profiling options:')
+ ..addOption(
+ argProfileMemory,
+ valueHelp: 'file',
+ defaultsTo: 'memory_samples.json',
+ help:
+ 'Start devtools headlessly and write memory profiling samples to the '
+ 'indicated file.',
+ );
+
+ if (verbose) {
+ argParser.addSeparator('App size options:');
+ }
+
+ // TODO(devoncarew): --appSizeBase and --appSizeTest should be renamed to
+ // something like --app-size-base and --app-size-test; #3146.
+ argParser
+ ..addOption(
+ argAppSizeBase,
+ valueHelp: 'appSizeBase',
+ help: 'Path to the base app size file used for app size debugging.',
+ hide: !verbose,
+ )
+ ..addOption(
+ argAppSizeTest,
+ valueHelp: 'appSizeTest',
+ help:
+ 'Path to the test app size file used for app size debugging.\nThis '
+ 'file should only be specified if --$argAppSizeBase is also specified.',
+ hide: !verbose,
+ );
+
+ if (verbose) {
+ argParser.addSeparator('Advanced options:');
+ }
+
+ // Args to show for verbose mode.
+ argParser
+ ..addOption(
+ argTryPorts,
+ defaultsTo: DevToolsServer.defaultTryPorts.toString(),
+ valueHelp: 'count',
+ help: 'The number of ascending ports to try binding to before failing '
+ 'with an error. ',
+ hide: !verbose,
+ )
+ ..addFlag(
+ argEnableNotifications,
+ negatable: false,
+ help: 'Requests notification permissions immediately when a client '
+ 'connects back to the server.',
+ hide: !verbose,
+ )
+ ..addFlag(
+ argAllowEmbedding,
+ help: 'Allow embedding DevTools inside an iframe.',
+ hide: !verbose,
+ )
+ ..addFlag(
+ argHeadlessMode,
+ negatable: false,
+ help: 'Causes the server to spawn Chrome in headless mode for use in '
+ 'automated testing.',
+ hide: !verbose,
+ );
+
+ // Deprecated and hidden argResults.
+ // TODO: Remove this - prefer that clients use the rest arg.
+ argParser
+ ..addOption(
+ argVmUri,
+ defaultsTo: '',
+ help: 'VM Service protocol URI.',
+ hide: true,
+ )
+
+ // Development only argResults.
+ ..addFlag(
+ argDebugMode,
+ negatable: false,
+ help: 'Run a debug build of the DevTools web frontend.',
+ hide: true,
+ );
+ }
+
+ final String customDevToolsPath;
+
+ @override
+ String get name => 'devtools';
+
+ @override
+ String get description => commandDescription;
+
+ @override
+ String get invocation => '${super.invocation} [service protocol uri]';
+
+ @override
+ Future<int> run() async {
+ final bool version = argResults[argVersion];
+ final bool machineMode = argResults[argMachine];
+ // launchBrowser defaults based on machine-mode if not explicitly supplied.
+ final bool launchBrowser = argResults.wasParsed(argLaunchBrowser)
+ ? argResults[argLaunchBrowser]
+ : !machineMode;
+ final bool enableNotifications = argResults[argEnableNotifications];
+ final bool allowEmbedding = argResults.wasParsed(argAllowEmbedding)
+ ? argResults[argAllowEmbedding]
+ : true;
+
+ final port = argResults[argPort] != null
+ ? int.tryParse(argResults[argPort]) ?? 0
+ : 0;
+
+ final bool headlessMode = argResults[argHeadlessMode];
+ final bool debugMode = argResults[argDebugMode];
+
+ final numPortsToTry = argResults[argTryPorts] != null
+ ? int.tryParse(argResults[argTryPorts]) ?? 0
+ : DevToolsServer.defaultTryPorts;
+
+ final bool verboseMode = argResults[argVerbose];
+ final String hostname = argResults[argHost];
+ final String appSizeBase = argResults[argAppSizeBase];
+ final String appSizeTest = argResults[argAppSizeTest];
+
+ final sdkDir = path.dirname(sdk.dart);
+ final fullSdk = sdkDir.endsWith('bin');
+ final devToolsBinaries =
+ fullSdk ? sdk.devToolsBinaries : path.absolute(sdkDir, 'devtools');
+
+ if (version) {
+ final versionStr = await DevToolsUtils.getVersion(devToolsBinaries);
+ DevToolsUtils.printOutput(
+ 'Dart DevTools version $versionStr',
+ {
+ 'version': versionStr,
+ },
+ machineMode: machineMode,
+ );
+ return null;
+ }
+
+ // Prefer getting the VM URI from the rest argResults; fall back on the 'vm-url'
+ // option otherwise.
+ String serviceProtocolUri;
+ if (argResults.rest.isNotEmpty) {
+ serviceProtocolUri = argResults.rest.first;
+ } else if (argResults.wasParsed(argVmUri)) {
+ serviceProtocolUri = argResults[argVmUri];
+ }
+
+ // Support collecting profile data.
+ String profileFilename;
+ if (argResults.wasParsed(argProfileMemory)) {
+ profileFilename = argResults[argProfileMemory];
+ }
+ if (profileFilename != null && !path.isAbsolute(profileFilename)) {
+ profileFilename = path.absolute(profileFilename);
+ }
+
+ final server = await DevToolsServer().serveDevTools(
+ machineMode: machineMode,
+ debugMode: debugMode,
+ launchBrowser: launchBrowser,
+ enableNotifications: enableNotifications,
+ allowEmbedding: allowEmbedding,
+ port: port,
+ headlessMode: headlessMode,
+ numPortsToTry: numPortsToTry,
+ customDevToolsPath: customDevToolsPath ?? devToolsBinaries,
+ serviceProtocolUri: serviceProtocolUri,
+ profileFilename: profileFilename,
+ verboseMode: verboseMode,
+ hostname: hostname,
+ appSizeBase: appSizeBase,
+ appSizeTest: appSizeTest,
+ );
+
+ return server == null ? -1 : 0;
+ }
+}
diff --git a/pkg/dartdev/lib/src/commands/doc.dart b/pkg/dartdev/lib/src/commands/doc.dart
index 8faff48..6201a28 100644
--- a/pkg/dartdev/lib/src/commands/doc.dart
+++ b/pkg/dartdev/lib/src/commands/doc.dart
@@ -23,15 +23,20 @@
DocCommand({bool verbose = false}) : super(cmdName, cmdDescription, verbose) {
argParser.addOption(
- 'output-dir',
+ 'output',
abbr: 'o',
+ valueHelp: 'directory',
defaultsTo: path.join('doc', 'api'),
- help: 'Output directory',
+ aliases: [
+ // The CLI option that shipped with Dart 2.16.
+ 'output-dir',
+ ],
+ help: 'Configure the output directory.',
);
argParser.addFlag(
'validate-links',
- negatable: true,
- help: 'Display context aware warnings for broken links (slow)',
+ negatable: false,
+ help: 'Display warnings for broken links.',
);
}
@@ -51,21 +56,17 @@
usageException("Error: Input directory doesn't exist: ${dir.path}");
}
- // Parse options.
- final options = [
- '--input=${dir.path}',
- '--output=${argResults['output-dir']}',
- ];
- if (argResults['validate-links']) {
- options.add('--validate-links');
- } else {
- options.add('--no-validate-links');
- }
-
// Specify where dartdoc resources are located.
final resourcesPath =
path.absolute(sdk.sdkPath, 'bin', 'resources', 'dartdoc', 'resources');
- options.add('--resources-dir=$resourcesPath');
+
+ // Build options.
+ final options = [
+ '--input=${dir.path}',
+ '--output=${argResults['output']}',
+ '--resources-dir=$resourcesPath',
+ if (argResults['validate-links']) '--validate-links'
+ ];
final config = await parseOptions(pubPackageMetaProvider, options);
if (config == null) {
@@ -73,11 +74,10 @@
return 2;
}
- // Call dartdoc.
+ // Call into package:dartdoc.
if (verbose) {
log.stdout('Using the following options: $options');
}
-
final packageConfigProvider = PhysicalPackageConfigProvider();
final packageBuilder = PubPackageBuilder(
config, pubPackageMetaProvider, packageConfigProvider);
diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml
index 9d78178..ea17317 100644
--- a/pkg/dartdev/pubspec.yaml
+++ b/pkg/dartdev/pubspec.yaml
@@ -20,7 +20,6 @@
dartdoc: any
dds:
path: ../dds
- devtools_server: any
front_end:
path: ../front_end
meta:
diff --git a/pkg/dartdev/test/commands/devtools_test.dart b/pkg/dartdev/test/commands/devtools_test.dart
index e14ba0ac..24cb80b 100644
--- a/pkg/dartdev/test/commands/devtools_test.dart
+++ b/pkg/dartdev/test/commands/devtools_test.dart
@@ -37,8 +37,10 @@
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('Open DevTools'));
- expect(result.stdout,
- contains('Usage: dart devtools [arguments] [service protocol uri]'));
+ expect(
+ result.stdout,
+ contains(
+ 'Usage: dart [vm-options] devtools [arguments] [service protocol uri]'));
// Shows verbose help.
expect(result.stdout, contains('--try-ports'));
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 03a0044..3cb8dd8 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 2.2.0
+- Add support for serving DevTools via `package:dds/devtools_server.dart`
+
# 2.1.7
- Re-release 2.1.6+1.
diff --git a/pkg/dds/lib/devtools_server.dart b/pkg/dds/lib/devtools_server.dart
new file mode 100644
index 0000000..ee451fa
--- /dev/null
+++ b/pkg/dds/lib/devtools_server.dart
@@ -0,0 +1,403 @@
+// Copyright (c) 2022, 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:browser_launcher/browser_launcher.dart';
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf;
+
+import 'src/devtools/client.dart';
+import 'src/devtools/handler.dart';
+import 'src/devtools/machine_mode_command_handler.dart';
+import 'src/devtools/memory_profile.dart';
+import 'src/devtools/utils.dart';
+import 'src/utils/console.dart';
+
+class DevToolsServer {
+ static const protocolVersion = '1.1.0';
+ static const defaultTryPorts = 10;
+
+ MachineModeCommandHandler? _machineModeCommandHandler;
+ late ClientManager clientManager;
+ final bool _isChromeOS = File('/dev/.cros_milestone').existsSync();
+
+ /// Serves DevTools.
+ ///
+ /// `handler` is the [shelf.Handler] that the server will use for all requests.
+ /// If null, [defaultHandler] will be used. Defaults to null.
+ ///
+ /// `customDevToolsPath` is a path to a directory containing a pre-built
+ /// DevTools application.
+ ///
+ // Note: this method is used by the Dart CLI and by package:dwds.
+ Future<HttpServer?> serveDevTools({
+ bool enableStdinCommands = true,
+ bool machineMode = false,
+ bool debugMode = false,
+ bool launchBrowser = false,
+ bool enableNotifications = false,
+ bool allowEmbedding = false,
+ bool headlessMode = false,
+ bool verboseMode = false,
+ String? hostname,
+ String? customDevToolsPath,
+ int port = 0,
+ int numPortsToTry = defaultTryPorts,
+ shelf.Handler? handler,
+ String? serviceProtocolUri,
+ String? profileFilename,
+ String? appSizeBase,
+ String? appSizeTest,
+ }) async {
+ hostname ??= 'localhost';
+
+ // Collect profiling information.
+ if (profileFilename != null && serviceProtocolUri != null) {
+ final Uri? vmServiceUri = Uri.tryParse(serviceProtocolUri);
+ if (vmServiceUri != null) {
+ await _hookupMemoryProfiling(
+ vmServiceUri,
+ profileFilename,
+ verboseMode,
+ );
+ }
+ return null;
+ }
+
+ if (machineMode) {
+ assert(
+ enableStdinCommands,
+ 'machineMode only works with enableStdinCommands.',
+ );
+ }
+
+ clientManager = ClientManager(
+ requestNotificationPermissions: enableNotifications,
+ );
+ handler ??= await defaultHandler(
+ buildDir: customDevToolsPath!,
+ clientManager: clientManager,
+ );
+
+ HttpServer? server;
+ SocketException? ex;
+ while (server == null && numPortsToTry >= 0) {
+ // If we have tried [numPortsToTry] ports and still have not been able to
+ // connect, try port 0 to find a random available port.
+ if (numPortsToTry == 0) port = 0;
+
+ try {
+ server = await HttpMultiServer.bind(hostname, port);
+ } on SocketException catch (e) {
+ ex = e;
+ numPortsToTry--;
+ port++;
+ }
+ }
+
+ // Re-throw the last exception if we failed to bind.
+ if (server == null && ex != null) {
+ throw ex;
+ }
+
+ final _server = server!;
+ if (allowEmbedding) {
+ _server.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN');
+ }
+
+ // Ensure browsers don't cache older versions of the app.
+ _server.defaultResponseHeaders.add(
+ HttpHeaders.cacheControlHeader,
+ 'max-age=900',
+ );
+
+ // Serve requests in an error zone to prevent failures
+ // when running from another error zone.
+ runZonedGuarded(
+ () => shelf.serveRequests(_server, handler!),
+ (e, _) => print('Error serving requests: $e'),
+ );
+
+ final devToolsUrl = 'http://${_server.address.host}:${_server.port}';
+
+ if (launchBrowser) {
+ if (serviceProtocolUri != null) {
+ serviceProtocolUri =
+ _normalizeVmServiceUri(serviceProtocolUri).toString();
+ }
+
+ final queryParameters = {
+ if (serviceProtocolUri != null) 'uri': serviceProtocolUri,
+ if (appSizeBase != null) 'appSizeBase': appSizeBase,
+ if (appSizeTest != null) 'appSizeTest': appSizeTest,
+ };
+ String url = Uri.parse(devToolsUrl)
+ .replace(queryParameters: queryParameters)
+ .toString();
+
+ // If app size parameters are present, open to the standalone `appsize`
+ // page, regardless if there is a vm service uri specified. We only check
+ // for the presence of [appSizeBase] here because [appSizeTest] may or may
+ // not be specified (it should only be present for diffs). If [appSizeTest]
+ // is present without [appSizeBase], we will ignore the parameter.
+ if (appSizeBase != null) {
+ final startQueryParamIndex = url.indexOf('?');
+ if (startQueryParamIndex != -1) {
+ url = '${url.substring(0, startQueryParamIndex)}'
+ '/#/appsize'
+ '${url.substring(startQueryParamIndex)}';
+ }
+ }
+
+ try {
+ await Chrome.start([url]);
+ } catch (e) {
+ print('Unable to launch Chrome: $e\n');
+ }
+ }
+
+ if (enableStdinCommands) {
+ String message = '''Serving DevTools at $devToolsUrl.
+
+ Hit ctrl-c to terminate the server.''';
+ if (!machineMode && debugMode) {
+ // Add bold to help find the correct url to open.
+ message = ConsoleUtils.bold('$message\n');
+ }
+
+ DevToolsUtils.printOutput(
+ message,
+ {
+ 'event': 'server.started',
+ // TODO(dantup): Remove this `method` field when we're sure VS Code
+ // users are all on a newer version that uses `event`. We incorrectly
+ // used `method` for the original releases.
+ 'method': 'server.started',
+ 'params': {
+ 'host': _server.address.host,
+ 'port': _server.port,
+ 'pid': pid,
+ 'protocolVersion': protocolVersion,
+ }
+ },
+ machineMode: machineMode,
+ );
+
+ if (machineMode) {
+ _machineModeCommandHandler = MachineModeCommandHandler(server: this);
+ await _machineModeCommandHandler!.initialize(
+ devToolsUrl: devToolsUrl,
+ headlessMode: headlessMode,
+ );
+ }
+ }
+
+ return server;
+ }
+
+ Future<Map<String, dynamic>> launchDevTools(
+ Map<String, dynamic> params,
+ Uri vmServiceUri,
+ String devToolsUrl,
+ bool headlessMode,
+ bool machineMode) async {
+ // First see if we have an existing DevTools client open that we can
+ // reuse.
+ final canReuse =
+ params.containsKey('reuseWindows') && params['reuseWindows'] == true;
+ final shouldNotify =
+ params.containsKey('notify') && params['notify'] == true;
+ final page = params['page'];
+ if (canReuse &&
+ _tryReuseExistingDevToolsInstance(
+ vmServiceUri,
+ page,
+ shouldNotify,
+ )) {
+ _emitLaunchEvent(
+ reused: true,
+ notified: shouldNotify,
+ pid: null,
+ machineMode: machineMode);
+ return {
+ 'reused': true,
+ 'notified': shouldNotify,
+ };
+ }
+
+ final uriParams = <String, dynamic>{};
+
+ // Copy over queryParams passed by the client
+ params['queryParams']?.forEach((key, value) => uriParams[key] = value);
+
+ // Add the URI to the VM service
+ uriParams['uri'] = vmServiceUri.toString();
+
+ final devToolsUri = Uri.parse(devToolsUrl);
+ final uriToLaunch = _buildUriToLaunch(uriParams, page, devToolsUri);
+
+ // TODO(dantup): When ChromeOS has support for tunneling all ports we can
+ // change this to always use the native browser for ChromeOS and may wish to
+ // handle this inside `browser_launcher`; https://crbug.com/848063.
+ final useNativeBrowser = _isChromeOS &&
+ _isAccessibleToChromeOSNativeBrowser(devToolsUri) &&
+ _isAccessibleToChromeOSNativeBrowser(vmServiceUri);
+ int? browserPid;
+ if (useNativeBrowser) {
+ await Process.start('x-www-browser', [uriToLaunch.toString()]);
+ } else {
+ final args = headlessMode
+ ? [
+ '--headless',
+ // When running headless, Chrome will quit immediately after loading
+ // the page unless we have the debug port open.
+ '--remote-debugging-port=9223',
+ '--disable-gpu',
+ '--no-sandbox',
+ ]
+ : <String>[];
+ final proc = await Chrome.start([uriToLaunch.toString()], args: args);
+ browserPid = proc.pid;
+ }
+ _emitLaunchEvent(
+ reused: false,
+ notified: false,
+ pid: browserPid!,
+ machineMode: machineMode);
+ return {
+ 'reused': false,
+ 'notified': false,
+ 'pid': browserPid,
+ };
+ }
+
+ Future<void> _hookupMemoryProfiling(
+ Uri observatoryUri,
+ String profileFile, [
+ bool verboseMode = false,
+ ]) async {
+ final service = await DevToolsUtils.connectToVmService(observatoryUri);
+ if (service == null) {
+ return;
+ }
+
+ final memoryProfiler = MemoryProfile(service, profileFile, verboseMode);
+ memoryProfiler.startPolling();
+
+ print('Writing memory profile samples to $profileFile...');
+ }
+
+ bool _tryReuseExistingDevToolsInstance(
+ Uri vmServiceUri,
+ String page,
+ bool notifyUser,
+ ) {
+ // First try to find a client that's already connected to this VM service,
+ // and just send the user a notification for that one.
+ final existingClient =
+ clientManager.findExistingConnectedReusableClient(vmServiceUri);
+ if (existingClient != null) {
+ try {
+ existingClient.showPage(page);
+ if (notifyUser) {
+ existingClient.notify();
+ }
+ return true;
+ } catch (e) {
+ print('Failed to reuse existing connected DevTools client');
+ print(e);
+ }
+ }
+
+ final reusableClient = clientManager.findReusableClient();
+ if (reusableClient != null) {
+ try {
+ reusableClient.connectToVmService(vmServiceUri, notifyUser);
+ return true;
+ } catch (e) {
+ print('Failed to reuse existing DevTools client');
+ print(e);
+ }
+ }
+ return false;
+ }
+
+ String _buildUriToLaunch(
+ Map<String, dynamic> uriParams,
+ page,
+ Uri devToolsUri,
+ ) {
+ final queryStringNameValues = [];
+ uriParams.forEach((key, value) => queryStringNameValues.add(
+ '${Uri.encodeQueryComponent(key)}=${Uri.encodeQueryComponent(value)}'));
+
+ if (page != null) {
+ queryStringNameValues.add('page=${Uri.encodeQueryComponent(page)}');
+ }
+
+ return devToolsUri
+ .replace(
+ path: '${devToolsUri.path.isEmpty ? '/' : devToolsUri.path}',
+ fragment: '?${queryStringNameValues.join('&')}')
+ .toString();
+ }
+
+ /// Prints a launch event to stdout so consumers of the DevTools server
+ /// can see when clients are being launched/reused.
+ void _emitLaunchEvent(
+ {required bool reused,
+ required bool notified,
+ required int? pid,
+ required bool machineMode}) {
+ DevToolsUtils.printOutput(
+ null,
+ {
+ 'event': 'client.launch',
+ 'params': {
+ 'reused': reused,
+ 'notified': notified,
+ 'pid': pid,
+ },
+ },
+ machineMode: machineMode,
+ );
+ }
+
+ bool _isAccessibleToChromeOSNativeBrowser(Uri uri) {
+ const tunneledPorts = {
+ 8000,
+ 8008,
+ 8080,
+ 8085,
+ 8888,
+ 9005,
+ 3000,
+ 4200,
+ 5000
+ };
+ return uri.hasPort && tunneledPorts.contains(uri.port);
+ }
+
+ // TODO(https://github.com/flutter/devtools/issues/3571): move to devtools_shared.
+ // Note: please keep this copy of normalizeVmServiceUri() in sync with the one
+ // in devtools_app.
+ Uri? _normalizeVmServiceUri(String value) {
+ value = value.trim();
+
+ // Cleanup encoded urls likely copied from the uri of an existing running
+ // DevTools app.
+ if (value.contains('%3A%2F%2F')) {
+ value = Uri.decodeFull(value);
+ }
+ final uri = Uri.parse(value.trim()).removeFragment();
+ if (!uri.isAbsolute) {
+ return null;
+ }
+ if (uri.path.endsWith('/')) return uri;
+ return uri.replace(path: uri.path);
+ }
+}
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 7de1b71..1d76900 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -24,7 +24,7 @@
import 'client.dart';
import 'client_manager.dart';
import 'constants.dart';
-import 'devtools/devtools_handler.dart';
+import 'devtools/handler.dart';
import 'expression_evaluator.dart';
import 'isolate_manager.dart';
import 'stream_manager.dart';
@@ -297,7 +297,7 @@
// the VM service.
final String buildDir =
_devToolsConfiguration!.customBuildDirectoryPath.toFilePath();
- return devtoolsHandler(
+ return defaultHandler(
dds: this,
buildDir: buildDir,
notFoundHandler: proxyHandler(remoteVmServiceUri),
diff --git a/pkg/dds/lib/src/devtools/client.dart b/pkg/dds/lib/src/devtools/client.dart
new file mode 100644
index 0000000..af82b55
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/client.dart
@@ -0,0 +1,220 @@
+// Copyright (c) 2022, 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 'package:collection/collection.dart';
+import 'package:devtools_shared/devtools_server.dart';
+import 'package:json_rpc_2/src/peer.dart' as json_rpc;
+import 'package:meta/meta.dart';
+import 'package:sse/src/server/sse_handler.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+class LoggingMiddlewareSink<S> implements StreamSink<S> {
+ LoggingMiddlewareSink(this.sink);
+
+ @override
+ void add(S event) {
+ print('DevTools SSE response: $event');
+ sink.add(event);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ print('DevTools SSE error response: $error');
+ sink.addError(error);
+ }
+
+ @override
+ Future addStream(Stream<S> stream) {
+ return sink.addStream(stream);
+ }
+
+ @override
+ Future close() => sink.close();
+
+ @override
+ Future get done => sink.done;
+
+ final StreamSink sink;
+}
+
+/// A connection between a DevTools front-end app and the DevTools server.
+///
+/// see `packages/devtools_app/lib/src/server_connection.dart`.
+class ClientManager {
+ ClientManager({required this.requestNotificationPermissions});
+
+ /// Whether to immediately request notification permissions when a client connects.
+ /// Otherwise permission will be requested only with the first notification.
+ final bool requestNotificationPermissions;
+ final List<DevToolsClient> _clients = [];
+
+ void acceptClient(SseConnection connection, {bool enableLogging = false}) {
+ final client = DevToolsClient.fromSSEConnection(
+ connection,
+ enableLogging,
+ );
+ if (requestNotificationPermissions) {
+ client.enableNotifications();
+ }
+ _clients.add(client);
+ connection.sink.done.then((_) => _clients.remove(client));
+ }
+
+ /// Finds an active DevTools instance that is not already connecting to
+ /// a VM service that we can reuse (for example if a user stopped debugging
+ /// and it disconnected, then started debugging again, we want to reuse
+ /// the open DevTools window).
+ DevToolsClient? findReusableClient() {
+ return _clients.firstWhereOrNull(
+ (c) => !c.hasConnection && !c.embedded,
+ );
+ }
+
+ /// Finds a client that may already be connected to this VM Service.
+ DevToolsClient? findExistingConnectedReusableClient(Uri vmServiceUri) {
+ // Checking the whole URI will fail if DevTools converted it from HTTP to
+ // WS, so just check the host, port and first segment of path (token).
+ return _clients.firstWhereOrNull(
+ (c) =>
+ c.hasConnection &&
+ !c.embedded &&
+ _areSameVmServices(c.vmServiceUri!, vmServiceUri),
+ );
+ }
+
+ @override
+ String toString() {
+ return _clients.map((c) {
+ return '${c.hasConnection.toString().padRight(5)} '
+ '${c.currentPage?.padRight(12)} ${c.vmServiceUri.toString()}';
+ }).join('\n');
+ }
+
+ Map<String, dynamic> toJson(dynamic id) => {
+ 'id': id,
+ 'result': {
+ 'clients': _clients.map((e) => e.toJson()).toList(),
+ }
+ };
+
+ bool _areSameVmServices(Uri uri1, Uri uri2) {
+ return uri1.host == uri2.host &&
+ uri1.port == uri2.port &&
+ uri1.pathSegments.isNotEmpty &&
+ uri2.pathSegments.isNotEmpty &&
+ uri1.pathSegments[0] == uri2.pathSegments[0];
+ }
+}
+
+/// Represents a DevTools client connection to the DevTools server API.
+class DevToolsClient {
+ factory DevToolsClient.fromSSEConnection(
+ SseConnection sse,
+ bool loggingEnabled,
+ ) {
+ Stream<String> stream = sse.stream;
+ StreamSink sink = sse.sink;
+ return DevToolsClient(
+ stream: stream,
+ sink: sink,
+ loggingEnabled: loggingEnabled,
+ );
+ }
+
+ @visibleForTesting
+ DevToolsClient({
+ required Stream<String> stream,
+ required StreamSink sink,
+ bool loggingEnabled = false,
+ }) {
+ if (loggingEnabled) {
+ stream = stream.map<String>((String e) {
+ print('DevTools SSE request: $e');
+ return e;
+ });
+ sink = LoggingMiddlewareSink(sink);
+ }
+
+ _devToolsPeer = json_rpc.Peer(
+ StreamChannel(stream, sink as StreamSink<String>),
+ strictProtocolChecks: false,
+ );
+ _registerJsonRpcMethods();
+ _devToolsPeer.listen();
+ }
+
+ void _registerJsonRpcMethods() {
+ _devToolsPeer.registerMethod('connected', (parameters) {
+ _vmServiceUri = Uri.parse(parameters['uri'].asString);
+ });
+
+ _devToolsPeer.registerMethod('disconnected', (parameters) {
+ _vmServiceUri = null;
+ });
+
+ _devToolsPeer.registerMethod('currentPage', (parameters) {
+ _currentPage = parameters['id'].asString;
+ _embedded = parameters['embedded'].asBool;
+ });
+
+ _devToolsPeer.registerMethod('getPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = ServerApi.devToolsPreferences.properties[key];
+ return value;
+ });
+
+ _devToolsPeer.registerMethod('setPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = parameters['value'].value;
+ ServerApi.devToolsPreferences.properties[key] = value;
+ });
+ }
+
+ /// Notify the DevTools client to connect to a specific VM service instance.
+ void connectToVmService(Uri uri, bool notifyUser) {
+ _devToolsPeer.sendNotification('connectToVm', {
+ 'uri': uri.toString(),
+ 'notify': notifyUser,
+ });
+ }
+
+ void notify() => _devToolsPeer.sendNotification('notify');
+
+ /// Enable notifications to the user from this DevTools client.
+ void enableNotifications() =>
+ _devToolsPeer.sendNotification('enableNotifications');
+
+ /// Notify the DevTools client to show a specific page.
+ void showPage(String pageId) {
+ _devToolsPeer.sendNotification('showPage', {
+ 'page': pageId,
+ });
+ }
+
+ Map<String, dynamic> toJson() => {
+ 'hasConnection': hasConnection,
+ 'currentPage': currentPage,
+ 'embedded': embedded,
+ 'vmServiceUri': vmServiceUri?.toString(),
+ };
+
+ /// The current DevTools page displayed by this client.
+ String? get currentPage => _currentPage;
+ String? _currentPage;
+
+ /// Returns true if this DevTools client is embedded.
+ bool get embedded => _embedded;
+ bool _embedded = false;
+
+ /// Returns the VM service URI that the DevTools client is currently
+ /// connected to. Returns null if the client is not connected to a process.
+ Uri? get vmServiceUri => _vmServiceUri;
+ Uri? _vmServiceUri;
+
+ bool get hasConnection => _vmServiceUri != null;
+
+ late json_rpc.Peer _devToolsPeer;
+}
diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart
deleted file mode 100644
index e5ec54e..0000000
--- a/pkg/dds/lib/src/devtools/devtools_client.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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 'package:devtools_shared/devtools_server.dart';
-import 'package:json_rpc_2/src/server.dart' as json_rpc;
-import 'package:sse/src/server/sse_handler.dart';
-import 'package:stream_channel/stream_channel.dart';
-
-class LoggingMiddlewareSink<S> implements StreamSink<S> {
- LoggingMiddlewareSink(this.sink);
-
- @override
- void add(S event) {
- print('DevTools SSE response: $event');
- sink.add(event);
- }
-
- @override
- void addError(Object error, [StackTrace? stackTrace]) {
- print('DevTools SSE error response: $error');
- sink.addError(error);
- }
-
- @override
- Future addStream(Stream<S> stream) {
- return sink.addStream(stream);
- }
-
- @override
- Future close() => sink.close();
-
- @override
- Future get done => sink.done;
-
- final StreamSink sink;
-}
-
-/// Represents a DevTools client connection to the DevTools server API.
-class DevToolsClient {
- DevToolsClient.fromSSEConnection(
- SseConnection sse,
- bool loggingEnabled,
- ) {
- Stream<String> stream = sse.stream;
- StreamSink sink = sse.sink;
-
- if (loggingEnabled) {
- stream = stream.map<String>((String e) {
- print('DevTools SSE request: $e');
- return e;
- });
- sink = LoggingMiddlewareSink(sink);
- }
-
- _server = json_rpc.Server(
- StreamChannel(stream, sink as StreamSink<String>),
- strictProtocolChecks: false,
- );
- _registerJsonRpcMethods();
- _server.listen();
- }
-
- void _registerJsonRpcMethods() {
- _server.registerMethod('connected', (parameters) {
- // Nothing to do here.
- });
-
- _server.registerMethod('currentPage', (parameters) {
- // Nothing to do here.
- });
-
- _server.registerMethod('disconnected', (parameters) {
- // Nothing to do here.
- });
-
- _server.registerMethod('getPreferenceValue', (parameters) {
- final key = parameters['key'].asString;
- final value = ServerApi.devToolsPreferences.properties[key];
- return value;
- });
-
- _server.registerMethod('setPreferenceValue', (parameters) {
- final key = parameters['key'].asString;
- final value = parameters['value'].value;
- ServerApi.devToolsPreferences.properties[key] = value;
- });
- }
-
- late json_rpc.Server _server;
-}
diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/handler.dart
similarity index 69%
rename from pkg/dds/lib/src/devtools/devtools_handler.dart
rename to pkg/dds/lib/src/devtools/handler.dart
index ad7067d..d323f9d 100644
--- a/pkg/dds/lib/src/devtools/devtools_handler.dart
+++ b/pkg/dds/lib/src/devtools/handler.dart
@@ -11,19 +11,23 @@
import '../constants.dart';
import '../dds_impl.dart';
-import 'devtools_client.dart';
+import 'client.dart';
/// Returns a [Handler] which handles serving DevTools and the DevTools server
-/// API under $DDS_URI/devtools/.
+/// API.
///
/// [buildDir] is the path to the pre-compiled DevTools instance to be served.
///
/// [notFoundHandler] is a [Handler] to which requests that could not be handled
/// by the DevTools handler are forwarded (e.g., a proxy to the VM service).
-FutureOr<Handler> devtoolsHandler({
- required DartDevelopmentServiceImpl dds,
+///
+/// If [dds] is null, DevTools is not being served by a DDS instance and is
+/// served by a standalone server (see `package:dds/devtools_server.dart`).
+FutureOr<Handler> defaultHandler({
+ DartDevelopmentServiceImpl? dds,
required String buildDir,
- required Handler notFoundHandler,
+ ClientManager? clientManager,
+ Handler? notFoundHandler,
}) {
// Serves the web assets for DevTools.
final devtoolsAssetHandler = createStaticHandler(
@@ -35,18 +39,20 @@
// Note: the handler path needs to match the full *original* path, not the
// current request URL (we remove '/devtools' in the initial router but we
// need to include it here).
- const devToolsSseHandlerPath = '/devtools/api/sse';
+ final devToolsSseHandlerPath = dds != null ? '/devtools/api/sse' : '/api/sse';
final devToolsApiHandler = SseHandler(
- dds.authCodesEnabled
- ? Uri.parse('/${dds.authCode}$devToolsSseHandlerPath')
+ (dds?.authCodesEnabled ?? false)
+ ? Uri.parse('/${dds!.authCode}$devToolsSseHandlerPath')
: Uri.parse(devToolsSseHandlerPath),
keepAlive: sseKeepAlive,
);
+ clientManager ??= ClientManager(requestNotificationPermissions: false);
+
devToolsApiHandler.connections.rest.listen(
- (sseConnection) => DevToolsClient.fromSSEConnection(
+ (sseConnection) => clientManager!.acceptClient(
sseConnection,
- dds.shouldLogRequests,
+ enableLogging: dds?.shouldLogRequests ?? false,
),
);
@@ -73,12 +79,14 @@
};
return (request) {
- final pathSegments = request.url.pathSegments;
- if (pathSegments.isEmpty || pathSegments.first != 'devtools') {
- return notFoundHandler(request);
+ if (notFoundHandler != null) {
+ final pathSegments = request.url.pathSegments;
+ if (pathSegments.isEmpty || pathSegments.first != 'devtools') {
+ return notFoundHandler(request);
+ }
+ // Forward all requests to /devtools/* to the DevTools handler.
+ request = request.change(path: 'devtools');
}
- // Forward all requests to /devtools/* to the DevTools handler.
- request = request.change(path: 'devtools');
return devtoolsHandler(request);
};
}
diff --git a/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
new file mode 100644
index 0000000..5b65d4c
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
@@ -0,0 +1,429 @@
+// Copyright (c) 2022, 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.
+
+// TODO(https://github.com/dart-lang/sdk/issues/48161): investigate whether or
+// not machine mode is still relevant.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_server.dart';
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:vm_service/vm_service.dart';
+
+import '../../devtools_server.dart';
+import 'utils.dart';
+
+class MachineModeCommandHandler {
+ static const launchDevToolsService = 'launchDevTools';
+ static const copyAndCreateDevToolsFile = 'copyAndCreateDevToolsFile';
+ static const restoreDevToolsFile = 'restoreDevToolsFiles';
+ static const errorLaunchingBrowserCode = 500;
+ static const bool machineMode = true;
+
+ MachineModeCommandHandler({required this.server});
+
+ /// The server instance associated with this client.
+ final DevToolsServer server;
+
+ // Only used for testing DevToolsUsage (used by survey).
+ DevToolsUsage? _devToolsUsage;
+
+ File? _devToolsBackup;
+
+ static bool _isValidVmServiceUri(Uri? uri) {
+ // Lots of things are considered valid URIs (including empty strings and
+ // single letters) since they can be relative, so we need to do some extra
+ // checks.
+ return uri != null &&
+ uri.isAbsolute &&
+ (uri.isScheme('ws') ||
+ uri.isScheme('wss') ||
+ uri.isScheme('http') ||
+ uri.isScheme('https'));
+ }
+
+ Future<void> initialize({
+ required String devToolsUrl,
+ required bool headlessMode,
+ }) async {
+ final Stream<Map<String, dynamic>> _stdinCommandStream = stdin
+ .transform<String>(utf8.decoder)
+ .transform<String>(const LineSplitter())
+ .where((String line) => line.startsWith('{') && line.endsWith('}'))
+ .map<Map<String, dynamic>>((String line) {
+ return json.decode(line) as Map<String, dynamic>;
+ });
+
+ // Example input:
+ // {
+ // "id":0,
+ // "method":"vm.register",
+ // "params":{
+ // "uri":"<vm-service-uri-here>",
+ // }
+ // }
+ _stdinCommandStream.listen((Map<String, dynamic> json) async {
+ // ID can be String, int or null
+ final dynamic id = json['id'];
+ final Map<String, dynamic> params = json['params'];
+ final method = json['method'];
+ switch (method) {
+ case 'vm.register':
+ await _handleVmRegister(id, params, headlessMode, devToolsUrl);
+ break;
+ case 'devTools.launch':
+ await _handleDevToolsLaunch(id, params, headlessMode, devToolsUrl);
+ break;
+ case 'client.list':
+ _handleClientsList(id, params);
+ break;
+ case 'devTools.survey':
+ _handleDevToolsSurvey(id, params);
+ break;
+ default:
+ DevToolsUtils.printOutput(
+ 'Unknown method $method',
+ {
+ 'id': id,
+ 'error': 'Unknown method $method',
+ },
+ machineMode: machineMode,
+ );
+ }
+ });
+ }
+
+ Future<void> _handleVmRegister(
+ dynamic id,
+ Map<String, dynamic> params,
+ bool headlessMode,
+ String devToolsUrl,
+ ) async {
+ if (!params.containsKey('uri')) {
+ DevToolsUtils.printOutput(
+ 'Invalid input: $params does not contain the key \'uri\'',
+ {
+ 'id': id,
+ 'error': 'Invalid input: $params does not contain the key \'uri\'',
+ },
+ machineMode: machineMode,
+ );
+ }
+
+ // params['uri'] should contain a vm service uri.
+ final uri = Uri.tryParse(params['uri']);
+
+ if (_isValidVmServiceUri(uri)) {
+ await registerLaunchDevToolsService(
+ uri!,
+ id,
+ devToolsUrl,
+ headlessMode,
+ );
+ } else {
+ DevToolsUtils.printOutput(
+ 'Uri must be absolute with a http, https, ws or wss scheme',
+ {
+ 'id': id,
+ 'error': 'Uri must be absolute with a http, https, ws or wss scheme',
+ },
+ machineMode: machineMode,
+ );
+ }
+ }
+
+ Future<void> _handleDevToolsLaunch(
+ dynamic id,
+ Map<String, dynamic> params,
+ bool headlessMode,
+ String devToolsUrl,
+ ) async {
+ if (!params.containsKey('vmServiceUri')) {
+ DevToolsUtils.printOutput(
+ "Invalid input: $params does not contain the key 'vmServiceUri'",
+ {
+ 'id': id,
+ 'error':
+ "Invalid input: $params does not contain the key e'vmServiceUri'",
+ },
+ machineMode: machineMode,
+ );
+ }
+
+ // params['vmServiceUri'] should contain a vm service uri.
+ final vmServiceUri = Uri.tryParse(params['vmServiceUri'])!;
+
+ if (_isValidVmServiceUri(vmServiceUri)) {
+ try {
+ final result = await server.launchDevTools(
+ params,
+ vmServiceUri,
+ devToolsUrl,
+ headlessMode,
+ machineMode,
+ );
+ DevToolsUtils.printOutput(
+ 'DevTools launched',
+ {'id': id, 'result': result},
+ machineMode: machineMode,
+ );
+ } catch (e, s) {
+ DevToolsUtils.printOutput(
+ 'Failed to launch browser: $e\n$s',
+ {'id': id, 'error': 'Failed to launch browser: $e\n$s'},
+ machineMode: machineMode,
+ );
+ }
+ } else {
+ DevToolsUtils.printOutput(
+ 'VM Service URI must be absolute with a http, https, ws or wss scheme',
+ {
+ 'id': id,
+ 'error':
+ 'VM Service Uri must be absolute with a http, https, ws or wss scheme',
+ },
+ machineMode: machineMode,
+ );
+ }
+ }
+
+ void _handleClientsList(dynamic id, Map<String, dynamic> params) {
+ DevToolsUtils.printOutput(
+ server.clientManager.toString(),
+ server.clientManager.toJson(id),
+ machineMode: machineMode,
+ );
+ }
+
+ void _handleDevToolsSurvey(dynamic id, Map<String, dynamic> params) {
+ _devToolsUsage ??= DevToolsUsage();
+ final String surveyRequest = params['surveyRequest'];
+ final String value = params['value'];
+
+ switch (surveyRequest) {
+ case copyAndCreateDevToolsFile:
+ // Backup and delete ~/.devtools file.
+ if (backupAndCreateDevToolsStore()) {
+ _devToolsUsage = null;
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ // TODO(bkonyi): fix incorrect spelling of "success" here and
+ // below once we figure out the impact of changing this key.
+ 'success': true,
+ },
+ },
+ machineMode: machineMode,
+ );
+ }
+ break;
+ case restoreDevToolsFile:
+ _devToolsUsage = null;
+ final content = restoreDevToolsStore();
+ if (content != null) {
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'success': true,
+ 'content': content,
+ },
+ },
+ machineMode: machineMode,
+ );
+
+ _devToolsUsage = null;
+ }
+ break;
+ case apiSetActiveSurvey:
+ _devToolsUsage!.activeSurvey = value;
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'success': _devToolsUsage!.activeSurvey == value,
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ },
+ },
+ machineMode: machineMode,
+ );
+ break;
+ case apiGetSurveyActionTaken:
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ 'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
+ },
+ },
+ machineMode: machineMode,
+ );
+ break;
+ case apiSetSurveyActionTaken:
+ _devToolsUsage!.surveyActionTaken = jsonDecode(value);
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ 'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
+ },
+ },
+ machineMode: machineMode,
+ );
+ break;
+ case apiGetSurveyShownCount:
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ 'surveyShownCount': _devToolsUsage!.surveyShownCount,
+ },
+ },
+ machineMode: machineMode,
+ );
+ break;
+ case apiIncrementSurveyShownCount:
+ _devToolsUsage!.incrementSurveyShownCount();
+ DevToolsUtils.printOutput(
+ 'DevTools Survey',
+ {
+ 'id': id,
+ 'result': {
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ 'surveyShownCount': _devToolsUsage!.surveyShownCount,
+ },
+ },
+ machineMode: machineMode,
+ );
+ break;
+ default:
+ DevToolsUtils.printOutput(
+ 'Unknown DevTools Survey Request $surveyRequest',
+ {
+ 'id': id,
+ 'result': {
+ 'activeSurvey': _devToolsUsage!.activeSurvey,
+ 'surveyActionTaken': _devToolsUsage!.surveyActionTaken,
+ 'surveyShownCount': _devToolsUsage!.surveyShownCount,
+ },
+ },
+ machineMode: machineMode,
+ );
+ }
+ }
+
+ bool backupAndCreateDevToolsStore() {
+ assert(_devToolsBackup == null);
+ final devToolsStore = File(
+ LocalFileSystem.devToolsStoreLocation(),
+ );
+ if (devToolsStore.existsSync()) {
+ _devToolsBackup = devToolsStore
+ .copySync('${LocalFileSystem.devToolsDir()}/.devtools_backup_test');
+ devToolsStore.deleteSync();
+ }
+ return true;
+ }
+
+ String? restoreDevToolsStore() {
+ if (_devToolsBackup != null) {
+ // Read the current ~/.devtools file
+ LocalFileSystem.maybeMoveLegacyDevToolsStore();
+
+ final devToolsStore = File(LocalFileSystem.devToolsStoreLocation());
+ final content = devToolsStore.readAsStringSync();
+
+ // Delete the temporary ~/.devtools file
+ devToolsStore.deleteSync();
+ if (_devToolsBackup!.existsSync()) {
+ // Restore the backup ~/.devtools file we created in
+ // backupAndCreateDevToolsStore.
+ _devToolsBackup!.copySync(LocalFileSystem.devToolsStoreLocation());
+ _devToolsBackup!.deleteSync();
+ _devToolsBackup = null;
+ }
+ return content;
+ }
+ return null;
+ }
+
+ Future<void> registerLaunchDevToolsService(
+ Uri vmServiceUri,
+ dynamic id,
+ String devToolsUrl,
+ bool headlessMode,
+ ) async {
+ try {
+ // Connect to the vm service and register a method to launch DevTools in
+ // chrome.
+ final service = await DevToolsUtils.connectToVmService(vmServiceUri);
+ if (service == null) return;
+
+ service.registerServiceCallback(
+ launchDevToolsService,
+ (params) async {
+ try {
+ await server.launchDevTools(
+ params,
+ vmServiceUri,
+ devToolsUrl,
+ headlessMode,
+ machineMode,
+ );
+ return {
+ 'result': Success().toJson(),
+ };
+ } catch (e, s) {
+ // Note: It's critical that we return responses in exactly the right format
+ // or the VM will unregister the service. The objects must match JSON-RPC
+ // however a successful response must also have a "type" field in its result.
+ // Otherwise, we can return an error object (instead of result) that includes
+ // code + message.
+ return {
+ 'error': {
+ 'code': errorLaunchingBrowserCode,
+ 'message': 'Failed to launch browser: $e\n$s',
+ },
+ };
+ }
+ },
+ );
+
+ final registerServiceMethodName = 'registerService';
+ await service.callMethod(registerServiceMethodName, args: {
+ 'service': launchDevToolsService,
+ 'alias': 'DevTools Server',
+ });
+
+ DevToolsUtils.printOutput(
+ 'Successfully registered launchDevTools service',
+ {
+ 'id': id,
+ 'result': {'success': true},
+ },
+ machineMode: machineMode,
+ );
+ } catch (e) {
+ DevToolsUtils.printOutput(
+ 'Unable to connect to VM service at $vmServiceUri: $e',
+ {
+ 'id': id,
+ 'error': 'Unable to connect to VM service at $vmServiceUri: $e',
+ },
+ machineMode: machineMode,
+ );
+ }
+ }
+}
diff --git a/pkg/dds/lib/src/devtools/memory_profile.dart b/pkg/dds/lib/src/devtools/memory_profile.dart
new file mode 100644
index 0000000..f99caf4
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/memory_profile.dart
@@ -0,0 +1,399 @@
+// Copyright (c) 2022, 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.
+
+// TODO(https://github.com/dart-lang/sdk/issues/48161) investigate whether we
+// can delete this file or not.
+
+import 'dart:async';
+import 'dart:io' as io;
+
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'service_registrations.dart' as service_registrations;
+
+class MemoryProfile {
+ MemoryProfile(this.service, String profileFilename, this._verboseMode) {
+ onConnectionClosed.listen(_handleConnectionStop);
+
+ service!.onEvent('Service').listen(handleServiceEvent);
+
+ _jsonFile = MemoryJsonFile.create(profileFilename);
+
+ _hookUpEvents();
+ }
+
+ late MemoryJsonFile _jsonFile;
+
+ final bool _verboseMode;
+
+ void _hookUpEvents() async {
+ final streamIds = [
+ EventStreams.kExtension,
+ EventStreams.kGC,
+ EventStreams.kIsolate,
+ EventStreams.kLogging,
+ EventStreams.kStderr,
+ EventStreams.kStdout,
+ // TODO(Kenzi): Collect timeline data too.
+ // EventStreams.kTimeline,
+ EventStreams.kVM,
+ EventStreams.kService,
+ ];
+
+ await Future.wait(streamIds.map((String id) async {
+ try {
+ await service!.streamListen(id);
+ } catch (e) {
+ if (id.endsWith('Logging')) {
+ // Don't complain about '_Logging' or 'Logging' events (new VMs don't
+ // have the private names, and older ones don't have the public ones).
+ } else {
+ print("Service client stream not supported: '$id'\n $e");
+ }
+ }
+ }));
+ }
+
+ bool get hasConnection => service != null;
+
+ void handleServiceEvent(Event e) {
+ if (e.kind == EventKind.kServiceRegistered) {
+ final serviceName = e.service!;
+ _registeredMethodsForService
+ .putIfAbsent(serviceName, () => [])
+ .add(e.method!);
+ }
+
+ if (e.kind == EventKind.kServiceUnregistered) {
+ final serviceName = e.service!;
+ _registeredMethodsForService.remove(serviceName);
+ }
+ }
+
+ late IsolateRef _selectedIsolate;
+
+ Future<Response?> getAdbMemoryInfo() async {
+ return await callService(
+ service_registrations.flutterMemory.service,
+ isolateId: _selectedIsolate.id,
+ );
+ }
+
+ /// Call a service that is registered by exactly one client.
+ Future<Response?> callService(
+ String name, {
+ String? isolateId,
+ Map<String, dynamic>? args,
+ }) async {
+ final registered = _registeredMethodsForService[name] ?? const [];
+ if (registered.isEmpty) {
+ throw Exception('There are no registered methods for service "$name"');
+ }
+ return service!.callMethod(
+ registered.first,
+ isolateId: isolateId,
+ args: args,
+ );
+ }
+
+ Map<String, List<String>> get registeredMethodsForService =>
+ _registeredMethodsForService;
+ final _registeredMethodsForService = <String, List<String>>{};
+
+ static const Duration updateDelay = Duration(milliseconds: 500);
+
+ VmService? service;
+
+ late Timer _pollingTimer;
+
+ /// Polled VM current RSS.
+ int? processRss;
+
+ final Map<String, List<HeapSpace>> isolateHeaps = <String, List<HeapSpace>>{};
+
+ final List<HeapSample> samples = <HeapSample>[];
+
+ AdbMemoryInfo? adbMemoryInfo;
+
+ late EventSample eventSample;
+
+ RasterCache? rasterCache;
+
+ late int heapMax;
+
+ Stream<void> get onConnectionClosed => _connectionClosedController.stream;
+ final _connectionClosedController = StreamController<void>.broadcast();
+
+ void _handleConnectionStop(dynamic event) {
+ // TODO(terry): Gracefully handle connection loss.
+ }
+
+ // TODO(terry): Investigate moving code from this point through end of class to devtools_shared.
+ void startPolling() {
+ _pollingTimer = Timer(updateDelay, _pollMemory);
+ service!.onGCEvent.listen(_handleGCEvent);
+ }
+
+ void _handleGCEvent(Event event) {
+ //final bool ignore = event.json['reason'] == 'compact';
+ final json = event.json!;
+ final List<HeapSpace> heaps = <HeapSpace>[
+ HeapSpace.parse(json['new'])!,
+ HeapSpace.parse(json['old'])!
+ ];
+ _updateGCEvent(event.isolate!.id!, heaps);
+ // TODO(terry): expose when GC occured as markers in memory timeline.
+ }
+
+ void stopPolling() {
+ _pollingTimer.cancel();
+ service = null;
+ }
+
+ Future<void> _pollMemory() async {
+ final service = this.service!;
+ final VM vm = await service.getVM();
+
+ // TODO(terry): Need to handle a possible Sentinel being returned.
+ final List<Isolate?> isolates =
+ await Future.wait(vm.isolates!.map((IsolateRef ref) async {
+ try {
+ return await service.getIsolate(ref.id!);
+ } catch (e) {
+ // TODO(terry): Seem to sometimes get a sentinel not sure how? VM issue?
+ // Unhandled Exception: type 'Sentinel' is not a subtype of type 'FutureOr<Isolate>'
+ print('Error [MEMORY_PROTOCOL]: $e');
+ return Future<Isolate?>.value();
+ }
+ }));
+
+ // Polls for current Android meminfo using:
+ // > adb shell dumpsys meminfo -d <package_name>
+ final isolate = isolates[0]!;
+ _selectedIsolate = IsolateRef(
+ id: isolate.id,
+ name: isolate.name,
+ number: isolate.number,
+ isSystemIsolate: isolate.isSystemIsolate,
+ );
+
+ if (hasConnection && vm.operatingSystem == 'android') {
+ // Poll ADB meminfo
+ adbMemoryInfo = await _fetchAdbInfo();
+ } else {
+ // TODO(terry): TBD alternative for iOS memory info - all values zero.
+ adbMemoryInfo = AdbMemoryInfo.empty();
+ }
+
+ // Query the engine's rasterCache estimate.
+ rasterCache = await _fetchRasterCacheInfo(_selectedIsolate);
+
+ // TODO(terry): There are no user interactions. However, might be nice to
+ // record VM GC's on the timeline.
+ eventSample = EventSample.empty();
+
+ // Polls for current RSS size.
+ _update(vm, isolates);
+
+ _pollingTimer = Timer(updateDelay, _pollMemory);
+ }
+
+ /// Poll ADB meminfo
+ Future<AdbMemoryInfo?> _fetchAdbInfo() async {
+ final adbMemInfo = await getAdbMemoryInfo();
+ if (adbMemInfo?.json != null) {
+ return AdbMemoryInfo.fromJsonInKB(adbMemInfo!.json!);
+ }
+ return null;
+ }
+
+ /// Poll Fultter engine's Raster Cache metrics.
+ /// @returns engine's rasterCache estimates or null.
+ Future<RasterCache?> _fetchRasterCacheInfo(IsolateRef selectedIsolate) async {
+ final response = await getRasterCacheMetrics(selectedIsolate);
+ return RasterCache.parse(response?.json);
+ }
+
+ /// @returns view id of selected isolate's 'FlutterView'.
+ /// @throws Exception if no 'FlutterView'.
+ Future<String?> getFlutterViewId(IsolateRef selectedIsolate) async {
+ final flutterViewListResponse = await service!.callServiceExtension(
+ service_registrations.flutterListViews,
+ isolateId: selectedIsolate.id,
+ );
+ final List<dynamic> views =
+ flutterViewListResponse.json!['views'].cast<Map<String, dynamic>>();
+
+ // Each isolate should only have one FlutterView.
+ final flutterView = views.firstWhere(
+ (view) => view['type'] == 'FlutterView',
+ orElse: () => null,
+ );
+
+ if (flutterView == null) {
+ final message =
+ 'No Flutter Views to query: ${flutterViewListResponse.json}';
+ print('ERROR: $message');
+ throw Exception(message);
+ }
+
+ final String flutterViewId = flutterView['id']!;
+ return flutterViewId;
+ }
+
+ /// Flutter engine returns estimate how much memory is used by layer/picture raster
+ /// cache entries in bytes.
+ ///
+ /// Call to returns JSON payload 'EstimateRasterCacheMemory' with two entries:
+ /// layerBytes - layer raster cache entries in bytes
+ /// pictureBytes - picture raster cache entries in bytes
+ Future<Response?> getRasterCacheMetrics(IsolateRef selectedIsolate) async {
+ final viewId = await getFlutterViewId(selectedIsolate);
+
+ return await service!.callServiceExtension(
+ service_registrations.flutterEngineRasterCache,
+ args: {'viewId': viewId},
+ isolateId: selectedIsolate.id,
+ );
+ }
+
+ void _update(VM vm, List<Isolate?> isolates) {
+ processRss = vm.json!['_currentRSS'];
+
+ isolateHeaps.clear();
+
+ for (Isolate? isolate in isolates) {
+ if (isolate != null) {
+ isolateHeaps[isolate.id!] = getHeaps(isolate);
+ }
+ }
+
+ _recalculate();
+ }
+
+ void _updateGCEvent(String id, List<HeapSpace> heaps) {
+ isolateHeaps[id] = heaps;
+ _recalculate(true);
+ }
+
+ void _recalculate([bool fromGC = false]) {
+ int total = 0;
+
+ int used = 0;
+ int capacity = 0;
+ int external = 0;
+ for (List<HeapSpace> heaps in isolateHeaps.values) {
+ used += heaps.fold<int>(0, (i, heap) => i + heap.used!);
+ capacity += heaps.fold<int>(0, (i, heap) => i + heap.capacity!);
+ external += heaps.fold<int>(0, (i, heap) => i + heap.external!);
+
+ capacity += external;
+
+ total +=
+ heaps.fold<int>(0, (i, heap) => i + heap.capacity! + heap.external!);
+ }
+
+ heapMax = total;
+
+ final time = DateTime.now().millisecondsSinceEpoch;
+ final sample = HeapSample(
+ time,
+ processRss ?? -1,
+ capacity,
+ used,
+ external,
+ fromGC,
+ adbMemoryInfo,
+ eventSample,
+ rasterCache,
+ );
+
+ if (_verboseMode) {
+ final timeCollected = _formatTime(
+ DateTime.fromMillisecondsSinceEpoch(time),
+ );
+
+ print(' Collected Sample: [$timeCollected] capacity=$capacity, '
+ 'ADB MemoryInfo total=${adbMemoryInfo!.total}${fromGC ? ' [GC]' : ''}');
+ }
+
+ _jsonFile.writeSample(sample);
+ }
+
+ static List<HeapSpace> getHeaps(Isolate isolate) {
+ final Map<String, dynamic> heaps = isolate.json!['_heaps'];
+ final heapList = <HeapSpace>[];
+ for (final heapJson in heaps.values) {
+ final heap = HeapSpace.parse(heapJson);
+ if (heap != null) {
+ heapList.add(heap);
+ }
+ }
+ return heapList;
+ }
+
+ static String _formatTime(DateTime value) {
+ String toStringLength(int value, int length) {
+ final result = '$value';
+ assert(length >= result.length);
+ return '0' * (length - result.length) + result;
+ }
+
+ return toStringLength(value.hour, 2) +
+ ':' +
+ toStringLength(value.minute, 2) +
+ ':' +
+ toStringLength(value.second, 2) +
+ '.' +
+ toStringLength(value.millisecond, 3);
+ }
+}
+
+class MemoryJsonFile {
+ MemoryJsonFile.create(this._absoluteFileName) {
+ _open();
+ }
+
+ final String _absoluteFileName;
+ late io.File _fs;
+ late io.RandomAccessFile _raFile;
+ bool _multipleSamples = false;
+
+ void _open() {
+ _fs = io.File(_absoluteFileName);
+ _raFile = _fs.openSync(mode: io.FileMode.writeOnly);
+
+ _populateJsonHeader();
+ }
+
+ void _populateJsonHeader() {
+ final payload = '${SamplesMemoryJson.header}${MemoryJson.trailer}';
+ _raFile.writeStringSync(payload);
+ _raFile.flushSync();
+ }
+
+ void _setPositionToWriteSample() {
+ // Set the file position to the data array field contents - inside of [].
+ final filePosition = _raFile.positionSync();
+ _raFile.setPositionSync(filePosition - MemoryJson.trailer.length);
+ }
+
+ void writeSample(HeapSample sample) {
+ _setPositionToWriteSample();
+
+ String encodedSample;
+ if (_multipleSamples) {
+ encodedSample = SamplesMemoryJson().encodeAnother(sample);
+ } else {
+ encodedSample = SamplesMemoryJson().encode(sample);
+ }
+
+ _raFile.writeStringSync('$encodedSample${MemoryJson.trailer}');
+
+ _raFile.flushSync();
+
+ _multipleSamples = true;
+ }
+}
diff --git a/pkg/dds/lib/src/devtools/service_registrations.dart b/pkg/dds/lib/src/devtools/service_registrations.dart
new file mode 100644
index 0000000..10b35ea
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/service_registrations.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, 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.
+
+// TODO(https://github.com/flutter/devtools/issues/3571): move to devtools_shared.
+class RegisteredServiceDescription {
+ const RegisteredServiceDescription._({
+ required this.service,
+ required this.title,
+ });
+
+ final String service;
+ final String title;
+}
+
+/// Flutter memory service registered by Flutter Tools.
+///
+/// We call this service to get version information about the Flutter Android memory info
+/// using Android's ADB.
+const flutterMemory = RegisteredServiceDescription._(
+ service: 'flutterMemoryInfo',
+ title: 'Flutter Memory Info',
+);
+
+const flutterListViews = '_flutter.listViews';
+
+/// Flutter engine returns estimate how much memory is used by layer/picture raster
+/// cache entries in bytes.
+const flutterEngineRasterCache = '_flutter.estimateRasterCacheMemory';
diff --git a/pkg/dds/lib/src/devtools/utils.dart b/pkg/dds/lib/src/devtools/utils.dart
new file mode 100644
index 0000000..b4c732e
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/utils.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:vm_service/utils.dart';
+import 'package:vm_service/vm_service.dart';
+
+abstract class DevToolsUtils {
+ static void printOutput(
+ String? message,
+ Object json, {
+ required bool machineMode,
+ }) {
+ final output = machineMode ? jsonEncode(json) : message;
+ if (output != null) {
+ print(output);
+ }
+ }
+
+ static Future<VmService?> connectToVmService(Uri theUri) async {
+ // Fix up the various acceptable URI formats into a WebSocket URI to connect.
+ final uri = convertToWebSocketUrl(serviceProtocolUrl: theUri);
+
+ try {
+ final WebSocket ws = await WebSocket.connect(uri.toString());
+
+ final VmService service = VmService(
+ ws.asBroadcastStream(),
+ (String message) => ws.add(message),
+ );
+
+ return service;
+ } catch (_) {
+ print('ERROR: Unable to connect to VMService $theUri');
+ return null;
+ }
+ }
+
+ static Future<String> getVersion(String devToolsDir) async {
+ try {
+ final versionFile = File(path.join(devToolsDir, 'version.json'));
+ final decoded = jsonDecode(await versionFile.readAsString());
+ return decoded['version'] ?? 'unknown';
+ } on FileSystemException {
+ return 'unknown';
+ }
+ }
+}
diff --git a/pkg/dds/lib/src/utils/console.dart b/pkg/dds/lib/src/utils/console.dart
new file mode 100644
index 0000000..10c302b
--- /dev/null
+++ b/pkg/dds/lib/src/utils/console.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.
+
+abstract class ConsoleUtils {
+ /// Make [contents] bold when printed to the terminal.
+ ///
+ /// This is a no-op on Windows.
+ static String bold(String contents) {
+ return '\u001b[1m$contents\u001b[0m';
+ }
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 51e3d8d..22e251c 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 2.1.7
+version: 2.2.0
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -12,9 +12,11 @@
dependencies:
async: ^2.4.1
+ browser_launcher: ^1.0.0
collection: ^1.15.0
dds_service_extensions: ^1.3.0
devtools_shared: ^2.3.0
+ http_multi_server: ^3.0.0
json_rpc_2: ^3.0.0
meta: ^1.1.8
path: ^1.8.0
diff --git a/pkg/dds/test/devtools_server/devtools_client_test.dart b/pkg/dds/test/devtools_server/devtools_client_test.dart
new file mode 100644
index 0000000..4a163b0
--- /dev/null
+++ b/pkg/dds/test/devtools_server/devtools_client_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, 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 'package:dds/src/devtools/client.dart';
+import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late json_rpc.Peer client;
+ late DevToolsClient devToolsClient;
+ setUp(() {
+ final requestController = StreamController<String>();
+ final requestStream = requestController.stream;
+ final requestSink = requestController.sink;
+
+ final responseController = StreamController<String>();
+ final responseStream = responseController.stream;
+ final responseSink = responseController.sink;
+ client = json_rpc.Peer(StreamChannel(responseStream, requestSink));
+ unawaited(client.listen());
+
+ devToolsClient = DevToolsClient(
+ stream: requestStream,
+ sink: responseSink,
+ );
+ });
+
+ test('DevToolsClient API', () async {
+ var response = await client.sendRequest('connected', {
+ 'uri': 'http://127.0.0.1:8181',
+ });
+ expect(response, isNull);
+ expect(devToolsClient.hasConnection, true);
+ expect(devToolsClient.vmServiceUri, Uri.parse('http://127.0.0.1:8181'));
+ expect(devToolsClient.embedded, false);
+ expect(devToolsClient.currentPage, isNull);
+
+ response = await client.sendRequest('disconnected');
+ expect(response, isNull);
+ expect(devToolsClient.hasConnection, false);
+ expect(devToolsClient.vmServiceUri, isNull);
+ expect(devToolsClient.embedded, false);
+ expect(devToolsClient.currentPage, isNull);
+
+ response = await client.sendRequest('currentPage', {
+ 'id': 'foo',
+ 'embedded': true,
+ });
+
+ expect(response, isNull);
+ expect(devToolsClient.hasConnection, false);
+ expect(devToolsClient.vmServiceUri, isNull);
+ expect(devToolsClient.embedded, true);
+ expect(devToolsClient.currentPage, 'foo');
+
+ // TODO: add tests for package:devtools_shared/devtools_server.dart
+ });
+
+ test('DevToolsClient notifications', () async {
+ final enableNotifications = Completer<void>();
+ client.registerMethod(
+ 'enableNotifications',
+ (_) => enableNotifications.complete(),
+ );
+ devToolsClient.enableNotifications();
+ await enableNotifications.future;
+
+ final showPage = Completer<void>();
+ String? pageId;
+ client.registerMethod('showPage', (parameters) {
+ pageId = parameters['page'].asString;
+ showPage.complete();
+ });
+ devToolsClient.showPage('foo');
+ await showPage.future;
+ expect(pageId, 'foo');
+
+ final connectToVmService = Completer<void>();
+ String? uri;
+ bool notifyUser = false;
+ client.registerMethod('connectToVm', (parameters) {
+ uri = parameters['uri'].asString;
+ notifyUser = parameters['notify'].asBool;
+ connectToVmService.complete();
+ });
+ devToolsClient.connectToVmService(Uri.parse('http://127.0.0.1:8181'), true);
+ await connectToVmService.future;
+ expect(uri, 'http://127.0.0.1:8181');
+ expect(notifyUser, true);
+
+ final notify = Completer<void>();
+ client.registerMethod('notify', (_) => notify.complete());
+ devToolsClient.notify();
+ await notify.future;
+ });
+}
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index de5dad0..f57bbdf 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -15,7 +15,6 @@
constructor_tearoffs/call_instantiation: TypeCheckError
constructor_tearoffs/lowering/invalid_redirect: VerificationError
-enhanced_enums/enum_as_supertype: RuntimeError
enhanced_enums/simple_mixins: RuntimeError
extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected.
extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected.
diff --git a/pkg/front_end/testcases/text_serialization.status b/pkg/front_end/testcases/text_serialization.status
index 3630e3a..5a838fa 100644
--- a/pkg/front_end/testcases/text_serialization.status
+++ b/pkg/front_end/testcases/text_serialization.status
@@ -8,7 +8,6 @@
constructor_tearoffs/call_instantiation: TypeCheckError
constructor_tearoffs/lowering/invalid_redirect: VerificationError
-enhanced_enums/enum_as_supertype: RuntimeError
enhanced_enums/simple_mixins: RuntimeError
extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected.
extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected.
diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status
index c71436c..f75594a 100644
--- a/pkg/front_end/testcases/weak.status
+++ b/pkg/front_end/testcases/weak.status
@@ -22,7 +22,6 @@
constructor_tearoffs/call_instantiation: TypeCheckError
constructor_tearoffs/lowering/invalid_redirect: VerificationError
-enhanced_enums/enum_as_supertype: RuntimeError
enhanced_enums/simple_mixins: RuntimeError
extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected.
extension_types/call_not_get: ExpectationFileMismatchSerialized # Expected.
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index 0ac05bf..61c56f6 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -1252,18 +1252,6 @@
DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate);
/**
- * Notifies the VM that the embedder expects |size| bytes of memory have become
- * unreachable. The VM may use this hint to adjust the garbage collector's
- * growth policy.
- *
- * Multiple calls are interpreted as increasing, not replacing, the estimate of
- * unreachable memory.
- *
- * Requires there to be a current isolate.
- */
-DART_EXPORT void Dart_HintFreed(intptr_t size);
-
-/**
* Notifies the VM that the embedder expects to be idle until |deadline|. The VM
* may use this time to perform garbage collection or other tasks to avoid
* delays during execution of Dart code in the future.
diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc
index 9326873..587b538 100644
--- a/runtime/vm/class_finalizer.cc
+++ b/runtime/vm/class_finalizer.cc
@@ -1223,6 +1223,7 @@
void ClassFinalizer::AllocateEnumValues(const Class& enum_cls) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
+ ObjectStore* object_store = thread->isolate_group()->object_store();
const auto& values_field =
Field::Handle(zone, enum_cls.LookupStaticField(Symbols::Values()));
@@ -1237,16 +1238,11 @@
ASSERT(values.IsArray());
}
- // The enum_cls is the actual declared class.
- // The shared super-class holds the fields for index and name.
- const auto& super_cls = Class::Handle(zone, enum_cls.SuperClass());
-
const auto& index_field =
- Field::Handle(zone, super_cls.LookupInstanceField(Symbols::Index()));
+ Field::Handle(zone, object_store->enum_index_field());
ASSERT(!index_field.IsNull());
- const auto& name_field = Field::Handle(
- zone, super_cls.LookupInstanceFieldAllowPrivate(Symbols::_name()));
+ const auto& name_field = Field::Handle(zone, object_store->enum_name_field());
ASSERT(!name_field.IsNull());
const auto& enum_name = String::Handle(zone, enum_cls.ScrubbedName());
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index 4784b36..fccacc2 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -219,11 +219,12 @@
12;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 152;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 108;
+ 160;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 116;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 172;
-static constexpr dart::compiler::target::word ObjectStore_type_type_offset = 96;
+ 180;
+static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
+ 104;
static constexpr dart::compiler::target::word OneByteString_data_offset = 12;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset = 8;
@@ -784,12 +785,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -1355,11 +1356,12 @@
12;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 152;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 108;
+ 160;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 116;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 172;
-static constexpr dart::compiler::target::word ObjectStore_type_type_offset = 96;
+ 180;
+static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
+ 104;
static constexpr dart::compiler::target::word OneByteString_data_offset = 12;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset = 8;
@@ -1917,12 +1919,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -2489,12 +2491,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -3060,12 +3062,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -3628,11 +3630,12 @@
12;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 152;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 108;
+ 160;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 116;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 172;
-static constexpr dart::compiler::target::word ObjectStore_type_type_offset = 96;
+ 180;
+static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
+ 104;
static constexpr dart::compiler::target::word OneByteString_data_offset = 12;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset = 8;
@@ -4187,12 +4190,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -4752,11 +4755,12 @@
12;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 152;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 108;
+ 160;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 116;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 172;
-static constexpr dart::compiler::target::word ObjectStore_type_type_offset = 96;
+ 180;
+static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
+ 104;
static constexpr dart::compiler::target::word OneByteString_data_offset = 12;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset = 8;
@@ -5308,12 +5312,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -5874,12 +5878,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -6439,12 +6443,12 @@
24;
static constexpr dart::compiler::target::word NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word ObjectStore_double_type_offset =
- 304;
-static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 216;
+ 320;
+static constexpr dart::compiler::target::word ObjectStore_int_type_offset = 232;
static constexpr dart::compiler::target::word ObjectStore_string_type_offset =
- 344;
+ 360;
static constexpr dart::compiler::target::word ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word OneByteString_data_offset = 16;
static constexpr dart::compiler::target::word PointerBase_data_field_offset = 8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
@@ -7037,13 +7041,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 152;
+ AOT_ObjectStore_double_type_offset = 160;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 108;
+ 116;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 172;
+ AOT_ObjectStore_string_type_offset = 180;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 96;
+ 104;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
12;
static constexpr dart::compiler::target::word
@@ -7669,13 +7673,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -8307,13 +8311,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -8942,13 +8946,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -9576,13 +9580,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -10206,13 +10210,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 152;
+ AOT_ObjectStore_double_type_offset = 160;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 108;
+ 116;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 172;
+ AOT_ObjectStore_string_type_offset = 180;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 96;
+ 104;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
12;
static constexpr dart::compiler::target::word
@@ -10831,13 +10835,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -11462,13 +11466,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -12090,13 +12094,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
@@ -12717,13 +12721,13 @@
static constexpr dart::compiler::target::word
AOT_NativeArguments_thread_offset = 0;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_double_type_offset = 304;
+ AOT_ObjectStore_double_type_offset = 320;
static constexpr dart::compiler::target::word AOT_ObjectStore_int_type_offset =
- 216;
+ 232;
static constexpr dart::compiler::target::word
- AOT_ObjectStore_string_type_offset = 344;
+ AOT_ObjectStore_string_type_offset = 360;
static constexpr dart::compiler::target::word AOT_ObjectStore_type_type_offset =
- 192;
+ 208;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
16;
static constexpr dart::compiler::target::word
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 8b8468d..7106d5b 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1811,17 +1811,6 @@
return Api::NewHandle(T, I->sticky_error());
}
-DART_EXPORT void Dart_HintFreed(intptr_t size) {
- if (size < 0) {
- FATAL1("%s requires a non-negative size", CURRENT_FUNC);
- }
- Thread* T = Thread::Current();
- CHECK_ISOLATE(T->isolate());
- API_TIMELINE_BEGIN_END(T);
- TransitionNativeToVM transition(T);
- T->heap()->HintFreed(size);
-}
-
DART_EXPORT void Dart_NotifyIdle(int64_t deadline) {
Thread* T = Thread::Current();
CHECK_ISOLATE(T->isolate());
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index c9851a6..5691670 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -9525,39 +9525,6 @@
}
}
-static void HintFreedNative(Dart_NativeArguments args) {
- int64_t size = 0;
- EXPECT_VALID(Dart_GetNativeIntegerArgument(args, 0, &size));
- Dart_HintFreed(size);
-}
-
-static Dart_NativeFunction HintFreed_native_lookup(Dart_Handle name,
- int argument_count,
- bool* auto_setup_scope) {
- return HintFreedNative;
-}
-
-TEST_CASE(DartAPI_HintFreed) {
- const char* kScriptChars = R"(
-@pragma("vm:external-name", "Test_nativeFunc")
-external void hintFreed(int size);
-void main() {
- var v;
- for (var i = 0; i < 100; i++) {
- var t = [];
- for (var j = 0; j < 10000; j++) {
- t.add(List.filled(100, null));
- }
- v = t;
- hintFreed(100 * 10000 * 4);
- }
-})";
- Dart_Handle lib =
- TestCase::LoadTestScript(kScriptChars, &HintFreed_native_lookup);
- Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
- EXPECT_VALID(result);
-}
-
static void NotifyIdleShortNative(Dart_NativeArguments args) {
Dart_NotifyIdle(Dart_TimelineGetMicros() + 10 * kMicrosecondsPerMillisecond);
}
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index fe3956d..b226659 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -356,10 +356,6 @@
return raw_obj;
}
-void Heap::HintFreed(intptr_t size) {
- old_space_.HintFreed(size);
-}
-
void Heap::NotifyIdle(int64_t deadline) {
Thread* thread = Thread::Current();
TIMELINE_FUNCTION_GC_DURATION(thread, "NotifyIdle");
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 2360700..121e075 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -104,7 +104,6 @@
ObjectPtr FindNewObject(FindObjectVisitor* visitor);
ObjectPtr FindObject(FindObjectVisitor* visitor);
- void HintFreed(intptr_t size);
void NotifyIdle(int64_t deadline);
void NotifyLowMemory();
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index f17e8e6..39b1603 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -1692,17 +1692,6 @@
}
}
-void PageSpaceController::HintFreed(intptr_t size) {
- intptr_t size_in_words = size << kWordSizeLog2;
- if (size_in_words > idle_gc_threshold_in_words_) {
- idle_gc_threshold_in_words_ = 0;
- } else {
- idle_gc_threshold_in_words_ -= size_in_words;
- }
-
- // TODO(rmacnak): Hasten the soft threshold at some discount?
-}
-
void PageSpaceGarbageCollectionHistory::AddGarbageCollectionTime(int64_t start,
int64_t end) {
Entry entry;
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index 9d039e1..b8e209b 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -265,7 +265,6 @@
int64_t start,
int64_t end);
void EvaluateAfterLoading(SpaceUsage after);
- void HintFreed(intptr_t size);
void set_last_usage(SpaceUsage current) { last_usage_ = current; }
@@ -359,7 +358,6 @@
void EvaluateAfterLoading() {
page_space_controller_.EvaluateAfterLoading(usage_);
}
- void HintFreed(intptr_t size) { page_space_controller_.HintFreed(size); }
int64_t UsedInWords() const { return usage_.used_in_words; }
int64_t CapacityInWords() const {
diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc
index 9fdbffd..87874b8 100644
--- a/runtime/vm/object_reload.cc
+++ b/runtime/vm/object_reload.cc
@@ -302,10 +302,11 @@
ASSERT(is_finalized());
ASSERT(old_enum.is_finalized());
- Zone* zone = Thread::Current()->zone();
+ Thread* thread = Thread::Current();
+ Zone* zone = thread->zone();
+ ObjectStore* object_store = thread->isolate_group()->object_store();
Field& field = Field::Handle(zone);
- Class& cls = Class::Handle(zone);
String& enum_ident = String::Handle();
Instance& old_enum_value = Instance::Handle(zone);
Instance& enum_value = Instance::Handle(zone);
@@ -338,8 +339,7 @@
old_deleted_enum_sentinel ^= field.StaticConstFieldValue();
ASSERT(!old_deleted_enum_sentinel.IsNull());
- cls = old_enum.SuperClass();
- field = cls.LookupInstanceFieldAllowPrivate(Symbols::_name());
+ field = object_store->enum_name_field();
ASSERT(!field.IsNull());
UnorderedHashMap<EnumMapTraits> enum_map(enum_map_storage.ptr());
@@ -374,8 +374,7 @@
deleted_enum_sentinel ^= field.StaticConstFieldValue();
ASSERT(!deleted_enum_sentinel.IsNull());
- cls = SuperClass();
- field = cls.LookupInstanceFieldAllowPrivate(Symbols::_name());
+ field = object_store->enum_name_field();
ASSERT(!field.IsNull());
UnorderedHashMap<EnumMapTraits> enum_map(enum_map_storage.ptr());
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index b7c1cb3..8ce45e3 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -361,6 +361,8 @@
if (list_class_.load() == Type::null()) {
ASSERT(non_nullable_list_rare_type_.load() == Type::null());
ASSERT(non_nullable_map_rare_type_.load() == Type::null());
+ ASSERT(enum_index_field_.load() == Field::null());
+ ASSERT(enum_name_field_.load() == Field::null());
ASSERT(_object_equals_function_.load() == Function::null());
ASSERT(_object_hash_code_function_.load() == Function::null());
ASSERT(_object_to_string_function_.load() == Function::null());
@@ -382,6 +384,21 @@
type ^= cls.RareType();
non_nullable_map_rare_type_.store(type.ptr());
+ auto& field = Field::Handle(zone);
+
+ cls = core_lib.LookupClassAllowPrivate(Symbols::_Enum());
+ ASSERT(!cls.IsNull());
+ const auto& error = cls.EnsureIsFinalized(thread);
+ ASSERT(error == Error::null());
+
+ field = cls.LookupInstanceField(Symbols::Index());
+ ASSERT(!field.IsNull());
+ enum_index_field_.store(field.ptr());
+
+ field = cls.LookupInstanceFieldAllowPrivate(Symbols::_name());
+ ASSERT(!field.IsNull());
+ enum_name_field_.store(field.ptr());
+
auto& function = Function::Handle(zone);
function = core_lib.LookupFunctionAllowPrivate(Symbols::_objectHashCode());
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 19689f0..3d77c61 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -47,6 +47,8 @@
LAZY_CORE(Class, list_class) \
LAZY_CORE(Type, non_nullable_list_rare_type) \
LAZY_CORE(Type, non_nullable_map_rare_type) \
+ LAZY_CORE(Field, enum_index_field) \
+ LAZY_CORE(Field, enum_name_field) \
LAZY_CORE(Function, _object_equals_function) \
LAZY_CORE(Function, _object_hash_code_function) \
LAZY_CORE(Function, _object_to_string_function) \
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 75bb968..5c8db08 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -293,6 +293,7 @@
V(_DeletedEnumPrefix, "Deleted enum value from ") \
V(_DeletedEnumSentinel, "_deleted_enum_sentinel") \
V(_Double, "_Double") \
+ V(_Enum, "_Enum") \
V(_ExternalFloat32Array, "_ExternalFloat32Array") \
V(_ExternalFloat32x4Array, "_ExternalFloat32x4Array") \
V(_ExternalFloat64Array, "_ExternalFloat64Array") \
diff --git a/tools/VERSION b/tools/VERSION
index bb3a9c1..03fbd69 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 29
+PRERELEASE 30
PRERELEASE_PATCH 0
\ No newline at end of file