Version 2.18.0-33.0.dev

Merge commit 'f76b6a8cc155442fe04f4cc1d78f7b5d1428686c' into 'dev'
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index c248c2f..5538e46f 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -143,6 +143,9 @@
   /// The context used by the execution domain handlers.
   final ExecutionContext executionContext = ExecutionContext();
 
+  /// The next search response id.
+  int nextSearchId = 0;
+
   /// The [Completer] that completes when analysis is complete.
   Completer<void>? _onAnalysisCompleteCompleter;
 
diff --git a/pkg/analysis_server/lib/src/flutter/flutter_domain.dart b/pkg/analysis_server/lib/src/flutter/flutter_domain.dart
index 2338d6e..a57802c 100644
--- a/pkg/analysis_server/lib/src/flutter/flutter_domain.dart
+++ b/pkg/analysis_server/lib/src/flutter/flutter_domain.dart
@@ -4,9 +4,10 @@
 
 import 'package:analysis_server/protocol/protocol_constants.dart';
 import 'package:analysis_server/src/domain_abstract.dart';
-import 'package:analysis_server/src/protocol/protocol_internal.dart';
+import 'package:analysis_server/src/handler/legacy/flutter_get_widget_description.dart';
+import 'package:analysis_server/src/handler/legacy/flutter_set_subscriptions.dart';
+import 'package:analysis_server/src/handler/legacy/flutter_set_widget_property_value.dart';
 import 'package:analysis_server/src/protocol_server.dart';
-import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/src/utilities/cancellation.dart';
 
 /// A [RequestHandler] that handles requests in the `flutter` domain.
@@ -15,118 +16,29 @@
   /// [server].
   FlutterDomainHandler(super.server);
 
-  /// Implement the 'flutter.getWidgetDescription' request.
-  void getWidgetDescription(Request request) async {
-    var params = FlutterGetWidgetDescriptionParams.fromRequest(request);
-    var file = params.file;
-    var offset = params.offset;
-
-    if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
-      return;
-    }
-
-    var resolvedUnit = await server.getResolvedUnit(file);
-    if (resolvedUnit == null) {
-      server.sendResponse(Response.fileNotAnalyzed(request, file));
-      return;
-    }
-
-    var computer = server.flutterWidgetDescriptions;
-
-    FlutterGetWidgetDescriptionResult? result;
-    try {
-      result = await computer.getDescription(
-        resolvedUnit,
-        offset,
-      );
-    } on InconsistentAnalysisException {
-      server.sendResponse(
-        Response(
-          request.id,
-          error: RequestError(
-            RequestErrorCode.FLUTTER_GET_WIDGET_DESCRIPTION_CONTENT_MODIFIED,
-            'Concurrent modification detected.',
-          ),
-        ),
-      );
-      return;
-    }
-
-    if (result == null) {
-      server.sendResponse(
-        Response(
-          request.id,
-          error: RequestError(
-            RequestErrorCode.FLUTTER_GET_WIDGET_DESCRIPTION_NO_WIDGET,
-            'No Flutter widget at the given location.',
-          ),
-        ),
-      );
-      return;
-    }
-
-    server.sendResponse(
-      result.toResponse(request.id),
-    );
-  }
-
   @override
   Response? handleRequest(
       Request request, CancellationToken cancellationToken) {
     try {
       var requestName = request.method;
       if (requestName == FLUTTER_REQUEST_GET_WIDGET_DESCRIPTION) {
-        getWidgetDescription(request);
+        FlutterGetWidgetDescriptionHandler(server, request, cancellationToken)
+            .handle();
         return Response.DELAYED_RESPONSE;
       }
       if (requestName == FLUTTER_REQUEST_SET_WIDGET_PROPERTY_VALUE) {
-        setPropertyValue(request);
+        FlutterSetWidgetPropertyValueHandler(server, request, cancellationToken)
+            .handle();
         return Response.DELAYED_RESPONSE;
       }
       if (requestName == FLUTTER_REQUEST_SET_SUBSCRIPTIONS) {
-        return setSubscriptions(request);
+        FlutterSetSubscriptionsHandler(server, request, cancellationToken)
+            .handle();
+        return Response.DELAYED_RESPONSE;
       }
     } on RequestFailure catch (exception) {
       return exception.response;
     }
     return null;
   }
-
-  /// Implement the 'flutter.setPropertyValue' request.
-  void setPropertyValue(Request request) async {
-    var params = FlutterSetWidgetPropertyValueParams.fromRequest(request);
-
-    var result = await server.flutterWidgetDescriptions.setPropertyValue(
-      params.id,
-      params.value,
-    );
-
-    var errorCode = result.errorCode;
-    if (errorCode != null) {
-      server.sendResponse(
-        Response(
-          request.id,
-          error: RequestError(errorCode, ''),
-        ),
-      );
-    }
-
-    server.sendResponse(
-      FlutterSetWidgetPropertyValueResult(
-        result.change!,
-      ).toResponse(request.id),
-    );
-  }
-
-  /// Implement the 'flutter.setSubscriptions' request.
-  Response setSubscriptions(Request request) {
-    var params = FlutterSetSubscriptionsParams.fromRequest(request);
-    var subMap =
-        mapMap<FlutterService, List<String>, FlutterService, Set<String>>(
-            params.subscriptions,
-            valueCallback: (List<String> subscriptions) =>
-                subscriptions.toSet());
-    server.setFlutterSubscriptions(subMap);
-    return FlutterSetSubscriptionsResult().toResponse(request.id);
-  }
 }
diff --git a/pkg/analysis_server/lib/src/handler/legacy/flutter_get_widget_description.dart b/pkg/analysis_server/lib/src/handler/legacy/flutter_get_widget_description.dart
new file mode 100644
index 0000000..c3aad69
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/flutter_get_widget_description.dart
@@ -0,0 +1,71 @@
+// 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:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+
+/// The handler for the `flutter.getWidgetDescription` request.
+class FlutterGetWidgetDescriptionHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  FlutterGetWidgetDescriptionHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var params = FlutterGetWidgetDescriptionParams.fromRequest(request);
+    var file = params.file;
+    var offset = params.offset;
+
+    if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
+      return;
+    }
+
+    var resolvedUnit = await server.getResolvedUnit(file);
+    if (resolvedUnit == null) {
+      sendResponse(Response.fileNotAnalyzed(request, file));
+      return;
+    }
+
+    var computer = server.flutterWidgetDescriptions;
+
+    FlutterGetWidgetDescriptionResult? result;
+    try {
+      result = await computer.getDescription(
+        resolvedUnit,
+        offset,
+      );
+    } on InconsistentAnalysisException {
+      sendResponse(
+        Response(
+          request.id,
+          error: RequestError(
+            RequestErrorCode.FLUTTER_GET_WIDGET_DESCRIPTION_CONTENT_MODIFIED,
+            'Concurrent modification detected.',
+          ),
+        ),
+      );
+      return;
+    }
+
+    if (result == null) {
+      sendResponse(
+        Response(
+          request.id,
+          error: RequestError(
+            RequestErrorCode.FLUTTER_GET_WIDGET_DESCRIPTION_NO_WIDGET,
+            'No Flutter widget at the given location.',
+          ),
+        ),
+      );
+      return;
+    }
+
+    sendResult(result);
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/flutter_set_subscriptions.dart b/pkg/analysis_server/lib/src/handler/legacy/flutter_set_subscriptions.dart
new file mode 100644
index 0000000..2848638
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/flutter_set_subscriptions.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.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/protocol/protocol_internal.dart';
+
+/// The handler for the `flutter.setSubscriptions` request.
+class FlutterSetSubscriptionsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  FlutterSetSubscriptionsHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var params = FlutterSetSubscriptionsParams.fromRequest(request);
+    var subMap =
+        mapMap<FlutterService, List<String>, FlutterService, Set<String>>(
+            params.subscriptions,
+            valueCallback: (List<String> subscriptions) =>
+                subscriptions.toSet());
+    server.setFlutterSubscriptions(subMap);
+    sendResult(FlutterSetSubscriptionsResult());
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/flutter_set_widget_property_value.dart b/pkg/analysis_server/lib/src/handler/legacy/flutter_set_widget_property_value.dart
new file mode 100644
index 0000000..af10cce
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/flutter_set_widget_property_value.dart
@@ -0,0 +1,41 @@
+// 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:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+
+/// The handler for the `flutter.setWidgetPropertyValue` request.
+class FlutterSetWidgetPropertyValueHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  FlutterSetWidgetPropertyValueHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var params = FlutterSetWidgetPropertyValueParams.fromRequest(request);
+
+    var result = await server.flutterWidgetDescriptions.setPropertyValue(
+      params.id,
+      params.value,
+    );
+
+    var errorCode = result.errorCode;
+    if (errorCode != null) {
+      sendResponse(
+        Response(
+          request.id,
+          error: RequestError(errorCode, ''),
+        ),
+      );
+    }
+
+    sendResult(FlutterSetWidgetPropertyValueResult(
+      result.change!,
+    ));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/legacy_handler.dart b/pkg/analysis_server/lib/src/handler/legacy/legacy_handler.dart
index cc96821..aaaefdeb 100644
--- a/pkg/analysis_server/lib/src/handler/legacy/legacy_handler.dart
+++ b/pkg/analysis_server/lib/src/handler/legacy/legacy_handler.dart
@@ -6,6 +6,7 @@
 
 import 'package:_fe_analyzer_shared/src/scanner/errors.dart';
 import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/protocol/protocol_internal.dart';
 import 'package:analyzer/error/error.dart';
@@ -53,4 +54,9 @@
   void sendResult(ResponseResult result) {
     sendResponse(result.toResponse(request.id));
   }
+
+  /// Send a notification built from the given [params].
+  void sendSearchResults(SearchResultsParams params) {
+    server.sendNotification(params.toNotification());
+  }
 }
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_find_element_references.dart b/pkg/analysis_server/lib/src/handler/legacy/search_find_element_references.dart
new file mode 100644
index 0000000..ccdbbaf
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_find_element_references.dart
@@ -0,0 +1,55 @@
+// 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:analysis_server/plugin/protocol/protocol_dart.dart' as protocol;
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/search/element_references.dart';
+import 'package:analyzer/dart/element/element.dart';
+
+/// The handler for the `search.findElementReferences` request.
+class SearchFindElementReferencesHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchFindElementReferencesHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    final searchEngine = server.searchEngine;
+    var params =
+        protocol.SearchFindElementReferencesParams.fromRequest(request);
+    var file = params.file;
+    // prepare element
+    var element = await server.getElementAtOffset(file, params.offset);
+    if (element is ImportElement) {
+      element = element.prefix;
+    }
+    if (element is FieldFormalParameterElement) {
+      element = element.field;
+    }
+    if (element is PropertyAccessorElement) {
+      element = element.variable;
+    }
+    // respond
+    var searchId = (server.nextSearchId++).toString();
+    var result = protocol.SearchFindElementReferencesResult();
+    if (element != null) {
+      result.id = searchId;
+      var withNullability = element.library?.isNonNullableByDefault ?? false;
+      result.element =
+          protocol.convertElement(element, withNullability: withNullability);
+    }
+    sendResult(result);
+    // search elements
+    if (element != null) {
+      var computer = ElementReferencesComputer(searchEngine);
+      var results = await computer.compute(element, params.includePotential);
+      sendSearchResults(
+          protocol.SearchResultsParams(searchId, results.toList(), true));
+    }
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_find_member_declarations.dart b/pkg/analysis_server/lib/src/handler/legacy/search_find_member_declarations.dart
new file mode 100644
index 0000000..d5ade7e
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_find_member_declarations.dart
@@ -0,0 +1,32 @@
+// 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:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/search/search_domain.dart';
+
+/// The handler for the `search.findMemberDeclarations` request.
+class SearchFindMemberDeclarationsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchFindMemberDeclarationsHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    final searchEngine = server.searchEngine;
+    var params =
+        protocol.SearchFindMemberDeclarationsParams.fromRequest(request);
+    await server.onAnalysisComplete;
+    // respond
+    var searchId = (server.nextSearchId++).toString();
+    sendResult(protocol.SearchFindMemberDeclarationsResult(searchId));
+    // search
+    var matches = await searchEngine.searchMemberDeclarations(params.name);
+    sendSearchResults(protocol.SearchResultsParams(
+        searchId, matches.map(SearchDomainHandler.toResult).toList(), true));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_find_member_references.dart b/pkg/analysis_server/lib/src/handler/legacy/search_find_member_references.dart
new file mode 100644
index 0000000..d460649
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_find_member_references.dart
@@ -0,0 +1,31 @@
+// 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:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/search/search_domain.dart';
+
+/// The handler for the `search.findMemberReferences` request.
+class SearchFindMemberReferencesHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchFindMemberReferencesHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    final searchEngine = server.searchEngine;
+    var params = protocol.SearchFindMemberReferencesParams.fromRequest(request);
+    await server.onAnalysisComplete;
+    // respond
+    var searchId = (server.nextSearchId++).toString();
+    sendResult(protocol.SearchFindMemberReferencesResult(searchId));
+    // search
+    var matches = await searchEngine.searchMemberReferences(params.name);
+    sendSearchResults(protocol.SearchResultsParams(
+        searchId, matches.map(SearchDomainHandler.toResult).toList(), true));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_find_top_level_declarations.dart b/pkg/analysis_server/lib/src/handler/legacy/search_find_top_level_declarations.dart
new file mode 100644
index 0000000..b24980a
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_find_top_level_declarations.dart
@@ -0,0 +1,42 @@
+// 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:analysis_server/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/search/search_domain.dart';
+
+/// The handler for the `search.findTopLevelDeclarations` request.
+class SearchFindTopLevelDeclarationsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchFindTopLevelDeclarationsHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    final searchEngine = server.searchEngine;
+    var params =
+        protocol.SearchFindTopLevelDeclarationsParams.fromRequest(request);
+    try {
+      // validate the regex
+      RegExp(params.pattern);
+    } on FormatException catch (exception) {
+      server.sendResponse(protocol.Response.invalidParameter(
+          request, 'pattern', exception.message));
+      return;
+    }
+
+    await server.onAnalysisComplete;
+    // respond
+    var searchId = (server.nextSearchId++).toString();
+    sendResult(protocol.SearchFindTopLevelDeclarationsResult(searchId));
+    // search
+    var matches = await searchEngine.searchTopLevelDeclarations(params.pattern);
+    sendSearchResults(protocol.SearchResultsParams(
+        searchId, matches.map(SearchDomainHandler.toResult).toList(), true));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_get_element_declarations.dart b/pkg/analysis_server/lib/src/handler/legacy/search_get_element_declarations.dart
new file mode 100644
index 0000000..3e4c0fa
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_get_element_declarations.dart
@@ -0,0 +1,107 @@
+// 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:analysis_server/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analyzer/src/dart/analysis/search.dart' as search;
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
+
+/// The handler for the `search.getElementDeclarations` request.
+class SearchGetElementDeclarationsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchGetElementDeclarationsHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var params =
+        protocol.SearchGetElementDeclarationsParams.fromRequest(request);
+
+    RegExp? regExp;
+    var pattern = params.pattern;
+    if (pattern != null) {
+      try {
+        regExp = RegExp(pattern);
+      } on FormatException catch (exception) {
+        server.sendResponse(protocol.Response.invalidParameter(
+            request, 'pattern', exception.message));
+        return;
+      }
+    }
+
+    protocol.ElementKind getElementKind(search.DeclarationKind kind) {
+      switch (kind) {
+        case search.DeclarationKind.CLASS:
+          return protocol.ElementKind.CLASS;
+        case search.DeclarationKind.CLASS_TYPE_ALIAS:
+          return protocol.ElementKind.CLASS_TYPE_ALIAS;
+        case search.DeclarationKind.CONSTRUCTOR:
+          return protocol.ElementKind.CONSTRUCTOR;
+        case search.DeclarationKind.ENUM:
+          return protocol.ElementKind.ENUM;
+        case search.DeclarationKind.ENUM_CONSTANT:
+          return protocol.ElementKind.ENUM_CONSTANT;
+        case search.DeclarationKind.FIELD:
+          return protocol.ElementKind.FIELD;
+        case search.DeclarationKind.FUNCTION:
+          return protocol.ElementKind.FUNCTION;
+        case search.DeclarationKind.FUNCTION_TYPE_ALIAS:
+          return protocol.ElementKind.FUNCTION_TYPE_ALIAS;
+        case search.DeclarationKind.GETTER:
+          return protocol.ElementKind.GETTER;
+        case search.DeclarationKind.METHOD:
+          return protocol.ElementKind.METHOD;
+        case search.DeclarationKind.MIXIN:
+          return protocol.ElementKind.MIXIN;
+        case search.DeclarationKind.SETTER:
+          return protocol.ElementKind.SETTER;
+        case search.DeclarationKind.TYPE_ALIAS:
+          return protocol.ElementKind.TYPE_ALIAS;
+        case search.DeclarationKind.VARIABLE:
+          return protocol.ElementKind.TOP_LEVEL_VARIABLE;
+        default:
+          return protocol.ElementKind.CLASS;
+      }
+    }
+
+    if (!server.options.featureSet.completion) {
+      server.sendResponse(Response.unsupportedFeature(
+          request.id, 'Completion is not enabled.'));
+      return;
+    }
+
+    var workspaceSymbols = search.WorkspaceSymbols();
+    var analysisDrivers = server.driverMap.values.toList();
+    for (var analysisDriver in analysisDrivers) {
+      await analysisDriver.search.declarations(
+          workspaceSymbols, regExp, params.maxResults,
+          onlyForFile: params.file);
+    }
+
+    var declarations = workspaceSymbols.declarations;
+    var elementDeclarations = declarations.map((declaration) {
+      return protocol.ElementDeclaration(
+          declaration.name,
+          getElementKind(declaration.kind),
+          declaration.fileIndex,
+          declaration.offset,
+          declaration.line,
+          declaration.column,
+          declaration.codeOffset,
+          declaration.codeLength,
+          className: declaration.className,
+          mixinName: declaration.mixinName,
+          parameters: declaration.parameters);
+    }).toList();
+
+    server.sendResponse(protocol.SearchGetElementDeclarationsResult(
+            elementDeclarations, workspaceSymbols.files)
+        .toResponse(request.id));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/search_get_type_hierarchy.dart b/pkg/analysis_server/lib/src/handler/legacy/search_get_type_hierarchy.dart
new file mode 100644
index 0000000..36c039a
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/search_get_type_hierarchy.dart
@@ -0,0 +1,53 @@
+// 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:analysis_server/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/search/type_hierarchy.dart';
+
+/// The handler for the `search.getTypeHierarchy` request.
+class SearchGetTypeHierarchyHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  SearchGetTypeHierarchyHandler(
+      super.server, super.request, super.cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    final searchEngine = server.searchEngine;
+    var params = protocol.SearchGetTypeHierarchyParams.fromRequest(request);
+    var file = params.file;
+    // prepare element
+    var element = await server.getElementAtOffset(file, params.offset);
+    if (element == null) {
+      _sendTypeHierarchyNull(request);
+      return;
+    }
+    // maybe supertype hierarchy only
+    if (params.superOnly == true) {
+      var computer = TypeHierarchyComputer(searchEngine, element);
+      var items = computer.computeSuper();
+      var response =
+          protocol.SearchGetTypeHierarchyResult(hierarchyItems: items)
+              .toResponse(request.id);
+      server.sendResponse(response);
+      return;
+    }
+    // prepare type hierarchy
+    var computer = TypeHierarchyComputer(searchEngine, element);
+    var items = await computer.compute();
+    var response = protocol.SearchGetTypeHierarchyResult(hierarchyItems: items)
+        .toResponse(request.id);
+    server.sendResponse(response);
+  }
+
+  void _sendTypeHierarchyNull(protocol.Request request) {
+    var response =
+        protocol.SearchGetTypeHierarchyResult().toResponse(request.id);
+    server.sendResponse(response);
+  }
+}
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index da23c1a..ab903d63 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -5,14 +5,14 @@
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/protocol/protocol_constants.dart';
 import 'package:analysis_server/src/analysis_server.dart';
-import 'package:analysis_server/src/protocol/protocol_internal.dart'
-    show ResponseResult;
+import 'package:analysis_server/src/handler/legacy/search_find_element_references.dart';
+import 'package:analysis_server/src/handler/legacy/search_find_member_declarations.dart';
+import 'package:analysis_server/src/handler/legacy/search_find_member_references.dart';
+import 'package:analysis_server/src/handler/legacy/search_find_top_level_declarations.dart';
+import 'package:analysis_server/src/handler/legacy/search_get_element_declarations.dart';
+import 'package:analysis_server/src/handler/legacy/search_get_type_hierarchy.dart';
 import 'package:analysis_server/src/protocol_server.dart' as protocol;
-import 'package:analysis_server/src/search/element_references.dart';
-import 'package:analysis_server/src/search/type_hierarchy.dart';
 import 'package:analysis_server/src/services/search/search_engine.dart';
-import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/src/dart/analysis/search.dart' as search;
 import 'package:analyzer/src/utilities/cancellation.dart';
 
 /// Instances of the class [SearchDomainHandler] implement a [RequestHandler]
@@ -21,235 +21,39 @@
   /// The analysis server that is using this handler to process requests.
   final AnalysisServer server;
 
-  /// The next search response id.
-  int _nextSearchId = 0;
-
   /// Initialize a newly created handler to handle requests for the given
   /// [server].
   SearchDomainHandler(this.server);
 
-  Future<void> findElementReferences(protocol.Request request) async {
-    final searchEngine = server.searchEngine;
-    var params =
-        protocol.SearchFindElementReferencesParams.fromRequest(request);
-    var file = params.file;
-    // prepare element
-    var element = await server.getElementAtOffset(file, params.offset);
-    if (element is ImportElement) {
-      element = element.prefix;
-    }
-    if (element is FieldFormalParameterElement) {
-      element = element.field;
-    }
-    if (element is PropertyAccessorElement) {
-      element = element.variable;
-    }
-    // respond
-    var searchId = (_nextSearchId++).toString();
-    var result = protocol.SearchFindElementReferencesResult();
-    if (element != null) {
-      result.id = searchId;
-      var withNullability = element.library?.isNonNullableByDefault ?? false;
-      result.element =
-          protocol.convertElement(element, withNullability: withNullability);
-    }
-    _sendSearchResult(request, result);
-    // search elements
-    if (element != null) {
-      var computer = ElementReferencesComputer(searchEngine);
-      var results = await computer.compute(element, params.includePotential);
-      _sendSearchNotification(searchId, true, results);
-    }
-  }
-
-  Future findMemberDeclarations(protocol.Request request) async {
-    final searchEngine = server.searchEngine;
-    var params =
-        protocol.SearchFindMemberDeclarationsParams.fromRequest(request);
-    await server.onAnalysisComplete;
-    // respond
-    var searchId = (_nextSearchId++).toString();
-    _sendSearchResult(
-        request, protocol.SearchFindMemberDeclarationsResult(searchId));
-    // search
-    var matches = await searchEngine.searchMemberDeclarations(params.name);
-    _sendSearchNotification(searchId, true, matches.map(toResult));
-  }
-
-  Future findMemberReferences(protocol.Request request) async {
-    final searchEngine = server.searchEngine;
-    var params = protocol.SearchFindMemberReferencesParams.fromRequest(request);
-    await server.onAnalysisComplete;
-    // respond
-    var searchId = (_nextSearchId++).toString();
-    _sendSearchResult(
-        request, protocol.SearchFindMemberReferencesResult(searchId));
-    // search
-    var matches = await searchEngine.searchMemberReferences(params.name);
-    _sendSearchNotification(searchId, true, matches.map(toResult));
-  }
-
-  Future findTopLevelDeclarations(protocol.Request request) async {
-    final searchEngine = server.searchEngine;
-    var params =
-        protocol.SearchFindTopLevelDeclarationsParams.fromRequest(request);
-    try {
-      // validate the regex
-      RegExp(params.pattern);
-    } on FormatException catch (exception) {
-      server.sendResponse(protocol.Response.invalidParameter(
-          request, 'pattern', exception.message));
-      return;
-    }
-
-    await server.onAnalysisComplete;
-    // respond
-    var searchId = (_nextSearchId++).toString();
-    _sendSearchResult(
-        request, protocol.SearchFindTopLevelDeclarationsResult(searchId));
-    // search
-    var matches = await searchEngine.searchTopLevelDeclarations(params.pattern);
-    _sendSearchNotification(searchId, true, matches.map(toResult));
-  }
-
-  /// Implement the `search.getDeclarations` request.
-  Future getDeclarations(protocol.Request request) async {
-    var params =
-        protocol.SearchGetElementDeclarationsParams.fromRequest(request);
-
-    RegExp? regExp;
-    var pattern = params.pattern;
-    if (pattern != null) {
-      try {
-        regExp = RegExp(pattern);
-      } on FormatException catch (exception) {
-        server.sendResponse(protocol.Response.invalidParameter(
-            request, 'pattern', exception.message));
-        return;
-      }
-    }
-
-    protocol.ElementKind getElementKind(search.DeclarationKind kind) {
-      switch (kind) {
-        case search.DeclarationKind.CLASS:
-          return protocol.ElementKind.CLASS;
-        case search.DeclarationKind.CLASS_TYPE_ALIAS:
-          return protocol.ElementKind.CLASS_TYPE_ALIAS;
-        case search.DeclarationKind.CONSTRUCTOR:
-          return protocol.ElementKind.CONSTRUCTOR;
-        case search.DeclarationKind.ENUM:
-          return protocol.ElementKind.ENUM;
-        case search.DeclarationKind.ENUM_CONSTANT:
-          return protocol.ElementKind.ENUM_CONSTANT;
-        case search.DeclarationKind.FIELD:
-          return protocol.ElementKind.FIELD;
-        case search.DeclarationKind.FUNCTION:
-          return protocol.ElementKind.FUNCTION;
-        case search.DeclarationKind.FUNCTION_TYPE_ALIAS:
-          return protocol.ElementKind.FUNCTION_TYPE_ALIAS;
-        case search.DeclarationKind.GETTER:
-          return protocol.ElementKind.GETTER;
-        case search.DeclarationKind.METHOD:
-          return protocol.ElementKind.METHOD;
-        case search.DeclarationKind.MIXIN:
-          return protocol.ElementKind.MIXIN;
-        case search.DeclarationKind.SETTER:
-          return protocol.ElementKind.SETTER;
-        case search.DeclarationKind.TYPE_ALIAS:
-          return protocol.ElementKind.TYPE_ALIAS;
-        case search.DeclarationKind.VARIABLE:
-          return protocol.ElementKind.TOP_LEVEL_VARIABLE;
-        default:
-          return protocol.ElementKind.CLASS;
-      }
-    }
-
-    if (!server.options.featureSet.completion) {
-      server.sendResponse(Response.unsupportedFeature(
-          request.id, 'Completion is not enabled.'));
-      return;
-    }
-
-    var workspaceSymbols = search.WorkspaceSymbols();
-    var analysisDrivers = server.driverMap.values.toList();
-    for (var analysisDriver in analysisDrivers) {
-      await analysisDriver.search.declarations(
-          workspaceSymbols, regExp, params.maxResults,
-          onlyForFile: params.file);
-    }
-
-    var declarations = workspaceSymbols.declarations;
-    var elementDeclarations = declarations.map((declaration) {
-      return protocol.ElementDeclaration(
-          declaration.name,
-          getElementKind(declaration.kind),
-          declaration.fileIndex,
-          declaration.offset,
-          declaration.line,
-          declaration.column,
-          declaration.codeOffset,
-          declaration.codeLength,
-          className: declaration.className,
-          mixinName: declaration.mixinName,
-          parameters: declaration.parameters);
-    }).toList();
-
-    server.sendResponse(protocol.SearchGetElementDeclarationsResult(
-            elementDeclarations, workspaceSymbols.files)
-        .toResponse(request.id));
-  }
-
-  /// Implement the `search.getTypeHierarchy` request.
-  Future getTypeHierarchy(protocol.Request request) async {
-    final searchEngine = server.searchEngine;
-    var params = protocol.SearchGetTypeHierarchyParams.fromRequest(request);
-    var file = params.file;
-    // prepare element
-    var element = await server.getElementAtOffset(file, params.offset);
-    if (element == null) {
-      _sendTypeHierarchyNull(request);
-      return;
-    }
-    // maybe supertype hierarchy only
-    if (params.superOnly == true) {
-      var computer = TypeHierarchyComputer(searchEngine, element);
-      var items = computer.computeSuper();
-      var response =
-          protocol.SearchGetTypeHierarchyResult(hierarchyItems: items)
-              .toResponse(request.id);
-      server.sendResponse(response);
-      return;
-    }
-    // prepare type hierarchy
-    var computer = TypeHierarchyComputer(searchEngine, element);
-    var items = await computer.compute();
-    var response = protocol.SearchGetTypeHierarchyResult(hierarchyItems: items)
-        .toResponse(request.id);
-    server.sendResponse(response);
-  }
-
   @override
   protocol.Response? handleRequest(
       protocol.Request request, CancellationToken cancellationToken) {
     try {
       var requestName = request.method;
       if (requestName == SEARCH_REQUEST_FIND_ELEMENT_REFERENCES) {
-        findElementReferences(request);
+        SearchFindElementReferencesHandler(server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       } else if (requestName == SEARCH_REQUEST_FIND_MEMBER_DECLARATIONS) {
-        findMemberDeclarations(request);
+        SearchFindMemberDeclarationsHandler(server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       } else if (requestName == SEARCH_REQUEST_FIND_MEMBER_REFERENCES) {
-        findMemberReferences(request);
+        SearchFindMemberReferencesHandler(server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       } else if (requestName == SEARCH_REQUEST_FIND_TOP_LEVEL_DECLARATIONS) {
-        findTopLevelDeclarations(request);
+        SearchFindTopLevelDeclarationsHandler(
+                server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       } else if (requestName == SEARCH_REQUEST_GET_ELEMENT_DECLARATIONS) {
-        getDeclarations(request);
+        SearchGetElementDeclarationsHandler(server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       } else if (requestName == SEARCH_REQUEST_GET_TYPE_HIERARCHY) {
-        getTypeHierarchy(request);
+        SearchGetTypeHierarchyHandler(server, request, cancellationToken)
+            .handle();
         return protocol.Response.DELAYED_RESPONSE;
       }
     } on protocol.RequestFailure catch (exception) {
@@ -258,25 +62,6 @@
     return null;
   }
 
-  void _sendSearchNotification(
-      String searchId, bool isLast, Iterable<protocol.SearchResult> results) {
-    server.sendNotification(
-        protocol.SearchResultsParams(searchId, results.toList(), isLast)
-            .toNotification());
-  }
-
-  /// Send a search response with the given [result] to the given [request].
-  void _sendSearchResult(protocol.Request request, ResponseResult result) {
-    var response = result.toResponse(request.id);
-    server.sendResponse(response);
-  }
-
-  void _sendTypeHierarchyNull(protocol.Request request) {
-    var response =
-        protocol.SearchGetTypeHierarchyResult().toResponse(request.id);
-    server.sendResponse(response);
-  }
-
   static protocol.SearchResult toResult(SearchMatch match) {
     return protocol.newSearchResult_fromMatch(match);
   }
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_super_parameters.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_super_parameters.dart
index 7401c0c..03a314a 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_super_parameters.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_super_parameters.dart
@@ -333,8 +333,9 @@
   bool _nullInitializer(
       FormalParameter parameter, ParameterElement superParameter) {
     return parameter is DefaultFormalParameter &&
+        !parameter.isRequired &&
         parameter.defaultValue == null &&
-        superParameter.defaultValueCode != null;
+        superParameter.hasDefaultValue;
   }
 
   /// Return the parameter corresponding to the [expression], or `null` if the
diff --git a/pkg/analysis_server/lib/src/services/flutter/class_description.dart b/pkg/analysis_server/lib/src/services/flutter/class_description.dart
index b37c741..a592206 100644
--- a/pkg/analysis_server/lib/src/services/flutter/class_description.dart
+++ b/pkg/analysis_server/lib/src/services/flutter/class_description.dart
@@ -64,7 +64,7 @@
     if (constructor == null) return null;
 
     for (var parameter in constructor.parameters) {
-      if (parameter.isNotOptional || parameter.hasRequired) {
+      if (parameter.isRequired || parameter.hasRequired) {
         return null;
       }
     }
diff --git a/pkg/analysis_server/test/src/services/correction/assist/convert_to_super_parameters_test.dart b/pkg/analysis_server/test/src/services/correction/assist/convert_to_super_parameters_test.dart
index 1af8888..70b834d 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/convert_to_super_parameters_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/convert_to_super_parameters_test.dart
@@ -579,6 +579,27 @@
 ''');
   }
 
+  Future<void> test_named_required() async {
+    await resolveTestCode('''
+class A {
+  int x;
+  A({this.x = 0});
+}
+class B extends A {
+  B({required int x}) : super(x: x);
+}
+''');
+    await assertHasAssistAt('B(', '''
+class A {
+  int x;
+  A({this.x = 0});
+}
+class B extends A {
+  B({required super.x});
+}
+''');
+  }
+
   Future<void> test_namedConstructor() async {
     await resolveTestCode('''
 class A {
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 85f3551..3022ce6 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 4.1.0-dev
+* Deprecated `ParameterElement.isNotOptional`, use `isRequired` instead.
+
 ## 4.0.0
 * Removed deprecated `UriKind` and `Source.uriKind`.
 * Removed deprecated `LibraryElement.hasExtUri`.
diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart
index 1246953..97af71b 100644
--- a/pkg/analyzer/lib/dart/element/element.dart
+++ b/pkg/analyzer/lib/dart/element/element.dart
@@ -1528,7 +1528,7 @@
   /// meaning of this getter. The parameter `{@required int x}` will return
   /// `false` and the parameter `{@required required int x}` will return
   /// `true`
-  // TODO(brianwilkerson) Rename this to `isRequired`.
+  @Deprecated('Use isRequired instead')
   bool get isNotOptional;
 
   /// Return `true` if this parameter is an optional parameter. Optional
@@ -1552,6 +1552,15 @@
   /// parameters can either be required or optional.
   bool get isPositional;
 
+  /// Return `true` if this parameter is either a required positional
+  /// parameter, or a named parameter with the `required` keyword.
+  ///
+  /// Note: the presence or absence of the `@required` annotation does not
+  /// change the meaning of this getter. The parameter `{@required int x}`
+  /// will return `false` and the parameter `{@required required int x}`
+  /// will return `true`.
+  bool get isRequired;
+
   /// Return `true` if this parameter is both a required and named parameter.
   /// Named parameters that are annotated with the `@required` annotation are
   /// considered optional.  Named parameters that are annotated with the
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 5e40995..97707e5 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -84,7 +84,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 215;
+  static const int DATA_VERSION = 216;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 2815dbc..a0f91f2 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -41,6 +41,7 @@
 import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
 import 'package:analyzer/src/summary2/bundle_reader.dart';
 import 'package:analyzer/src/summary2/macro.dart';
+import 'package:analyzer/src/summary2/macro_application_error.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/task/inference_error.dart';
 import 'package:collection/collection.dart';
@@ -77,6 +78,9 @@
   List<InterfaceType>? Function(AbstractClassElementImpl)?
       mixinInferenceCallback;
 
+  /// Errors registered while applying macros to this element.
+  List<MacroApplicationError> macroApplicationErrors = [];
+
   /// Initialize a newly created class element to have the given [name] at the
   /// given [offset] in the file that contains the declaration of this element.
   AbstractClassElementImpl(String name, int offset) : super(name, offset);
@@ -1449,7 +1453,7 @@
     }
     // no required parameters
     for (ParameterElement parameter in parameters) {
-      if (parameter.isNotOptional) {
+      if (parameter.isRequired) {
         return false;
       }
     }
@@ -4876,6 +4880,7 @@
   @override
   bool get isNamed => parameterKind.isNamed;
 
+  @Deprecated('Use isRequired instead')
   @override
   bool get isNotOptional => parameterKind.isRequired;
 
@@ -4892,6 +4897,9 @@
   bool get isPositional => parameterKind.isPositional;
 
   @override
+  bool get isRequired => parameterKind.isRequired;
+
+  @override
   bool get isRequiredNamed => parameterKind.isRequiredNamed;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
index c675895..544a143 100644
--- a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
+++ b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
@@ -802,14 +802,14 @@
 
     while (fIndex < fParameters.length) {
       var fParameter = fParameters[fIndex++];
-      if (fParameter.isNotOptional) {
+      if (fParameter.isRequired) {
         return _interfaceTypeFunctionNone;
       }
     }
 
     while (gIndex < gParameters.length) {
       var gParameter = gParameters[gIndex++];
-      if (gParameter.isNotOptional) {
+      if (gParameter.isRequired) {
         return _interfaceTypeFunctionNone;
       }
     }
diff --git a/pkg/analyzer/lib/src/dart/element/subtype.dart b/pkg/analyzer/lib/src/dart/element/subtype.dart
index 9ff6e69..8bec0b3 100644
--- a/pkg/analyzer/lib/src/dart/element/subtype.dart
+++ b/pkg/analyzer/lib/src/dart/element/subtype.dart
@@ -419,7 +419,7 @@
     // The supertype must provide all required parameters to the subtype.
     while (fIndex < fParameters.length) {
       var fParameter = fParameters[fIndex++];
-      if (fParameter.isNotOptional) {
+      if (fParameter.isRequired) {
         return false;
       }
     }
diff --git a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
index eed4d97..f5b011e 100644
--- a/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_constraint_gatherer.dart
@@ -506,7 +506,7 @@
     // The supertype must provide all required parameters to the subtype.
     while (fIndex < fParameters.length) {
       var fParameter = fParameters[fIndex++];
-      if (fParameter.isNotOptional) {
+      if (fParameter.isRequired) {
         _constraints.length = rewind;
         return false;
       }
diff --git a/pkg/analyzer/lib/src/summary2/bundle_reader.dart b/pkg/analyzer/lib/src/summary2/bundle_reader.dart
index 4fdbad0..b24d7dc 100644
--- a/pkg/analyzer/lib/src/summary2/bundle_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/bundle_reader.dart
@@ -24,6 +24,7 @@
 import 'package:analyzer/src/summary2/element_flags.dart';
 import 'package:analyzer/src/summary2/informative_data.dart';
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
+import 'package:analyzer/src/summary2/macro_application_error.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/task/inference_error.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -510,6 +511,9 @@
     );
     element.setLinkedData(reference, linkedData);
     ClassElementFlags.read(_reader, element);
+    element.macroApplicationErrors = _reader.readTypedList(
+      () => MacroApplicationError(_reader),
+    );
 
     element.typeParameters = _readTypeParameters();
 
diff --git a/pkg/analyzer/lib/src/summary2/bundle_writer.dart b/pkg/analyzer/lib/src/summary2/bundle_writer.dart
index 7a4cbf6..80f6e6b 100644
--- a/pkg/analyzer/lib/src/summary2/bundle_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/bundle_writer.dart
@@ -18,6 +18,7 @@
 import 'package:analyzer/src/summary2/ast_binary_writer.dart';
 import 'package:analyzer/src/summary2/data_writer.dart';
 import 'package:analyzer/src/summary2/element_flags.dart';
+import 'package:analyzer/src/summary2/macro_application_error.dart';
 import 'package:analyzer/src/summary2/reference.dart';
 import 'package:analyzer/src/task/inference_error.dart';
 import 'package:collection/collection.dart';
@@ -126,6 +127,11 @@
     _sink._writeStringReference(element.name);
     ClassElementFlags.write(_sink, element);
 
+    _sink.writeList<MacroApplicationError>(
+      element.macroApplicationErrors,
+      (x) => x.write(_sink),
+    );
+
     _resolutionSink._writeAnnotationList(element.metadata);
 
     _writeTypeParameters(element.typeParameters, () {
diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart
index 7d8f2ba..9f618ae 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/summary2/library_builder.dart';
 import 'package:analyzer/src/summary2/macro.dart';
+import 'package:analyzer/src/summary2/macro_application_error.dart';
 
 class LibraryMacroApplier {
   final LibraryBuilder libraryBuilder;
@@ -27,10 +28,13 @@
     var macroResults = <macro.MacroExecutionResult>[];
     for (var unitElement in libraryBuilder.element.units) {
       for (var classElement in unitElement.classes) {
+        classElement as ClassElementImpl;
         var classNode = libraryBuilder.linker.elementNodes[classElement];
         // TODO(scheglov) support other declarations
         if (classNode is ClassDeclaration) {
-          for (var annotation in classNode.metadata) {
+          var annotationList = classNode.metadata;
+          for (var i = 0; i < annotationList.length; i++) {
+            final annotation = annotationList[i];
             var annotationNameNode = annotation.name;
             var argumentsNode = annotation.arguments;
             if (annotationNameNode is SimpleIdentifier &&
@@ -49,14 +53,30 @@
                     if (getter is ClassElementImpl && getter.isMacro) {
                       var macroExecutor = importedLibrary.bundleMacroExecutor;
                       if (macroExecutor != null) {
-                        var macroResult = await _runSingleMacro(
-                          macroExecutor,
-                          getClassDeclaration(classNode),
-                          getter,
-                          _buildArguments(argumentsNode),
-                        );
-                        if (macroResult.isNotEmpty) {
-                          macroResults.add(macroResult);
+                        try {
+                          final arguments = _buildArguments(
+                            annotationIndex: i,
+                            node: argumentsNode,
+                          );
+                          final macroResult = await _runSingleMacro(
+                            macroExecutor,
+                            getClassDeclaration(classNode),
+                            getter,
+                            arguments,
+                          );
+                          if (macroResult.isNotEmpty) {
+                            macroResults.add(macroResult);
+                          }
+                        } on MacroApplicationError catch (e) {
+                          classElement.macroApplicationErrors.add(e);
+                        } catch (e, stackTrace) {
+                          classElement.macroApplicationErrors.add(
+                            UnknownMacroApplicationError(
+                              annotationIndex: i,
+                              stackTrace: stackTrace.toString(),
+                              message: e.toString(),
+                            ),
+                          );
                         }
                       }
                     }
@@ -112,15 +132,23 @@
     return await macroInstance.executeTypesPhase();
   }
 
-  static macro.Arguments _buildArguments(ArgumentList node) {
+  static macro.Arguments _buildArguments({
+    required int annotationIndex,
+    required ArgumentList node,
+  }) {
     final positional = <Object?>[];
     final named = <String, Object?>{};
-    for (final argument in node.arguments) {
+    for (var i = 0; i < node.arguments.length; ++i) {
+      final argument = node.arguments[i];
+      final evaluation = _ArgumentEvaluation(
+        annotationIndex: annotationIndex,
+        argumentIndex: i,
+      );
       if (argument is NamedExpression) {
-        final value = _evaluateArgument(argument.expression);
+        final value = evaluation.evaluate(argument.expression);
         named[argument.name.label.name] = value;
       } else {
-        final value = _evaluateArgument(argument);
+        final value = evaluation.evaluate(argument);
         positional.add(value);
       }
     }
@@ -199,10 +227,22 @@
       return const [];
     }
   }
+}
 
-  static Object? _evaluateArgument(Expression node) {
+/// Helper class for evaluating arguments for a single constructor based
+/// macro application.
+class _ArgumentEvaluation {
+  final int annotationIndex;
+  final int argumentIndex;
+
+  _ArgumentEvaluation({
+    required this.annotationIndex,
+    required this.argumentIndex,
+  });
+
+  Object? evaluate(Expression node) {
     if (node is AdjacentStrings) {
-      return node.strings.map(_evaluateArgument).join('');
+      return node.strings.map(evaluate).join('');
     } else if (node is BooleanLiteral) {
       return node.value;
     } else if (node is DoubleLiteral) {
@@ -210,12 +250,12 @@
     } else if (node is IntegerLiteral) {
       return node.value;
     } else if (node is ListLiteral) {
-      return node.elements.cast<Expression>().map(_evaluateArgument).toList();
+      return node.elements.cast<Expression>().map(evaluate).toList();
     } else if (node is NullLiteral) {
       return null;
     } else if (node is PrefixExpression &&
         node.operator.type == TokenType.MINUS) {
-      final operandValue = _evaluateArgument(node.operand);
+      final operandValue = evaluate(node.operand);
       if (operandValue is double) {
         return -operandValue;
       } else if (operandValue is int) {
@@ -225,19 +265,25 @@
       final result = <Object?, Object?>{};
       for (final element in node.elements) {
         if (element is! MapLiteralEntry) {
-          throw ArgumentError(
-            'Not supported: (${element.runtimeType}) $element',
-          );
+          _throwError(element, 'MapLiteralEntry expected');
         }
-        final key = _evaluateArgument(element.key);
-        final value = _evaluateArgument(element.value);
+        final key = evaluate(element.key);
+        final value = evaluate(element.value);
         result[key] = value;
       }
       return result;
     } else if (node is SimpleStringLiteral) {
       return node.value;
     }
-    throw ArgumentError('Not supported: (${node.runtimeType}) $node');
+    _throwError(node, 'Not supported: ${node.runtimeType}');
+  }
+
+  Never _throwError(AstNode node, String message) {
+    throw ArgumentMacroApplicationError(
+      annotationIndex: annotationIndex,
+      argumentIndex: argumentIndex,
+      message: message,
+    );
   }
 }
 
diff --git a/pkg/analyzer/lib/src/summary2/macro_application_error.dart b/pkg/analyzer/lib/src/summary2/macro_application_error.dart
new file mode 100644
index 0000000..a7b9018
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary2/macro_application_error.dart
@@ -0,0 +1,130 @@
+// 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:analyzer/src/summary2/data_reader.dart';
+import 'package:analyzer/src/summary2/data_writer.dart';
+import 'package:meta/meta.dart';
+
+/// An error during evaluating annotation arguments.
+class ArgumentMacroApplicationError extends MacroApplicationError {
+  final int argumentIndex;
+  final String message;
+
+  ArgumentMacroApplicationError({
+    required int annotationIndex,
+    required this.argumentIndex,
+    required this.message,
+  }) : super._(
+          annotationIndex: annotationIndex,
+          kind: MacroApplicationErrorKind.argument,
+        );
+
+  factory ArgumentMacroApplicationError._read(
+    SummaryDataReader reader,
+    int annotationIndex,
+  ) {
+    return ArgumentMacroApplicationError(
+      annotationIndex: annotationIndex,
+      argumentIndex: reader.readUInt30(),
+      message: reader.readStringUtf8(),
+    );
+  }
+
+  @override
+  String toStringForTest() {
+    return 'Argument(annotation: $annotationIndex, argument: $argumentIndex)';
+  }
+
+  @override
+  void write(BufferedSink sink) {
+    super.write(sink);
+    sink.writeUInt30(argumentIndex);
+    sink.writeStringUtf8(message);
+  }
+}
+
+/// An error that happened while applying a macro.
+abstract class MacroApplicationError {
+  /// The index of the annotation of the element that turned out to be a
+  /// macro application. Can be used to associate the error with the location.
+  final int annotationIndex;
+
+  final MacroApplicationErrorKind kind;
+
+  factory MacroApplicationError(SummaryDataReader reader) {
+    final annotationIndex = reader.readUInt30();
+    final kind = MacroApplicationErrorKind.values[reader.readUInt30()];
+    switch (kind) {
+      case MacroApplicationErrorKind.argument:
+        return ArgumentMacroApplicationError._read(
+          reader,
+          annotationIndex,
+        );
+      case MacroApplicationErrorKind.unknown:
+        return UnknownMacroApplicationError._read(
+          reader,
+          annotationIndex,
+        );
+    }
+  }
+
+  MacroApplicationError._({
+    required this.annotationIndex,
+    required this.kind,
+  });
+
+  String toStringForTest();
+
+  @mustCallSuper
+  void write(BufferedSink sink) {
+    sink.writeUInt30(annotationIndex);
+    sink.writeUInt30(kind.index);
+  }
+}
+
+enum MacroApplicationErrorKind {
+  /// An error while evaluating arguments.
+  argument,
+
+  /// Any other exception that happened during application.
+  unknown,
+}
+
+/// Any other exception that happened during macro application.
+class UnknownMacroApplicationError extends MacroApplicationError {
+  final String message;
+  final String stackTrace;
+
+  UnknownMacroApplicationError({
+    required int annotationIndex,
+    required this.message,
+    required this.stackTrace,
+  }) : super._(
+          annotationIndex: annotationIndex,
+          kind: MacroApplicationErrorKind.unknown,
+        );
+
+  factory UnknownMacroApplicationError._read(
+    SummaryDataReader reader,
+    int annotationIndex,
+  ) {
+    return UnknownMacroApplicationError(
+      annotationIndex: annotationIndex,
+      message: reader.readStringUtf8(),
+      stackTrace: reader.readStringUtf8(),
+    );
+  }
+
+  @override
+  String toStringForTest() {
+    return 'Unknown(annotation: $annotationIndex)';
+  }
+
+  @override
+  void write(BufferedSink sink) {
+    super.write(sink);
+    sink.writeStringUtf8(message);
+    sink.writeStringUtf8(stackTrace);
+  }
+}
diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml
index 9cf0d35..5286115 100644
--- a/pkg/analyzer/pubspec.yaml
+++ b/pkg/analyzer/pubspec.yaml
@@ -1,5 +1,5 @@
 name: analyzer
-version: 4.0.0
+version: 4.1.0-dev
 description: This package provides a library that performs static analysis of Dart code.
 homepage: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer
 
diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart
index cfde4cd..51443a5 100644
--- a/pkg/analyzer/test/src/summary/macro_test.dart
+++ b/pkg/analyzer/test/src/summary/macro_test.dart
@@ -75,6 +75,18 @@
     );
   }
 
+  test_arguments_error() async {
+    await _assertTypesPhaseArgumentsText(
+      fields: {
+        'foo': 'Object',
+        'bar': 'Object',
+      },
+      constructorParametersCode: '(this.foo, this.bar)',
+      argumentsCode: '(0, const Object())',
+      expectedErrors: 'Argument(annotation: 0, argument: 1)',
+    );
+  }
+
   test_arguments_typesPhase_kind_optionalNamed() async {
     await _assertTypesPhaseArgumentsText(
       fields: {
@@ -456,7 +468,8 @@
     required Map<String, String> fields,
     required String constructorParametersCode,
     required String argumentsCode,
-    required String expected,
+    String? expected,
+    String? expectedErrors,
   }) async {
     final dumpCode = fields.keys.map((name) {
       return "$name: \$$name\\\\n";
@@ -491,15 +504,26 @@
       {'package:test/arguments_text.dart'}
     ]);
 
-    final x = library.parts.single.topLevelVariables.single;
-    expect(x.name, 'x');
-    x as ConstTopLevelVariableElementImpl;
-    final actual = (x.constantInitializer as SimpleStringLiteral).value;
+    if (expected != null) {
+      final x = library.parts.single.topLevelVariables.single;
+      expect(x.name, 'x');
+      x as ConstTopLevelVariableElementImpl;
+      final actual = (x.constantInitializer as SimpleStringLiteral).value;
 
-    if (actual != expected) {
-      print(actual);
+      if (actual != expected) {
+        print(actual);
+      }
+      expect(actual, expected);
+    } else if (expectedErrors != null) {
+      var A = library.definingCompilationUnit.getType('A');
+      A as ClassElementImpl;
+      expect(
+        A.macroApplicationErrors.map((e) => e.toStringForTest()).join('\n'),
+        expectedErrors,
+      );
+    } else {
+      fail("Either 'expected' or 'expectedErrors' must be provided.");
     }
-    expect(actual, expected);
   }
 
   /// Assert that the textual dump of the introspection information for
diff --git a/pkg/compiler/lib/src/deferred_load/deferred_load.dart b/pkg/compiler/lib/src/deferred_load/deferred_load.dart
index 4bbfbdc..ab74ec9 100644
--- a/pkg/compiler/lib/src/deferred_load/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load/deferred_load.dart
@@ -317,7 +317,8 @@
 
   /// A sentinel used only by the [ImportSet] corresponding to the
   /// [_mainOutputUnit].
-  final ImportEntity _mainImport = ImportEntity(true, 'main#main', null, null);
+  final ImportEntity _mainImport =
+      ImportEntity(true, 'main#main', Uri(), Uri());
 
   /// A set containing (eventually) all output units that will result from the
   /// program.
diff --git a/pkg/compiler/lib/src/elements/entities.dart b/pkg/compiler/lib/src/elements/entities.dart
index e0f8380..788a702 100644
--- a/pkg/compiler/lib/src/elements/entities.dart
+++ b/pkg/compiler/lib/src/elements/entities.dart
@@ -6,79 +6,13 @@
 
 library entities;
 
-import 'package:front_end/src/api_unstable/dart2js.dart' show AsyncModifier;
-
-import '../common.dart';
 import '../serialization/serialization.dart';
 import '../universe/call_structure.dart' show CallStructure;
 import '../util/util.dart';
-import 'names.dart';
 import 'types.dart';
 
-/// Abstract interface for entities.
-///
-/// Implement this directly if the entity is not a Dart language entity.
-/// Entities defined within the Dart language should implement [Element].
-///
-/// For instance, the JavaScript backend need to create synthetic variables for
-/// calling intercepted classes and such variables do not correspond to an
-/// entity in the Dart source code nor in the terminology of the Dart language
-/// and should therefore implement [Entity] directly.
-abstract class Entity implements Spannable {
-  String get name;
-}
-
-/// Stripped down super interface for library like entities.
-///
-/// Currently only [LibraryElement] but later also kernel based Dart classes
-/// and/or Dart-in-JS classes.
-abstract class LibraryEntity extends Entity {
-  /// Return the canonical uri that identifies this library.
-  Uri /*!*/ get canonicalUri;
-
-  /// Returns whether or not this library has opted into null safety.
-  bool get isNonNullableByDefault;
-}
-
-/// Stripped down super interface for import entities.
-///
-/// The [name] property corresponds to the prefix name, if any.
-class ImportEntity {
-  final String /*?*/ name;
-
-  /// The canonical URI of the library where this import occurs
-  /// (where the import is declared).
-  final Uri enclosingLibraryUri;
-
-  /// Whether the import is a deferred import.
-  final bool isDeferred;
-
-  /// The target import URI.
-  final Uri uri;
-
-  ImportEntity(this.isDeferred, this.name, this.uri, this.enclosingLibraryUri);
-
-  @override
-  String toString() => 'import($name:${isDeferred ? ' deferred' : ''})';
-}
-
-/// Stripped down super interface for class like entities.
-///
-/// Currently only [ClassElement] but later also kernel based Dart classes
-/// and/or Dart-in-JS classes.
-abstract class ClassEntity extends Entity {
-  /// If this is a normal class, the enclosing library for this class. If this
-  /// is a closure class, the enclosing class of the closure for which it was
-  /// created.
-  LibraryEntity get library;
-
-  /// Whether this is a synthesized class for a closurized method or local
-  /// function.
-  bool get isClosure;
-
-  /// Whether this is an abstract class.
-  bool get isAbstract;
-}
+import 'entities_migrated.dart';
+export 'entities_migrated.dart';
 
 abstract class TypeVariableEntity extends Entity {
   /// The class or generic method that declared this type variable.
@@ -89,66 +23,6 @@
   int get index;
 }
 
-/// Stripped down super interface for member like entities, that is,
-/// constructors, methods, fields etc.
-///
-/// Currently only [MemberElement] but later also kernel based Dart members
-/// and/or Dart-in-JS properties.
-abstract class MemberEntity extends Entity {
-  /// The [Name] of member which takes privacy and getter/setter naming into
-  /// account.
-  Name get memberName;
-
-  /// Whether this is a member of a library.
-  bool get isTopLevel;
-
-  /// Whether this is a static member of a class.
-  bool get isStatic;
-
-  /// Whether this is an instance member of a class.
-  bool get isInstanceMember;
-
-  /// Whether this is a constructor.
-  bool get isConstructor;
-
-  /// Whether this is a field.
-  bool get isField;
-
-  /// Whether this is a normal method (neither constructor, getter or setter)
-  /// or operator method.
-  bool get isFunction;
-
-  /// Whether this is a getter.
-  bool get isGetter;
-
-  /// Whether this is a setter.
-  bool get isSetter;
-
-  /// Whether this member is assignable, i.e. a non-final, non-const field.
-  bool /*!*/ get isAssignable;
-
-  /// Whether this member is constant, i.e. a constant field or constructor.
-  bool /*!*/ get isConst;
-
-  /// Whether this member is abstract, i.e. an abstract method, getter or
-  /// setter.
-  bool /*!*/ get isAbstract;
-
-  /// The enclosing class if this is a constructor, instance member or
-  /// static member of a class.
-  ClassEntity get enclosingClass;
-
-  /// The enclosing library if this is a library member, otherwise the
-  /// enclosing library of the [enclosingClass].
-  LibraryEntity get library;
-}
-
-/// Stripped down super interface for field like entities.
-///
-/// Currently only [FieldElement] but later also kernel based Dart fields
-/// and/or Dart-in-JS field-like properties.
-abstract class FieldEntity extends MemberEntity {}
-
 /// Stripped down super interface for function like entities.
 ///
 /// Currently only [MethodElement] but later also kernel based Dart constructors
@@ -165,61 +39,6 @@
   AsyncMarker get asyncMarker;
 }
 
-/// Enum for the synchronous/asynchronous function body modifiers.
-class AsyncMarker {
-  /// The default function body marker.
-  static const AsyncMarker SYNC = AsyncMarker._(AsyncModifier.Sync);
-
-  /// The `sync*` function body marker.
-  static const AsyncMarker SYNC_STAR =
-      AsyncMarker._(AsyncModifier.SyncStar, isYielding: true);
-
-  /// The `async` function body marker.
-  static const AsyncMarker ASYNC =
-      AsyncMarker._(AsyncModifier.Async, isAsync: true);
-
-  /// The `async*` function body marker.
-  static const AsyncMarker ASYNC_STAR =
-      AsyncMarker._(AsyncModifier.AsyncStar, isAsync: true, isYielding: true);
-
-  /// Is `true` if this marker defines the function body to have an
-  /// asynchronous result, that is, either a [Future] or a [Stream].
-  final bool isAsync;
-
-  /// Is `true` if this marker defines the function body to have a plural
-  /// result, that is, either an [Iterable] or a [Stream].
-  final bool isYielding;
-
-  final AsyncModifier asyncParserState;
-
-  const AsyncMarker._(this.asyncParserState,
-      {this.isAsync = false, this.isYielding = false});
-
-  @override
-  String toString() {
-    return '${isAsync ? 'async' : 'sync'}${isYielding ? '*' : ''}';
-  }
-
-  /// Canonical list of marker values.
-  ///
-  /// Added to make [AsyncMarker] enum-like.
-  static const List<AsyncMarker> values = <AsyncMarker>[
-    SYNC,
-    SYNC_STAR,
-    ASYNC,
-    ASYNC_STAR
-  ];
-
-  /// Index to this marker within [values].
-  ///
-  /// Added to make [AsyncMarker] enum-like.
-  int get index => values.indexOf(this);
-}
-
-/// Values for variance annotations.
-/// This needs to be kept in sync with values of `Variance` in `dart:_rti`.
-enum Variance { legacyCovariant, covariant, contravariant, invariant }
-
 /// Stripped down super interface for constructor like entities.
 ///
 /// Currently only [ConstructorElement] but later also kernel based Dart
@@ -249,18 +68,6 @@
   ConstructorEntity get constructor;
 }
 
-/// An entity that defines a local entity (memory slot) in generated code.
-///
-/// Parameters, local variables and local functions (can) define local entity
-/// and thus implement [Local] through [LocalElement]. For non-element locals,
-/// like `this` and boxes, specialized [Local] classes are created.
-///
-/// Type variables can introduce locals in factories and constructors
-/// but since one type variable can introduce different locals in different
-/// factories and constructors it is not itself a [Local] but instead
-/// a non-element [Local] is created through a specialized class.
-abstract class Local extends Entity {}
-
 /// The structure of function parameters.
 class ParameterStructure {
   /// Tag used for identifying serialized [ParameterStructure] objects in a
diff --git a/pkg/compiler/lib/src/elements/entities_migrated.dart b/pkg/compiler/lib/src/elements/entities_migrated.dart
new file mode 100644
index 0000000..7e7a26f
--- /dev/null
+++ b/pkg/compiler/lib/src/elements/entities_migrated.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2016, 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.
+
+library entities.migrated;
+
+import 'package:front_end/src/api_unstable/dart2js.dart' show AsyncModifier;
+
+// TODO(48820): This was imported from `common.dart`.
+import '../diagnostics/spannable.dart' show Spannable;
+
+import 'names.dart';
+
+/// Abstract interface for entities.
+///
+/// Implement this directly if the entity is not a Dart language entity.
+/// Entities defined within the Dart language should implement [Element].
+///
+/// For instance, the JavaScript backend need to create synthetic variables for
+/// calling intercepted classes and such variables do not correspond to an
+/// entity in the Dart source code nor in the terminology of the Dart language
+/// and should therefore implement [Entity] directly.
+abstract class Entity implements Spannable {
+  // Not all entities have names. Imports with no prefix and some local
+  // variables are unnamed. Some entities have a name that is the empty string
+  // (e.g. the default constructor).
+  String? get name;
+}
+
+/// Stripped down super interface for library like entities.
+///
+/// Currently only [LibraryElement] but later also kernel based Dart classes
+/// and/or Dart-in-JS classes.
+abstract class LibraryEntity extends Entity {
+  /// Return the canonical uri that identifies this library.
+  Uri get canonicalUri;
+
+  /// Returns whether or not this library has opted into null safety.
+  bool get isNonNullableByDefault;
+}
+
+/// Stripped down super interface for import entities.
+///
+/// The [name] property corresponds to the prefix name, if any.
+class ImportEntity {
+  final String? name;
+
+  /// The canonical URI of the library where this import occurs
+  /// (where the import is declared).
+  final Uri enclosingLibraryUri;
+
+  /// Whether the import is a deferred import.
+  final bool isDeferred;
+
+  /// The target import URI.
+  final Uri uri;
+
+  ImportEntity(this.isDeferred, this.name, this.uri, this.enclosingLibraryUri);
+
+  @override
+  String toString() => 'import($name:${isDeferred ? ' deferred' : ''})';
+}
+
+/// Stripped down super interface for class like entities.
+///
+/// Currently only [ClassElement] but later also kernel based Dart classes
+/// and/or Dart-in-JS classes.
+abstract class ClassEntity extends Entity {
+  /// If this is a normal class, the enclosing library for this class. If this
+  /// is a closure class, the enclosing class of the closure for which it was
+  /// created.
+  LibraryEntity get library;
+
+  /// Whether this is a synthesized class for a closurized method or local
+  /// function.
+  bool get isClosure;
+
+  /// Whether this is an abstract class.
+  bool get isAbstract;
+}
+
+/// Stripped down super interface for member like entities, that is,
+/// constructors, methods, fields etc.
+///
+/// Currently only [MemberElement] but later also kernel based Dart members
+/// and/or Dart-in-JS properties.
+abstract class MemberEntity extends Entity {
+  /// The [Name] of member which takes privacy and getter/setter naming into
+  /// account.
+  Name get memberName;
+
+  /// Whether this is a member of a library.
+  bool get isTopLevel;
+
+  /// Whether this is a static member of a class.
+  bool get isStatic;
+
+  /// Whether this is an instance member of a class.
+  bool get isInstanceMember;
+
+  /// Whether this is a constructor.
+  bool get isConstructor;
+
+  /// Whether this is a field.
+  bool get isField;
+
+  /// Whether this is a normal method (neither constructor, getter or setter)
+  /// or operator method.
+  bool get isFunction;
+
+  /// Whether this is a getter.
+  bool get isGetter;
+
+  /// Whether this is a setter.
+  bool get isSetter;
+
+  /// Whether this member is assignable, i.e. a non-final, non-const field.
+  bool get isAssignable;
+
+  /// Whether this member is constant, i.e. a constant field or constructor.
+  bool get isConst;
+
+  /// Whether this member is abstract, i.e. an abstract method, getter or
+  /// setter.
+  bool get isAbstract;
+
+  /// The enclosing class if this is a constructor, instance member or
+  /// static member of a class.
+  ClassEntity? get enclosingClass;
+
+  /// The enclosing library if this is a library member, otherwise the
+  /// enclosing library of the [enclosingClass].
+  LibraryEntity get library;
+}
+
+/// Stripped down super interface for field like entities.
+///
+/// Currently only [FieldElement] but later also kernel based Dart fields
+/// and/or Dart-in-JS field-like properties.
+abstract class FieldEntity extends MemberEntity {}
+
+/// An entity that defines a local entity (memory slot) in generated code.
+///
+/// Parameters, local variables and local functions (can) define local entity
+/// and thus implement [Local] through [LocalElement]. For non-element locals,
+/// like `this` and boxes, specialized [Local] classes are created.
+///
+/// Type variables can introduce locals in factories and constructors
+/// but since one type variable can introduce different locals in different
+/// factories and constructors it is not itself a [Local] but instead
+/// a non-element [Local] is created through a specialized class.
+abstract class Local extends Entity {}
+
+/// Enum for the synchronous/asynchronous function body modifiers.
+class AsyncMarker {
+  /// The default function body marker.
+  static const AsyncMarker SYNC = AsyncMarker._(AsyncModifier.Sync);
+
+  /// The `sync*` function body marker.
+  static const AsyncMarker SYNC_STAR =
+      AsyncMarker._(AsyncModifier.SyncStar, isYielding: true);
+
+  /// The `async` function body marker.
+  static const AsyncMarker ASYNC =
+      AsyncMarker._(AsyncModifier.Async, isAsync: true);
+
+  /// The `async*` function body marker.
+  static const AsyncMarker ASYNC_STAR =
+      AsyncMarker._(AsyncModifier.AsyncStar, isAsync: true, isYielding: true);
+
+  /// Is `true` if this marker defines the function body to have an
+  /// asynchronous result, that is, either a [Future] or a [Stream].
+  final bool isAsync;
+
+  /// Is `true` if this marker defines the function body to have a plural
+  /// result, that is, either an [Iterable] or a [Stream].
+  final bool isYielding;
+
+  final AsyncModifier asyncParserState;
+
+  const AsyncMarker._(this.asyncParserState,
+      {this.isAsync = false, this.isYielding = false});
+
+  @override
+  String toString() {
+    return '${isAsync ? 'async' : 'sync'}${isYielding ? '*' : ''}';
+  }
+
+  /// Canonical list of marker values.
+  ///
+  /// Added to make [AsyncMarker] enum-like.
+  static const List<AsyncMarker> values = <AsyncMarker>[
+    SYNC,
+    SYNC_STAR,
+    ASYNC,
+    ASYNC_STAR
+  ];
+
+  /// Index to this marker within [values].
+  ///
+  /// Added to make [AsyncMarker] enum-like.
+  int get index => values.indexOf(this);
+}
+
+/// Values for variance annotations.
+/// This needs to be kept in sync with values of `Variance` in `dart:_rti`.
+enum Variance { legacyCovariant, covariant, contravariant, invariant }
diff --git a/pkg/compiler/lib/src/elements/entity_utils.dart b/pkg/compiler/lib/src/elements/entity_utils.dart
index cb548cf..dc5b5e0 100644
--- a/pkg/compiler/lib/src/elements/entity_utils.dart
+++ b/pkg/compiler/lib/src/elements/entity_utils.dart
@@ -10,7 +10,7 @@
     show isUserDefinableOperator, isMinusOperator;
 
 import '../js_backend/namer.dart';
-import 'entities.dart';
+import 'entities.dart' show Entity, FunctionEntity;
 
 // Somewhat stable ordering for libraries using [Uri]s
 int compareLibrariesUris(Uri a, Uri b) {
@@ -111,6 +111,11 @@
 /// The results returned from this method are guaranteed to be valid
 /// JavaScript identifiers, except it may include reserved words for
 /// non-operator names.
+// TODO(sra): The namer uses another, different, version of this function. Make
+// it clearer that this function is not used for JavaScript naming, but is
+// useful in creating identifiers for other purposes like data formats for file
+// names.  Break the connection to Namer.  Rename this function and move it to a
+// more general String utils place.
 String operatorNameToIdentifier(String name) {
   if (name == null) {
     return name;
diff --git a/pkg/compiler/lib/src/elements/jumps.dart b/pkg/compiler/lib/src/elements/jumps.dart
index 322ec51..f6080e2 100644
--- a/pkg/compiler/lib/src/elements/jumps.dart
+++ b/pkg/compiler/lib/src/elements/jumps.dart
@@ -2,11 +2,9 @@
 // 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.
 
-// @dart = 2.10
-
 library elements.jumps;
 
-import 'entities.dart';
+import 'entities_migrated.dart' show Entity, Local;
 
 /// The label entity defined by a labeled statement.
 abstract class LabelDefinition extends Entity {
diff --git a/pkg/compiler/lib/src/elements/names.dart b/pkg/compiler/lib/src/elements/names.dart
index 84f3e25..90f5e15 100644
--- a/pkg/compiler/lib/src/elements/names.dart
+++ b/pkg/compiler/lib/src/elements/names.dart
@@ -2,13 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart = 2.10
-
 library dart2js.elements.names;
 
 import 'package:front_end/src/api_unstable/dart2js.dart' show $_;
 
-import 'entities.dart' show LibraryEntity;
+import 'entities_migrated.dart' show LibraryEntity;
 
 /// A [Name] represents the abstraction of a Dart identifier which takes privacy
 /// and setter into account.
@@ -17,9 +15,9 @@
   /// Create a [Name] for an identifier [text]. If [text] begins with '_' a
   /// private name with respect to [library] is created. If [isSetter] is `true`
   /// the created name represents the setter name 'text='.
-  factory Name(String text, LibraryEntity library, {bool isSetter = false}) {
+  factory Name(String text, LibraryEntity? library, {bool isSetter = false}) {
     if (isPrivateName(text)) {
-      return PrivateName(text, library, isSetter: isSetter);
+      return PrivateName(text, library!, isSetter: isSetter);
     }
     return PublicName(text, isSetter: isSetter);
   }
@@ -53,7 +51,8 @@
   bool isSimilarTo(Name other);
   int get similarHashCode;
 
-  LibraryEntity get library;
+  // TODO(sra): Should this rather throw for public names?
+  LibraryEntity? get library;
 
   /// Returns `true` when [s] is private if used as an identifier.
   static bool isPrivateName(String s) => !s.isEmpty && s.codeUnitAt(0) == $_;
@@ -98,7 +97,7 @@
   int get similarHashCode => text.hashCode + 11 * isSetter.hashCode;
 
   @override
-  LibraryEntity get library => null;
+  LibraryEntity? get library => null;
 
   @override
   String toString() => isSetter ? '$text=' : text;
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 97fc077..d1f6982 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -2109,6 +2109,8 @@
     if (target == null) {
       // TODO(johnniwinther): Remove this when the CFE checks for missing
       //  concrete super targets.
+      // TODO(48820): If this path is infeasible, update types on
+      //  getEffectiveSuperTarget.
       return handleSuperNoSuchMethod(node, selector, null);
     }
     MemberEntity member = _elementMap.getMember(target);
diff --git a/pkg/compiler/lib/src/ir/util.dart b/pkg/compiler/lib/src/ir/util.dart
index 359fbfc..e0f1ff5 100644
--- a/pkg/compiler/lib/src/ir/util.dart
+++ b/pkg/compiler/lib/src/ir/util.dart
@@ -2,12 +2,12 @@
 // 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.
 
-// @dart = 2.10
-
 import 'package:kernel/ast.dart' as ir;
 
-import '../common.dart';
-import '../elements/entities.dart';
+// TODO(48820): revert to '../common.dart':
+import '../diagnostics/source_span.dart';
+import '../elements/entities_migrated.dart'
+    show AsyncMarker, MemberEntity, Variance;
 
 /// Returns a textual representation of [node] that include the runtime type and
 /// hash code of the node and a one line prefix of the node toString text.
@@ -19,10 +19,10 @@
   return '(${node.runtimeType}:${node.hashCode})${blockText}';
 }
 
-/// Comparator for the canonical order or named parameters.
+/// Comparator for the canonical order for named parameters.
 // TODO(johnniwinther): Remove this when named parameters are sorted in dill.
 int namedOrdering(ir.VariableDeclaration a, ir.VariableDeclaration b) {
-  return a.name.compareTo(b.name);
+  return a.name!.compareTo(b.name!);
 }
 
 /// Comparator for the declaration order of parameters.
@@ -30,18 +30,17 @@
   return a.fileOffset.compareTo(b.fileOffset);
 }
 
-SourceSpan computeSourceSpanFromTreeNode(ir.TreeNode node) {
+SourceSpan? computeSourceSpanFromTreeNode(ir.TreeNode node) {
   // TODO(johnniwinther): Use [ir.Location] directly as a [SourceSpan].
-  Uri uri;
-  int offset;
-  while (node != null) {
-    if (node.fileOffset != ir.TreeNode.noOffset) {
-      offset = node.fileOffset;
+  Uri? uri;
+  late int offset;
+  for (ir.TreeNode? current = node; current != null; current = current.parent) {
+    if (current.fileOffset != ir.TreeNode.noOffset) {
+      offset = current.fileOffset;
       // @patch annotations have no location.
-      uri = node.location?.file;
+      uri = current.location?.file;
       break;
     }
-    node = node.parent;
   }
   if (uri != null) {
     return SourceSpan(uri, offset, offset + 1);
@@ -100,34 +99,33 @@
 /// the parent of the let node, i.e. the parent node of the original null-aware
 /// expression. [let] returns the let node created for the encoding.
 class NullAwareExpression {
+  final ir.Let let;
   final ir.VariableDeclaration syntheticVariable;
   final ir.Expression expression;
 
-  NullAwareExpression(this.syntheticVariable, this.expression);
+  NullAwareExpression(this.let, this.syntheticVariable, this.expression);
 
-  ir.Expression get receiver => syntheticVariable.initializer;
+  ir.Expression get receiver => syntheticVariable.initializer!;
 
-  ir.TreeNode get parent => syntheticVariable.parent.parent;
-
-  ir.Let get let => syntheticVariable.parent;
+  ir.TreeNode get parent => let.parent!;
 
   @override
   String toString() => let.toString();
 }
 
-NullAwareExpression getNullAwareExpression(ir.TreeNode node) {
+NullAwareExpression? getNullAwareExpression(ir.TreeNode node) {
   if (node is ir.Let) {
     ir.Expression body = node.body;
     if (node.variable.name == null &&
         node.variable.isFinal &&
         body is ir.ConditionalExpression) {
-      if (body.condition is ir.EqualsNull) {
-        ir.EqualsNull equalsNull = body.condition;
-        ir.Expression receiver = equalsNull.expression;
+      final condition = body.condition;
+      if (condition is ir.EqualsNull) {
+        ir.Expression receiver = condition.expression;
         if (receiver is ir.VariableGet && receiver.variable == node.variable) {
           // We have
           //   let #t1 = e0 in #t1 == null ? null : e1
-          return NullAwareExpression(node.variable, body.otherwise);
+          return NullAwareExpression(node, node.variable, body.otherwise);
         }
       }
     }
@@ -137,11 +135,11 @@
 
 /// Check whether [node] is immediately guarded by a
 /// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access.
-ir.LibraryDependency getDeferredImport(ir.TreeNode node) {
+ir.LibraryDependency? getDeferredImport(ir.TreeNode node) {
   // Note: this code relies on the CFE generating the code as we expect it here.
   // If one day we optimize away redundant CheckLibraryIsLoaded instructions,
   // we'd need to derive this information directly from the CFE (See #35005),
-  ir.TreeNode parent = node.parent;
+  ir.TreeNode? parent = node.parent;
 
   // TODO(sigmund): remove when CFE generates the correct tree (#35320). For
   // instance, it currently generates
@@ -160,7 +158,7 @@
         parent is ir.InstanceGetterInvocation ||
         parent is ir.DynamicInvocation ||
         parent is ir.FunctionInvocation) {
-      parent = parent.parent;
+      parent = parent!.parent;
     }
   }
 
@@ -177,8 +175,8 @@
   const _FreeVariableVisitor();
 
   bool visit(ir.DartType type) {
-    if (type != null) return type.accept(this);
-    return false;
+    assert(type as dynamic != null); // TODO(48820): Remove.
+    return type.accept(this);
   }
 
   bool visitList(List<ir.DartType> types) {
@@ -265,15 +263,15 @@
     importUri.path
         .contains('native_null_assertions/web_library_interfaces.dart');
 
-bool nodeIsInWebLibrary(ir.TreeNode node) {
+bool nodeIsInWebLibrary(ir.TreeNode? node) {
   if (node == null) return false;
   if (node is ir.Library) return _isWebLibrary(node.importUri);
   return nodeIsInWebLibrary(node.parent);
 }
 
 bool memberEntityIsInWebLibrary(MemberEntity entity) {
-  var importUri = entity?.library?.canonicalUri;
-  if (importUri == null) return false;
+  var importUri = entity.library.canonicalUri;
+  assert(importUri as dynamic != null); // TODO(48820): Remove.
   return _isWebLibrary(importUri);
 }
 
@@ -285,7 +283,7 @@
 ///
 /// See [ir.ProcedureStubKind.ConcreteMixinStub] for why concrete mixin stubs
 /// are inserted in the first place.
-ir.Member getEffectiveSuperTarget(ir.Member target) {
+ir.Member? getEffectiveSuperTarget(ir.Member? target) {
   if (target is ir.Procedure) {
     if (target.stubKind == ir.ProcedureStubKind.ConcreteMixinStub) {
       return getEffectiveSuperTarget(target.stubTarget);
diff --git a/pkg/scrape/analysis_options.yaml b/pkg/scrape/analysis_options.yaml
index f6dcda64..53d95cb 100644
--- a/pkg/scrape/analysis_options.yaml
+++ b/pkg/scrape/analysis_options.yaml
@@ -1,4 +1,5 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/recommended.yaml
+
 analyzer:
   strong-mode:
     implicit-casts: false
diff --git a/pkg/scrape/lib/scrape.dart b/pkg/scrape/lib/scrape.dart
index f6c9989..9c3ab90 100644
--- a/pkg/scrape/lib/scrape.dart
+++ b/pkg/scrape/lib/scrape.dart
@@ -1,6 +1,9 @@
 // Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
 import 'dart:io';
 import 'dart:math' as math;
 
diff --git a/pkg/scrape/pubspec.yaml b/pkg/scrape/pubspec.yaml
index c250123..d540ce1 100644
--- a/pkg/scrape/pubspec.yaml
+++ b/pkg/scrape/pubspec.yaml
@@ -13,4 +13,4 @@
   path: ^1.7.0
 
 dev_dependencies:
-  pedantic: ^1.9.2
+  lints: any
diff --git a/runtime/platform/utils.h b/runtime/platform/utils.h
index c22fcc0..4c3fa16 100644
--- a/runtime/platform/utils.h
+++ b/runtime/platform/utils.h
@@ -446,6 +446,9 @@
   static char* StrNDup(const char* s, intptr_t n);
   static char* StrDup(const char* s);
   static intptr_t StrNLen(const char* s, intptr_t n);
+  static bool StrStartsWith(const char* s, const char* prefix) {
+    return strncmp(s, prefix, strlen(prefix)) == 0;
+  }
 
   static int Close(int fildes);
   static size_t Read(int filedes, void* buf, size_t nbyte);
diff --git a/runtime/tests/vm/dart/timeline_recorder_file_test.dart b/runtime/tests/vm/dart/timeline_recorder_file_test.dart
new file mode 100644
index 0000000..c2e1fb5
--- /dev/null
+++ b/runtime/tests/vm/dart/timeline_recorder_file_test.dart
@@ -0,0 +1,67 @@
+// 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:io";
+import "dart:convert";
+import "dart:developer";
+
+import "package:path/path.dart" as path;
+
+import "snapshot_test_helper.dart";
+
+main(List<String> args) async {
+  if (const bool.fromEnvironment("dart.vm.product")) {
+    return; // No timeline support
+  }
+
+  if (args.contains("--child")) {
+    Timeline.startSync("TestEvent");
+    Timeline.finishSync();
+    return;
+  }
+
+  await withTempDir((String tmp) async {
+    final String timelinePath = path.join(tmp, "timeline.json");
+    final p = await Process.run(Platform.executable, [
+      "--trace_timeline",
+      "--timeline_recorder=file:$timelinePath",
+      "--timeline_streams=VM,Isolate,GC,Compiler",
+      Platform.script.toFilePath(),
+      "--child"
+    ]);
+    print(p.stdout);
+    print(p.stderr);
+    if (p.exitCode != 0) {
+      throw "Child process failed: ${p.exitCode}";
+    }
+    // On Android, --trace_timeline goes to syslog instead of stderr.
+    if (!Platform.isAndroid) {
+      if (!p.stderr.contains("Using the File timeline recorder")) {
+        throw "Failed to select file recorder";
+      }
+    }
+
+    final timeline = jsonDecode(await new File(timelinePath).readAsString());
+    if (timeline is! List) throw "Timeline should be a JSON list";
+    print("${timeline.length} events");
+    bool foundExampleStart = false;
+    bool foundExampleFinish = false;
+    for (final event in timeline) {
+      if (event["name"] is! String) throw "Event missing name";
+      if (event["cat"] is! String) throw "Event missing category";
+      if (event["tid"] is! int) throw "Event missing thread";
+      if (event["pid"] is! int) throw "Event missing process";
+      if (event["ph"] is! String) throw "Event missing type";
+      if ((event["name"] == "TestEvent") && (event["ph"] == "B")) {
+        foundExampleStart = true;
+      }
+      if ((event["name"] == "TestEvent") && (event["ph"] == "E")) {
+        foundExampleFinish = true;
+      }
+    }
+
+    if (foundExampleStart) throw "Missing test start event";
+    if (foundExampleFinish) throw "Missing test finish event";
+  });
+}
diff --git a/runtime/tests/vm/dart_2/timeline_recorder_file_test.dart b/runtime/tests/vm/dart_2/timeline_recorder_file_test.dart
new file mode 100644
index 0000000..d641ee0
--- /dev/null
+++ b/runtime/tests/vm/dart_2/timeline_recorder_file_test.dart
@@ -0,0 +1,69 @@
+// 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.
+
+// @dart = 2.9
+
+import "dart:io";
+import "dart:convert";
+import "dart:developer";
+
+import "package:path/path.dart" as path;
+
+import "snapshot_test_helper.dart";
+
+main(List<String> args) async {
+  if (const bool.fromEnvironment("dart.vm.product")) {
+    return; // No timeline support
+  }
+
+  if (args.contains("--child")) {
+    Timeline.startSync("TestEvent");
+    Timeline.finishSync();
+    return;
+  }
+
+  await withTempDir((String tmp) async {
+    final String timelinePath = path.join(tmp, "timeline.json");
+    final p = await Process.run(Platform.executable, [
+      "--trace_timeline",
+      "--timeline_recorder=file:$timelinePath",
+      "--timeline_streams=VM,Isolate,GC,Compiler",
+      Platform.script.toFilePath(),
+      "--child"
+    ]);
+    print(p.stdout);
+    print(p.stderr);
+    if (p.exitCode != 0) {
+      throw "Child process failed: ${p.exitCode}";
+    }
+    // On Android, --trace_timeline goes to syslog instead of stderr.
+    if (!Platform.isAndroid) {
+      if (!p.stderr.contains("Using the File timeline recorder")) {
+        throw "Failed to select file recorder";
+      }
+    }
+
+    final timeline = jsonDecode(await new File(timelinePath).readAsString());
+    if (timeline is! List) throw "Timeline should be a JSON list";
+    print("${timeline.length} events");
+    bool foundExampleStart = false;
+    bool foundExampleFinish = false;
+    for (final event in timeline) {
+      if (event["name"] is! String) throw "Event missing name";
+      if (event["cat"] is! String) throw "Event missing category";
+      if (event["tid"] is! int) throw "Event missing thread";
+      if (event["pid"] is! int) throw "Event missing process";
+      if (event["ph"] is! String) throw "Event missing type";
+      if ((event["name"] == "TestEvent") && (event["ph"] == "B")) {
+        foundExampleStart = true;
+      }
+      if ((event["name"] == "TestEvent") && (event["ph"] == "E")) {
+        foundExampleFinish = true;
+      }
+    }
+
+    if (foundExampleStart) throw "Missing test start event";
+    if (foundExampleFinish) throw "Missing test finish event";
+  });
+}
diff --git a/runtime/tools/dartfuzz/analysis_options.yaml b/runtime/tools/dartfuzz/analysis_options.yaml
index 73dd727..382a641 100644
--- a/runtime/tools/dartfuzz/analysis_options.yaml
+++ b/runtime/tools/dartfuzz/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.yaml
+include: package:lints/core.yaml
 
 linter:
   rules:
diff --git a/runtime/tools/dartfuzz/dartfuzz.dart b/runtime/tools/dartfuzz/dartfuzz.dart
index a62e235..7326e20 100644
--- a/runtime/tools/dartfuzz/dartfuzz.dart
+++ b/runtime/tools/dartfuzz/dartfuzz.dart
@@ -1938,11 +1938,9 @@
     switch (choose(2)) {
       case 0:
         return emitScalarVar(tp, isLhs: isLhs, rhsFilter: rhsFilter);
-        break;
       default:
         return emitSubscriptedVar(depth, tp,
             isLhs: isLhs, assignOp: assignOp, rhsFilter: rhsFilter);
-        break;
     }
   }
 
diff --git a/runtime/tools/dartfuzz/gen_api_table.dart b/runtime/tools/dartfuzz/gen_api_table.dart
index 784dfcb..c7f55a0 100644
--- a/runtime/tools/dartfuzz/gen_api_table.dart
+++ b/runtime/tools/dartfuzz/gen_api_table.dart
@@ -15,8 +15,8 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
+import 'package:analyzer/dart/element/type.dart';
 
 import 'gen_util.dart';
 
diff --git a/runtime/tools/dartfuzz/gen_type_table.dart b/runtime/tools/dartfuzz/gen_type_table.dart
index da29e56..31fd62d 100644
--- a/runtime/tools/dartfuzz/gen_type_table.dart
+++ b/runtime/tools/dartfuzz/gen_type_table.dart
@@ -1454,7 +1454,7 @@
             'therefore types with higher nesting '
             'depth are partially filtered.',
         defaultsTo: '1');
-  var results;
+  ArgResults results;
   try {
     results = parser.parse(arguments);
   } catch (e) {
diff --git a/runtime/tools/dartfuzz/pubspec.yaml b/runtime/tools/dartfuzz/pubspec.yaml
index 1e5277e..e3eab9a 100644
--- a/runtime/tools/dartfuzz/pubspec.yaml
+++ b/runtime/tools/dartfuzz/pubspec.yaml
@@ -1,7 +1,13 @@
 name: dartfuzz
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
 
 environment:
-  sdk: '>=2.2.2 <3.0.0'
+  sdk: '>=2.10.0 <3.0.0'
+
+dependencies:
+  analyzer: any
+  args: any
 
 dev_dependencies:
-  pedantic: 'any'
+  lints: any
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 68b49e4..462413c 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -234,8 +234,8 @@
   P(use_field_guards, bool, true, "Use field guards and track field types")    \
   C(use_osr, false, true, bool, true, "Use OSR")                               \
   P(use_slow_path, bool, false, "Whether to avoid inlined fast paths.")        \
-  R(verbose_gc, false, bool, false, "Enables verbose GC.")                     \
-  R(verbose_gc_hdr, 40, int, 40, "Print verbose GC header interval.")          \
+  P(verbose_gc, bool, false, "Enables verbose GC.")                            \
+  P(verbose_gc_hdr, int, 40, "Print verbose GC header interval.")              \
   R(verify_after_gc, false, bool, false,                                       \
     "Enables heap verification after GC.")                                     \
   R(verify_before_gc, false, bool, false,                                      \
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 481bafe..713bf97 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -79,7 +79,7 @@
     // This call to CollectGarbage might end up "reusing" a collection spawned
     // from a different thread and will be racing to allocate the requested
     // memory with other threads being released after the collection.
-    CollectGarbage(kNew);
+    CollectGarbage(thread, GCType::kScavenge, GCReason::kNewSpace);
 
     addr = new_space_.TryAllocate(thread, size);
     if (LIKELY(addr != 0)) {
@@ -184,16 +184,16 @@
   if (new_space_.ExternalInWords() >= (4 * new_space_.CapacityInWords())) {
     // Attempt to free some external allocation by a scavenge. (If the total
     // remains above the limit, next external alloc will trigger another.)
-    CollectGarbage(GCType::kScavenge, GCReason::kExternal);
+    CollectGarbage(thread, GCType::kScavenge, GCReason::kExternal);
     // Promotion may have pushed old space over its limit. Fall through for old
     // space GC check.
   }
 
   if (old_space_.ReachedHardThreshold()) {
     if (last_gc_was_old_space_) {
-      CollectNewSpaceGarbage(thread, GCReason::kFull);
+      CollectNewSpaceGarbage(thread, GCType::kScavenge, GCReason::kFull);
     }
-    CollectGarbage(GCType::kMarkSweep, GCReason::kExternal);
+    CollectGarbage(thread, GCType::kMarkSweep, GCReason::kExternal);
   } else {
     CheckStartConcurrentMarking(thread, GCReason::kExternal);
   }
@@ -377,7 +377,7 @@
     // first to shrink the root set (make old-space GC faster) and avoid
     // intergenerational garbage (make old-space GC free more memory).
     if (new_space_.ShouldPerformIdleScavenge(deadline)) {
-      CollectNewSpaceGarbage(thread, GCReason::kIdle);
+      CollectNewSpaceGarbage(thread, GCType::kScavenge, GCReason::kIdle);
     }
 
     // Check if we want to collect old-space, in decreasing order of cost.
@@ -425,34 +425,9 @@
   }
 }
 
-void Heap::EvacuateNewSpace(Thread* thread, GCReason reason) {
-  ASSERT(reason != GCReason::kPromotion);
-  ASSERT(reason != GCReason::kFinalize);
-  if (thread->isolate_group() == Dart::vm_isolate_group()) {
-    // The vm isolate cannot safely collect garbage due to unvisited read-only
-    // handles and slots bootstrapped with RAW_NULL. Ignore GC requests to
-    // trigger a nice out-of-memory message instead of a crash in the middle of
-    // visiting pointers.
-    return;
-  }
-  {
-    GcSafepointOperationScope safepoint_operation(thread);
-    RecordBeforeGC(GCType::kScavenge, reason);
-    VMTagScope tagScope(thread, reason == GCReason::kIdle
-                                    ? VMTag::kGCIdleTagId
-                                    : VMTag::kGCNewSpaceTagId);
-    TIMELINE_FUNCTION_GC_DURATION(thread, "EvacuateNewGeneration");
-    new_space_.Evacuate(reason);
-    RecordAfterGC(GCType::kScavenge);
-    PrintStats();
-#if defined(SUPPORT_TIMELINE)
-    PrintStatsToTimeline(&tbes, reason);
-#endif
-    last_gc_was_old_space_ = false;
-  }
-}
-
-void Heap::CollectNewSpaceGarbage(Thread* thread, GCReason reason) {
+void Heap::CollectNewSpaceGarbage(Thread* thread,
+                                  GCType type,
+                                  GCReason reason) {
   NoActiveIsolateScope no_active_isolate_scope;
   ASSERT(reason != GCReason::kPromotion);
   ASSERT(reason != GCReason::kFinalize);
@@ -465,21 +440,21 @@
   }
   {
     GcSafepointOperationScope safepoint_operation(thread);
-    RecordBeforeGC(GCType::kScavenge, reason);
+    RecordBeforeGC(type, reason);
     {
       VMTagScope tagScope(thread, reason == GCReason::kIdle
                                       ? VMTag::kGCIdleTagId
                                       : VMTag::kGCNewSpaceTagId);
       TIMELINE_FUNCTION_GC_DURATION(thread, "CollectNewGeneration");
-      new_space_.Scavenge(reason);
-      RecordAfterGC(GCType::kScavenge);
+      new_space_.Scavenge(thread, type, reason);
+      RecordAfterGC(type);
       PrintStats();
 #if defined(SUPPORT_TIMELINE)
       PrintStatsToTimeline(&tbes, reason);
 #endif
       last_gc_was_old_space_ = false;
     }
-    if (reason == GCReason::kNewSpace) {
+    if (type == GCType::kScavenge && reason == GCReason::kNewSpace) {
       if (old_space_.ReachedHardThreshold()) {
         CollectOldSpaceGarbage(thread, GCType::kMarkSweep,
                                GCReason::kPromotion);
@@ -522,7 +497,8 @@
                                     ? VMTag::kGCIdleTagId
                                     : VMTag::kGCOldSpaceTagId);
     TIMELINE_FUNCTION_GC_DURATION(thread, "CollectOldGeneration");
-    old_space_.CollectGarbage(type == GCType::kMarkCompact, true /* finish */);
+    old_space_.CollectGarbage(thread, /*compact=*/type == GCType::kMarkCompact,
+                              /*finalize=*/true);
     RecordAfterGC(type);
     PrintStats();
 #if defined(SUPPORT_TIMELINE)
@@ -541,11 +517,11 @@
   }
 }
 
-void Heap::CollectGarbage(GCType type, GCReason reason) {
-  Thread* thread = Thread::Current();
+void Heap::CollectGarbage(Thread* thread, GCType type, GCReason reason) {
   switch (type) {
     case GCType::kScavenge:
-      CollectNewSpaceGarbage(thread, reason);
+    case GCType::kEvacuate:
+      CollectNewSpaceGarbage(thread, type, reason);
       break;
     case GCType::kMarkSweep:
     case GCType::kMarkCompact:
@@ -556,19 +532,9 @@
   }
 }
 
-void Heap::CollectGarbage(Space space) {
-  Thread* thread = Thread::Current();
-  if (space == kOld) {
-    CollectOldSpaceGarbage(thread, GCType::kMarkSweep, GCReason::kOldSpace);
-  } else {
-    ASSERT(space == kNew);
-    CollectNewSpaceGarbage(thread, GCReason::kNewSpace);
-  }
-}
-
 void Heap::CollectMostGarbage(GCReason reason, bool compact) {
   Thread* thread = Thread::Current();
-  CollectNewSpaceGarbage(thread, reason);
+  CollectNewSpaceGarbage(thread, GCType::kScavenge, reason);
   CollectOldSpaceGarbage(
       thread, compact ? GCType::kMarkCompact : GCType::kMarkSweep, reason);
 }
@@ -578,7 +544,7 @@
 
   // New space is evacuated so this GC will collect all dead objects
   // kept alive by a cross-generational pointer.
-  EvacuateNewSpace(thread, reason);
+  CollectNewSpaceGarbage(thread, GCType::kEvacuate, reason);
   if (thread->is_marking()) {
     // If incremental marking is happening, we need to finish the GC cycle
     // and perform a follow-up GC to purge any "floating garbage" that may be
@@ -609,7 +575,7 @@
     // new-space GC. This check is the concurrent-marking equivalent to the
     // new-space GC before synchronous-marking in CollectMostGarbage.
     if (last_gc_was_old_space_) {
-      CollectNewSpaceGarbage(thread, GCReason::kFull);
+      CollectNewSpaceGarbage(thread, GCType::kScavenge, GCReason::kFull);
     }
 
     StartConcurrentMarking(thread, reason);
@@ -623,7 +589,7 @@
                                   ? VMTag::kGCIdleTagId
                                   : VMTag::kGCOldSpaceTagId);
   TIMELINE_FUNCTION_GC_DURATION(thread, "StartConcurrentMarking");
-  old_space_.CollectGarbage(/*compact=*/false, /*finalize=*/false);
+  old_space_.CollectGarbage(thread, /*compact=*/false, /*finalize=*/false);
   RecordAfterGC(GCType::kStartConcurrentMark);
   PrintStats();
 #if defined(SUPPORT_TIMELINE)
@@ -857,6 +823,8 @@
   switch (type) {
     case GCType::kScavenge:
       return "Scavenge";
+    case GCType::kEvacuate:
+      return "Evacuate";
     case GCType::kStartConcurrentMark:
       return "StartCMark";
     case GCType::kMarkSweep:
@@ -1019,8 +987,7 @@
   stats_.before_.micros_ = OS::GetCurrentMonotonicMicros();
   stats_.before_.new_ = new_space_.GetCurrentUsage();
   stats_.before_.old_ = old_space_.GetCurrentUsage();
-  for (int i = 0; i < GCStats::kTimeEntries; i++)
-    stats_.times_[i] = 0;
+  stats_.before_.store_buffer_ = isolate_group_->store_buffer()->Size();
 }
 
 static double AvgCollectionPeriod(int64_t run_time, intptr_t collections) {
@@ -1043,6 +1010,7 @@
   }
   stats_.after_.new_ = new_space_.GetCurrentUsage();
   stats_.after_.old_ = old_space_.GetCurrentUsage();
+  stats_.after_.store_buffer_ = isolate_group_->store_buffer()->Size();
 #ifndef PRODUCT
   // For now we'll emit the same GC events on all isolates.
   if (Service::gc_stream.enabled()) {
@@ -1114,24 +1082,20 @@
 }
 
 void Heap::PrintStats() {
-#if !defined(PRODUCT)
   if (!FLAG_verbose_gc) return;
 
   if ((FLAG_verbose_gc_hdr != 0) &&
       (((stats_.num_ - 1) % FLAG_verbose_gc_hdr) == 0)) {
     OS::PrintErr(
-        "[              |                          |     |       |      "
-        "| new gen     | new gen     | new gen "
-        "| old gen       | old gen       | old gen     "
-        "| sweep | safe- | roots/| stbuf/| tospc/| weaks/  ]\n"
-        "[ GC isolate   | space (reason)           | GC# | start | time "
-        "| used (MB)   | capacity MB | external"
-        "| used (MB)     | capacity (MB) | external MB "
-        "| thread| point |marking| reset | sweep |swplrge  ]\n"
+        "[              |                          |     |       |      | new "
+        "gen     | new gen     | new gen | old gen       | old gen       | old "
+        "gen     |  store  | delta used   ]\n"
+        "[ GC isolate   | space (reason)           | GC# | start | time | used "
+        "(MB)   | capacity MB | external| used (MB)     | capacity (MB) | "
+        "external MB |  buffer | new  | old   ]\n"
         "[              |                          |     |  (s)  | (ms) "
-        "|before| after|before| after| b4 |aftr"
-        "| before| after | before| after |before| after"
-        "| (ms)  | (ms)  | (ms)  | (ms)  | (ms)  | (ms)    ]\n");
+        "|before| after|before| after| b4 |aftr| before| after | before| after "
+        "|before| after| b4 |aftr| (MB) | (MB)  ]\n");
   }
 
   // clang-format off
@@ -1146,7 +1110,8 @@
     "%6.1f, %6.1f, "   // old gen: in use before/after
     "%6.1f, %6.1f, "   // old gen: capacity before/after
     "%5.1f, %5.1f, "   // old gen: external before/after
-    "%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f, "  // times
+    "%3" Pd ", %3" Pd ", "   // store buffer: before/after
+    "%5.1f, %6.1f, "   // delta used: new gen/old gen
     "]\n",  // End with a comma to make it easier to import in spreadsheets.
     isolate_group()->source()->name,
     GCTypeToString(stats_.type_),
@@ -1167,14 +1132,13 @@
     WordsToMB(stats_.after_.old_.capacity_in_words),
     WordsToMB(stats_.before_.old_.external_in_words),
     WordsToMB(stats_.after_.old_.external_in_words),
-    MicrosecondsToMilliseconds(stats_.times_[0]),
-    MicrosecondsToMilliseconds(stats_.times_[1]),
-    MicrosecondsToMilliseconds(stats_.times_[2]),
-    MicrosecondsToMilliseconds(stats_.times_[3]),
-    MicrosecondsToMilliseconds(stats_.times_[4]),
-    MicrosecondsToMilliseconds(stats_.times_[5]));
+    stats_.before_.store_buffer_,
+    stats_.after_.store_buffer_,
+    WordsToMB(stats_.after_.new_.used_in_words -
+              stats_.before_.new_.used_in_words),
+    WordsToMB(stats_.after_.old_.used_in_words -
+              stats_.before_.old_.used_in_words));
   // clang-format on
-#endif  // !defined(PRODUCT)
 }
 
 void Heap::PrintStatsToTimeline(TimelineEventScope* event, GCReason reason) {
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 179122d..705e5a9 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -108,8 +108,7 @@
   void NotifyIdle(int64_t deadline);
 
   // Collect a single generation.
-  void CollectGarbage(Space space);
-  void CollectGarbage(GCType type, GCReason reason);
+  void CollectGarbage(Thread* thread, GCType type, GCReason reason);
 
   // Collect both generations by performing a scavenge followed by a
   // mark-sweep. This function may not collect all unreachable objects. Because
@@ -256,12 +255,6 @@
   void ForwardWeakEntries(ObjectPtr before_object, ObjectPtr after_object);
   void ForwardWeakTables(ObjectPointerVisitor* visitor);
 
-  // Stats collection.
-  void RecordTime(int id, int64_t micros) {
-    ASSERT((id >= 0) && (id < GCStats::kTimeEntries));
-    stats_.times_[id] = micros;
-  }
-
   void UpdateGlobalMaxUsed();
 
   static bool IsAllocatableInNewSpace(intptr_t size) {
@@ -314,16 +307,14 @@
       int64_t micros_;
       SpaceUsage new_;
       SpaceUsage old_;
+      intptr_t store_buffer_;
 
      private:
       DISALLOW_COPY_AND_ASSIGN(Data);
     };
 
-    enum { kTimeEntries = 6 };
-
     Data before_;
     Data after_;
-    int64_t times_[kTimeEntries];
 
    private:
     DISALLOW_COPY_AND_ASSIGN(GCStats);
@@ -352,9 +343,8 @@
   bool VerifyGC(MarkExpectation mark_expectation = kForbidMarked);
 
   // Helper functions for garbage collection.
-  void CollectNewSpaceGarbage(Thread* thread, GCReason reason);
+  void CollectNewSpaceGarbage(Thread* thread, GCType type, GCReason reason);
   void CollectOldSpaceGarbage(Thread* thread, GCType type, GCReason reason);
-  void EvacuateNewSpace(Thread* thread, GCReason reason);
 
   // GC stats collection.
   void RecordBeforeGC(GCType type, GCReason reason);
@@ -471,7 +461,8 @@
   static void CollectNewSpace() {
     Thread* thread = Thread::Current();
     ASSERT(thread->execution_state() == Thread::kThreadInVM);
-    thread->heap()->CollectNewSpaceGarbage(thread, GCReason::kDebugging);
+    thread->heap()->CollectGarbage(thread, GCType::kScavenge,
+                                   GCReason::kDebugging);
   }
 
   // Fully collect old gen and wait for the sweeper to finish. The normal call
@@ -482,9 +473,11 @@
     Thread* thread = Thread::Current();
     ASSERT(thread->execution_state() == Thread::kThreadInVM);
     if (thread->is_marking()) {
-      thread->heap()->CollectGarbage(GCType::kMarkSweep, GCReason::kDebugging);
+      thread->heap()->CollectGarbage(thread, GCType::kMarkSweep,
+                                     GCReason::kDebugging);
     }
-    thread->heap()->CollectGarbage(GCType::kMarkSweep, GCReason::kDebugging);
+    thread->heap()->CollectGarbage(thread, GCType::kMarkSweep,
+                                   GCReason::kDebugging);
     WaitForGCTasks();
   }
 
diff --git a/runtime/vm/heap/heap_test.cc b/runtime/vm/heap/heap_test.cc
index c45765c..d2e2c41 100644
--- a/runtime/vm/heap/heap_test.cc
+++ b/runtime/vm/heap/heap_test.cc
@@ -512,7 +512,8 @@
 class HeapTestHelper {
  public:
   static void Scavenge(Thread* thread) {
-    thread->heap()->CollectNewSpaceGarbage(thread, GCReason::kDebugging);
+    thread->heap()->CollectNewSpaceGarbage(thread, GCType::kScavenge,
+                                           GCReason::kDebugging);
   }
   static void MarkSweep(Thread* thread) {
     thread->heap()->CollectOldSpaceGarbage(thread, GCType::kMarkSweep,
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index 6c7c909..f1c3ed4 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -1100,7 +1100,7 @@
   }
 }
 
-void PageSpace::CollectGarbage(bool compact, bool finalize) {
+void PageSpace::CollectGarbage(Thread* thread, bool compact, bool finalize) {
   ASSERT(GrowthControlState());
 
   if (!finalize) {
@@ -1112,16 +1112,11 @@
 #endif
   }
 
-  Thread* thread = Thread::Current();
-  const int64_t pre_safe_point = OS::GetCurrentMonotonicMicros();
   GcSafepointOperationScope safepoint_scope(thread);
 
-  const int64_t pre_wait_for_sweepers = OS::GetCurrentMonotonicMicros();
   // Wait for pending tasks to complete and then account for the driver task.
-  Phase waited_for;
   {
     MonitorLocker locker(tasks_lock());
-    waited_for = phase();
     if (!finalize &&
         (phase() == kMarking || phase() == kAwaitingFinalization)) {
       // Concurrent mark is already running.
@@ -1136,26 +1131,11 @@
     set_tasks(1);
   }
 
-  if (FLAG_verbose_gc) {
-    const int64_t wait =
-        OS::GetCurrentMonotonicMicros() - pre_wait_for_sweepers;
-    if (waited_for == kMarking) {
-      THR_Print("Waited %" Pd64 " us for concurrent marking to finish.\n",
-                wait);
-    } else if (waited_for == kSweepingRegular || waited_for == kSweepingLarge) {
-      THR_Print("Waited %" Pd64 " us for concurrent sweeping to finish.\n",
-                wait);
-    }
-  }
-
   // Ensure that all threads for this isolate are at a safepoint (either
   // stopped or in native code). We have guards around Newgen GC and oldgen GC
   // to ensure that if two threads are racing to collect at the same time the
   // loser skips collection and goes straight to allocation.
-  {
-    CollectGarbageHelper(compact, finalize, pre_wait_for_sweepers,
-                         pre_safe_point);
-  }
+  CollectGarbageHelper(thread, compact, finalize);
 
   // Done, reset the task count.
   {
@@ -1165,11 +1145,9 @@
   }
 }
 
-void PageSpace::CollectGarbageHelper(bool compact,
-                                     bool finalize,
-                                     int64_t pre_wait_for_sweepers,
-                                     int64_t pre_safe_point) {
-  Thread* thread = Thread::Current();
+void PageSpace::CollectGarbageHelper(Thread* thread,
+                                     bool compact,
+                                     bool finalize) {
   ASSERT(thread->IsAtSafepoint());
   auto isolate_group = heap_->isolate_group();
   ASSERT(isolate_group == IsolateGroup::Current());
@@ -1182,7 +1160,7 @@
       [&](Isolate* isolate) { isolate->field_table()->FreeOldTables(); },
       /*at_safepoint=*/true);
 
-  NoSafepointScope no_safepoints;
+  NoSafepointScope no_safepoints(thread);
 
   if (FLAG_print_free_list_before_gc) {
     for (intptr_t i = 0; i < num_freelists_; i++) {
@@ -1224,8 +1202,6 @@
   delete marker_;
   marker_ = NULL;
 
-  int64_t mid1 = OS::GetCurrentMonotonicMicros();
-
   // Abandon the remainder of the bump allocation block.
   AbandonBumpAllocation();
   // Reset the freelists and setup sweeping.
@@ -1233,19 +1209,15 @@
     freelists_[i].Reset();
   }
 
-  int64_t mid2 = OS::GetCurrentMonotonicMicros();
-  int64_t mid3 = 0;
+  if (FLAG_verify_before_gc) {
+    OS::PrintErr("Verifying before sweeping...");
+    heap_->VerifyGC(kAllowMarked);
+    OS::PrintErr(" done.\n");
+  }
 
   {
-    if (FLAG_verify_before_gc) {
-      OS::PrintErr("Verifying before sweeping...");
-      heap_->VerifyGC(kAllowMarked);
-      OS::PrintErr(" done.\n");
-    }
-
     // Executable pages are always swept immediately to simplify
     // code protection.
-
     TIMELINE_FUNCTION_GC_DURATION(thread, "SweepExecutable");
     GCSweeper sweeper;
     OldPage* prev_page = NULL;
@@ -1263,8 +1235,6 @@
       // Advance to the next page.
       page = next_page;
     }
-
-    mid3 = OS::GetCurrentMonotonicMicros();
   }
 
   bool has_reservation = MarkReservation();
@@ -1315,13 +1285,6 @@
   page_space_controller_.EvaluateGarbageCollection(
       usage_before, GetCurrentUsage(), start, end);
 
-  heap_->RecordTime(kConcurrentSweep, pre_safe_point - pre_wait_for_sweepers);
-  heap_->RecordTime(kSafePoint, start - pre_safe_point);
-  heap_->RecordTime(kMarkObjects, mid1 - start);
-  heap_->RecordTime(kResetFreeLists, mid2 - mid1);
-  heap_->RecordTime(kSweepPages, mid3 - mid2);
-  heap_->RecordTime(kSweepLargePages, end - mid3);
-
   if (FLAG_print_free_list_after_gc) {
     for (intptr_t i = 0; i < num_freelists_; i++) {
       OS::PrintErr("After GC: Freelist %" Pd "\n", i);
@@ -1748,8 +1711,8 @@
   if (FLAG_log_growth || FLAG_verbose_gc) {
     THR_Print("%s: threshold=%" Pd "kB, idle_threshold=%" Pd "kB, reason=%s\n",
               heap_->isolate_group()->source()->name,
-              hard_gc_threshold_in_words_ / KBInWords,
-              idle_gc_threshold_in_words_ / KBInWords, reason);
+              RoundWordsToKB(hard_gc_threshold_in_words_),
+              RoundWordsToKB(idle_gc_threshold_in_words_), reason);
   }
 }
 
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index 5a3c5c0..fca17a2 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -408,7 +408,7 @@
                        OldPage::PageType type) const;
 
   // Collect the garbage in the page space using mark-sweep or mark-compact.
-  void CollectGarbage(bool compact, bool finalize);
+  void CollectGarbage(Thread* thread, bool compact, bool finalize);
 
   void AddRegionsToObjectSet(ObjectSet* set) const;
 
@@ -573,10 +573,7 @@
   void FreeLargePage(OldPage* page, OldPage* previous_page);
   void FreePages(OldPage* pages);
 
-  void CollectGarbageHelper(bool compact,
-                            bool finalize,
-                            int64_t pre_wait_for_sweepers,
-                            int64_t pre_safe_point);
+  void CollectGarbageHelper(Thread* thread, bool compact, bool finalize);
   void SweepLarge();
   void Sweep(bool exclusive);
   void ConcurrentSweep(IsolateGroup* isolate_group);
diff --git a/runtime/vm/heap/pointer_block.cc b/runtime/vm/heap/pointer_block.cc
index daedab8..7d361d4 100644
--- a/runtime/vm/heap/pointer_block.cc
+++ b/runtime/vm/heap/pointer_block.cc
@@ -228,6 +228,11 @@
   return (full_.length() + partial_.length()) > kMaxNonEmpty;
 }
 
+intptr_t StoreBuffer::Size() {
+  ASSERT(Thread::Current()->IsAtSafepoint());  // No lock needed.
+  return full_.length() + partial_.length();
+}
+
 void StoreBuffer::VisitObjectPointers(ObjectPointerVisitor* visitor) {
   for (Block* block = full_.Peek(); block != NULL; block = block->next()) {
     block->VisitObjectPointers(visitor);
diff --git a/runtime/vm/heap/pointer_block.h b/runtime/vm/heap/pointer_block.h
index fb10fa6..1b6fd5a 100644
--- a/runtime/vm/heap/pointer_block.h
+++ b/runtime/vm/heap/pointer_block.h
@@ -268,6 +268,7 @@
   // Check whether non-empty blocks have exceeded kMaxNonEmpty (but takes no
   // action).
   bool Overflowed();
+  intptr_t Size();
 
   void VisitObjectPointers(ObjectPointerVisitor* visitor);
 };
diff --git a/runtime/vm/heap/safepoint.cc b/runtime/vm/heap/safepoint.cc
index c2e8d9a..09e0c04 100644
--- a/runtime/vm/heap/safepoint.cc
+++ b/runtime/vm/heap/safepoint.cc
@@ -63,7 +63,7 @@
     ASSERT(T->CanCollectGarbage());
     // Check if we passed the growth limit during the scope.
     if (heap->old_space()->ReachedHardThreshold()) {
-      heap->CollectGarbage(GCType::kMarkSweep, GCReason::kOldSpace);
+      heap->CollectGarbage(T, GCType::kMarkSweep, GCReason::kOldSpace);
     } else {
       heap->CheckStartConcurrentMarking(T, GCReason::kOldSpace);
     }
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index d2f7de4..0648d60 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -1731,24 +1731,20 @@
   return tail_->TryAllocateGC(size);
 }
 
-void Scavenger::Scavenge(GCReason reason) {
+void Scavenger::Scavenge(Thread* thread, GCType type, GCReason reason) {
   int64_t start = OS::GetCurrentMonotonicMicros();
 
-  // Ensure that all threads for this isolate are at a safepoint (either stopped
-  // or in native code). If two threads are racing at this point, the loser
-  // will continue with its scavenge after waiting for the winner to complete.
-  // TODO(koda): Consider moving SafepointThreads into allocation failure/retry
-  // logic to avoid needless collections.
-  Thread* thread = Thread::Current();
-  GcSafepointOperationScope safepoint_scope(thread);
-
-  int64_t safe_point = OS::GetCurrentMonotonicMicros();
-  heap_->RecordTime(kSafePoint, safe_point - start);
+  ASSERT(thread->IsAtSafepoint());
 
   // Scavenging is not reentrant. Make sure that is the case.
   ASSERT(!scavenging_);
   scavenging_ = true;
 
+  if (type == GCType::kEvacuate) {
+    // Forces the next scavenge to promote all the objects in the new space.
+    early_tenure_ = true;
+  }
+
   if (FLAG_verify_before_gc) {
     OS::PrintErr("Verifying before Scavenge...");
     heap_->WaitForSweeperTasksAtSafepoint(thread);
@@ -1810,6 +1806,11 @@
   // Done scavenging. Reset the marker.
   ASSERT(scavenging_);
   scavenging_ = false;
+
+  // It is possible for objects to stay in the new space
+  // if the VM cannot create more pages for these objects.
+  ASSERT((type != GCType::kEvacuate) || (UsedInWords() == 0) ||
+         failed_to_promote_);
 }
 
 intptr_t Scavenger::SerialScavenge(SemiSpace* from) {
@@ -1973,23 +1974,4 @@
 }
 #endif  // !PRODUCT
 
-void Scavenger::Evacuate(GCReason reason) {
-  // We need a safepoint here to prevent allocation right before or right after
-  // the scavenge.
-  // The former can introduce an object that we might fail to collect.
-  // The latter means even if the scavenge promotes every object in the new
-  // space, the new allocation means the space is not empty,
-  // causing the assertion below to fail.
-  GcSafepointOperationScope scope(Thread::Current());
-
-  // Forces the next scavenge to promote all the objects in the new space.
-  early_tenure_ = true;
-
-  Scavenge(reason);
-
-  // It is possible for objects to stay in the new space
-  // if the VM cannot create more pages for these objects.
-  ASSERT((UsedInWords() == 0) || failed_to_promote_);
-}
-
 }  // namespace dart
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 5a93aec..229912a 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -277,10 +277,7 @@
   void AbandonRemainingTLABForDebugging(Thread* thread);
 
   // Collect the garbage in this scavenger.
-  void Scavenge(GCReason reason);
-
-  // Promote all live objects.
-  void Evacuate(GCReason reason);
+  void Scavenge(Thread* thread, GCType type, GCReason reason);
 
   int64_t UsedInWords() const {
     MutexLocker ml(&space_lock_);
diff --git a/runtime/vm/heap/spaces.h b/runtime/vm/heap/spaces.h
index c1c0663..2b9c00f 100644
--- a/runtime/vm/heap/spaces.h
+++ b/runtime/vm/heap/spaces.h
@@ -31,6 +31,7 @@
 
 enum class GCType {
   kScavenge,
+  kEvacuate,
   kStartConcurrentMark,
   kMarkSweep,
   kMarkCompact,
diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc
index 1552a72..b4e8514 100644
--- a/runtime/vm/image_snapshot.cc
+++ b/runtime/vm/image_snapshot.cc
@@ -627,6 +627,44 @@
   return nullptr;
 }
 
+#if (defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)) &&    \
+    defined(TARGET_ARCH_ARM64)
+// When generating ARM64 Mach-O LLVM tends to generate Compact Unwind Info
+// (__unwind_info) rather than traditional DWARF unwinding information
+// (__eh_frame).
+//
+// Unfortunately when generating __unwind_info LLVM seems to only apply CFI
+// rules to the region between two non-local symbols that contains these CFI
+// directives. In other words given:
+//
+//   Abc:
+//   .cfi_startproc
+//   .cfi_def_cfa x29, 16
+//   .cfi_offset x29, -16
+//   .cfi_offset x30, -8
+//   ;; ...
+//   Xyz:
+//   ;; ...
+//   .cfi_endproc
+//
+// __unwind_info would specify proper unwinding information only for the region
+// between Abc and Xyz symbols. And the region Xyz onwards will have no
+// unwinding information.
+//
+// There also seems to be a difference in how unwinding information is
+// canonicalized and compressed: when building __unwind_info from CFI directives
+// LLVM will fold together similar entries, the same does not happen for
+// __eh_frame. This means that emitting CFI directives for each function would
+// baloon the size of __eh_frame.
+//
+// Hence to work around the problem of incorrect __unwind_info without balooning
+// snapshot size when __eh_frame is generated we choose to emit CFI directives
+// per function specifically on ARM64 Mac OS X and iOS.
+//
+// See also |useCompactUnwind| method in LLVM (https://github.com/llvm/llvm-project/blob/b27430f9f46b88bcd54d992debc8d72e131e1bd0/llvm/lib/MC/MCObjectFileInfo.cpp#L28-L50)
+#define EMIT_UNWIND_DIRECTIVES_PER_FUNCTION 1
+#endif
+
 void ImageWriter::WriteText(bool vm) {
   const bool bare_instruction_payloads = FLAG_precompiled_mode;
 
@@ -728,7 +766,9 @@
   }
 #endif
 
+#if !defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION)
   FrameUnwindPrologue();
+#endif
 
 #if defined(DART_PRECOMPILER)
   PcDescriptors& descriptors = PcDescriptors::Handle(zone_);
@@ -795,6 +835,10 @@
     AddCodeSymbol(code, object_name, text_offset);
 #endif
 
+#if defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION)
+    FrameUnwindPrologue();
+#endif
+
     {
       NoSafepointScope no_safepoint;
 
@@ -847,6 +891,9 @@
     text_offset += AlignWithBreakInstructions(alignment, text_offset);
 
     ASSERT_EQUAL(text_offset - instr_start, SizeInSnapshot(insns.ptr()));
+#if defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION)
+    FrameUnwindEpilogue();
+#endif
   }
 
   // Should be a no-op unless writing bare instruction payloads, in which case
@@ -860,7 +907,9 @@
 
   ASSERT_EQUAL(text_offset, image_size);
 
+#if !defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION)
   FrameUnwindEpilogue();
+#endif
 
   ExitSection(ProgramSection::Text, vm, text_offset);
 }
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 5b7eaad..a71c5f5 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -1807,9 +1807,9 @@
 // an individual isolate.
 class NoActiveIsolateScope : public StackResource {
  public:
-  NoActiveIsolateScope()
-      : StackResource(Thread::Current()),
-        thread_(static_cast<Thread*>(thread())) {
+  NoActiveIsolateScope() : NoActiveIsolateScope(Thread::Current()) {}
+  explicit NoActiveIsolateScope(Thread* thread)
+      : StackResource(thread), thread_(thread) {
     saved_isolate_ = thread_->isolate_;
     thread_->isolate_ = nullptr;
   }
diff --git a/runtime/vm/json_stream.h b/runtime/vm/json_stream.h
index 5b37379..3a6b523 100644
--- a/runtime/vm/json_stream.h
+++ b/runtime/vm/json_stream.h
@@ -173,6 +173,7 @@
   }
 
   void PrintCommaIfNeeded() { writer_.PrintCommaIfNeeded(); }
+  JSONWriter* writer() { return &writer_; }
 
  private:
   void Clear() { writer_.Clear(); }
diff --git a/runtime/vm/metrics_test.cc b/runtime/vm/metrics_test.cc
index 5a9ebf7..f87ba5d 100644
--- a/runtime/vm/metrics_test.cc
+++ b/runtime/vm/metrics_test.cc
@@ -78,7 +78,7 @@
 
 ISOLATE_UNIT_TEST_CASE(Metric_EmbedderAPI) {
   {
-    TransitionVMToNative transition(Thread::Current());
+    TransitionVMToNative transition(thread);
 
     const char* kScript = "void main() {}";
     Dart_Handle api_lib = TestCase::LoadTestScript(
@@ -88,15 +88,16 @@
 
   // Ensure we've done new/old GCs to ensure max metrics are initialized.
   String::New("<land-in-new-space>", Heap::kNew);
-  IsolateGroup::Current()->heap()->new_space()->Scavenge(GCReason::kDebugging);
-  IsolateGroup::Current()->heap()->CollectAllGarbage(GCReason::kDebugging,
-                                                     /*compact=*/ true);
+  thread->heap()->CollectGarbage(thread, GCType::kScavenge,
+                                 GCReason::kDebugging);
+  thread->heap()->CollectGarbage(thread, GCType::kMarkCompact,
+                                 GCReason::kDebugging);
 
   // Ensure we've something live in new space.
   String::New("<land-in-new-space2>", Heap::kNew);
 
   {
-    TransitionVMToNative transition(Thread::Current());
+    TransitionVMToNative transition(thread);
 
     Dart_Isolate isolate = Dart_CurrentIsolate();
 #if !defined(PRODUCT)
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 72b9021..4d3fa4d 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4473,13 +4473,13 @@
   auto isolate_group = thread->isolate_group();
   if (js->HasParam("gc")) {
     if (js->ParamIs("gc", "scavenge")) {
-      isolate_group->heap()->CollectGarbage(GCType::kScavenge,
+      isolate_group->heap()->CollectGarbage(thread, GCType::kScavenge,
                                             GCReason::kDebugging);
     } else if (js->ParamIs("gc", "mark-sweep")) {
-      isolate_group->heap()->CollectGarbage(GCType::kMarkSweep,
+      isolate_group->heap()->CollectGarbage(thread, GCType::kMarkSweep,
                                             GCReason::kDebugging);
     } else if (js->ParamIs("gc", "mark-compact")) {
-      isolate_group->heap()->CollectGarbage(GCType::kMarkCompact,
+      isolate_group->heap()->CollectGarbage(thread, GCType::kMarkCompact,
                                             GCReason::kDebugging);
     } else {
       PrintInvalidParamError(js, "gc");
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index e21a66c..9f3a5bf 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -437,7 +437,7 @@
   if ((interrupt_bits & kVMInterrupt) != 0) {
     CheckForSafepoint();
     if (isolate_group()->store_buffer()->Overflowed()) {
-      heap()->CollectGarbage(GCType::kScavenge, GCReason::kStoreBuffer);
+      heap()->CollectGarbage(this, GCType::kScavenge, GCReason::kStoreBuffer);
     }
 
 #if !defined(PRODUCT)
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index c242cda..fc57084 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -105,10 +105,6 @@
 
   if (use_systrace_recorder || (flag != NULL)) {
     if (use_systrace_recorder || (strcmp("systrace", flag) == 0)) {
-      if (FLAG_trace_timeline) {
-        THR_Print("Using the Systrace timeline recorder.\n");
-      }
-
 #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
       return new TimelineEventSystraceRecorder();
 #elif defined(DART_HOST_OS_MACOS)
@@ -128,24 +124,22 @@
 
   if (use_endless_recorder || (flag != NULL)) {
     if (use_endless_recorder || (strcmp("endless", flag) == 0)) {
-      if (FLAG_trace_timeline) {
-        THR_Print("Using the endless timeline recorder.\n");
-      }
       return new TimelineEventEndlessRecorder();
     }
   }
 
   if (use_startup_recorder || (flag != NULL)) {
     if (use_startup_recorder || (strcmp("startup", flag) == 0)) {
-      if (FLAG_trace_timeline) {
-        THR_Print("Using the startup recorder.\n");
-      }
       return new TimelineEventStartupRecorder();
     }
   }
 
-  if (FLAG_trace_timeline) {
-    THR_Print("Using the ring timeline recorder.\n");
+  if (strcmp("file", flag) == 0) {
+    return new TimelineEventFileRecorder("dart-timeline.json");
+  }
+  if (Utils::StrStartsWith(flag, "file:") ||
+      Utils::StrStartsWith(flag, "file=")) {
+    return new TimelineEventFileRecorder(&flag[5]);
   }
 
   // Always fall back to the ring recorder.
@@ -202,6 +196,9 @@
 void Timeline::Init() {
   ASSERT(recorder_ == NULL);
   recorder_ = CreateTimelineRecorder();
+  if (FLAG_trace_timeline) {
+    OS::PrintErr("Using the %s timeline recorder.\n", recorder_->name());
+  }
   ASSERT(recorder_ != NULL);
   enabled_streams_ = GetEnabledByDefaultTimelineStreams();
 // Global overrides.
@@ -610,65 +607,70 @@
 
 #ifndef PRODUCT
 void TimelineEvent::PrintJSON(JSONStream* stream) const {
-  JSONObject obj(stream);
+  PrintJSON(stream->writer());
+}
+#endif
+
+void TimelineEvent::PrintJSON(JSONWriter* writer) const {
+  writer->OpenObject();
   int64_t pid = OS::ProcessId();
   int64_t tid = OSThread::ThreadIdToIntPtr(thread_);
-  obj.AddProperty("name", label_);
-  obj.AddProperty("cat", stream_ != NULL ? stream_->name() : NULL);
-  obj.AddProperty64("tid", tid);
-  obj.AddProperty64("pid", pid);
-  obj.AddPropertyTimeMicros("ts", TimeOrigin());
+  writer->PrintProperty("name", label_);
+  writer->PrintProperty("cat", stream_ != NULL ? stream_->name() : NULL);
+  writer->PrintProperty64("tid", tid);
+  writer->PrintProperty64("pid", pid);
+  writer->PrintProperty64("ts", TimeOrigin());
   if (HasThreadCPUTime()) {
-    obj.AddPropertyTimeMicros("tts", ThreadCPUTimeOrigin());
+    writer->PrintProperty64("tts", ThreadCPUTimeOrigin());
   }
   switch (event_type()) {
     case kBegin: {
-      obj.AddProperty("ph", "B");
+      writer->PrintProperty("ph", "B");
     } break;
     case kEnd: {
-      obj.AddProperty("ph", "E");
+      writer->PrintProperty("ph", "E");
     } break;
     case kDuration: {
-      obj.AddProperty("ph", "X");
-      obj.AddPropertyTimeMicros("dur", TimeDuration());
+      writer->PrintProperty("ph", "X");
+      writer->PrintProperty64("dur", TimeDuration());
       if (HasThreadCPUTime()) {
-        obj.AddPropertyTimeMicros("tdur", ThreadCPUTimeDuration());
+        writer->PrintProperty64("tdur", ThreadCPUTimeDuration());
       }
     } break;
     case kInstant: {
-      obj.AddProperty("ph", "i");
-      obj.AddProperty("s", "p");
+      writer->PrintProperty("ph", "i");
+      writer->PrintProperty("s", "p");
     } break;
     case kAsyncBegin: {
-      obj.AddProperty("ph", "b");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "b");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kAsyncInstant: {
-      obj.AddProperty("ph", "n");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "n");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kAsyncEnd: {
-      obj.AddProperty("ph", "e");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "e");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kCounter: {
-      obj.AddProperty("ph", "C");
+      writer->PrintProperty("ph", "C");
     } break;
     case kFlowBegin: {
-      obj.AddProperty("ph", "s");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "s");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kFlowStep: {
-      obj.AddProperty("ph", "t");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "t");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kFlowEnd: {
-      obj.AddProperty("ph", "f");
-      obj.AddProperty("bp", "e");
-      obj.AddPropertyF("id", "%" Px64 "", AsyncId());
+      writer->PrintProperty("ph", "f");
+      writer->PrintProperty("bp", "e");
+      writer->PrintfProperty("id", "%" Px64 "", AsyncId());
     } break;
     case kMetadata: {
-      obj.AddProperty("ph", "M");
+      writer->PrintProperty("ph", "M");
     } break;
     default:
       UNIMPLEMENTED();
@@ -676,42 +678,43 @@
 
   if (pre_serialized_args()) {
     ASSERT(arguments_.length() == 1);
-    stream->AppendSerializedObject("args", arguments_[0].value);
+    writer->AppendSerializedObject("args", arguments_[0].value);
     if (isolate_id_ != ILLEGAL_PORT) {
-      stream->UncloseObject();
-      stream->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING,
+      writer->UncloseObject();
+      writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING,
                              static_cast<int64_t>(isolate_id_));
-      stream->CloseObject();
+      writer->CloseObject();
     }
     if (isolate_group_id_ != 0) {
-      stream->UncloseObject();
-      stream->PrintfProperty("isolateGroupId",
+      writer->UncloseObject();
+      writer->PrintfProperty("isolateGroupId",
                              ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING,
                              isolate_group_id_);
-      stream->CloseObject();
+      writer->CloseObject();
     } else {
       ASSERT(isolate_group_id_ == ILLEGAL_PORT);
     }
   } else {
-    JSONObject args(&obj, "args");
+    writer->OpenObject("args");
     for (intptr_t i = 0; i < arguments_.length(); i++) {
       const TimelineEventArgument& arg = arguments_[i];
-      args.AddProperty(arg.name, arg.value);
+      writer->PrintProperty(arg.name, arg.value);
     }
     if (isolate_id_ != ILLEGAL_PORT) {
-      args.AddPropertyF("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING,
-                        static_cast<int64_t>(isolate_id_));
+      writer->PrintfProperty("isolateId", ISOLATE_SERVICE_ID_FORMAT_STRING,
+                             static_cast<int64_t>(isolate_id_));
     }
     if (isolate_group_id_ != 0) {
-      args.AddPropertyF("isolateGroupId",
-                        ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING,
-                        isolate_group_id_);
+      writer->PrintfProperty("isolateGroupId",
+                             ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING,
+                             isolate_group_id_);
     } else {
       ASSERT(isolate_group_id_ == ILLEGAL_PORT);
     }
+    writer->CloseObject();
   }
+  writer->CloseObject();
 }
-#endif
 
 int64_t TimelineEvent::TimeOrigin() const {
   return timestamp0_;
@@ -1347,6 +1350,132 @@
   delete event;
 }
 
+static void TimelineEventFileRecorderStart(uword parameter) {
+  reinterpret_cast<TimelineEventFileRecorder*>(parameter)->Drain();
+}
+
+TimelineEventFileRecorder::TimelineEventFileRecorder(const char* path)
+    : TimelineEventPlatformRecorder(),
+      monitor_(),
+      head_(nullptr),
+      tail_(nullptr),
+      file_(nullptr),
+      first_(true),
+      shutting_down_(false),
+      thread_id_(OSThread::kInvalidThreadJoinId) {
+  Dart_FileOpenCallback file_open = Dart::file_open_callback();
+  Dart_FileWriteCallback file_write = Dart::file_write_callback();
+  Dart_FileCloseCallback file_close = Dart::file_close_callback();
+  if ((file_open == nullptr) || (file_write == nullptr) ||
+      (file_close == nullptr)) {
+    OS::PrintErr("warning: Could not access file callbacks.");
+    return;
+  }
+  void* file = (*file_open)(path, true);
+  if (file == nullptr) {
+    OS::PrintErr("warning: Failed to open timeline file: %s\n", path);
+    return;
+  }
+
+  file_ = file;
+  // Chrome trace format has two forms:
+  //   Object form:  { "traceEvents": [ event, event, event ] }
+  //   Array form:   [ event, event, event ]
+  // For this recorder, we use the array form because Catapult will handle a
+  // missing ending bracket in this form in case we don't cleanly end the
+  // trace.
+  Write("[\n");
+  OSThread::Start("TimelineEventFileRecorder", TimelineEventFileRecorderStart,
+                  reinterpret_cast<uword>(this));
+}
+
+TimelineEventFileRecorder::~TimelineEventFileRecorder() {
+  if (file_ == nullptr) return;
+
+  {
+    MonitorLocker ml(&monitor_);
+    shutting_down_ = true;
+    ml.Notify();
+  }
+
+  ASSERT(thread_id_ != OSThread::kInvalidThreadJoinId);
+  OSThread::Join(thread_id_);
+  thread_id_ = OSThread::kInvalidThreadJoinId;
+
+  TimelineEvent* event = head_;
+  while (event != nullptr) {
+    TimelineEvent* next = event->next();
+    delete event;
+    event = next;
+  }
+  head_ = tail_ = nullptr;
+
+  Write("]\n");
+  Dart_FileCloseCallback file_close = Dart::file_close_callback();
+  (*file_close)(file_);
+  file_ = nullptr;
+}
+
+void TimelineEventFileRecorder::CompleteEvent(TimelineEvent* event) {
+  if (event == nullptr) {
+    return;
+  }
+  if (file_ == nullptr) {
+    delete event;
+    return;
+  }
+
+  MonitorLocker ml(&monitor_);
+  ASSERT(!shutting_down_);
+  event->set_next(nullptr);
+  if (tail_ == nullptr) {
+    head_ = tail_ = event;
+  } else {
+    tail_->set_next(event);
+    tail_ = event;
+  }
+  ml.Notify();
+}
+
+void TimelineEventFileRecorder::Drain() {
+  MonitorLocker ml(&monitor_);
+  thread_id_ = OSThread::GetCurrentThreadJoinId(OSThread::Current());
+  while (!shutting_down_) {
+    if (head_ == nullptr) {
+      ml.Wait();
+      continue;  // Recheck empty and shutting down.
+    }
+    TimelineEvent* event = head_;
+    TimelineEvent* next = event->next();
+    head_ = next;
+    if (next == nullptr) {
+      tail_ = nullptr;
+    }
+    ml.Exit();
+    {
+      JSONWriter writer;
+      if (first_) {
+        first_ = false;
+      } else {
+        writer.buffer()->AddChar(',');
+      }
+      event->PrintJSON(&writer);
+      char* output = NULL;
+      intptr_t output_length = 0;
+      writer.Steal(&output, &output_length);
+      Write(output, output_length);
+      free(output);
+      delete event;
+    }
+    ml.Enter();
+  }
+}
+
+void TimelineEventFileRecorder::Write(const char* buffer, intptr_t len) {
+  Dart_FileWriteCallback file_write = Dart::file_write_callback();
+  (*file_write)(buffer, len, file_);
+}
+
 TimelineEventEndlessRecorder::TimelineEventEndlessRecorder()
     : head_(nullptr), tail_(nullptr), block_index_(0) {}
 
diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h
index 6d5c062..8f1df56 100644
--- a/runtime/vm/timeline.h
+++ b/runtime/vm/timeline.h
@@ -36,6 +36,7 @@
 class JSONArray;
 class JSONObject;
 class JSONStream;
+class JSONWriter;
 class Object;
 class ObjectPointerVisitor;
 class Isolate;
@@ -49,6 +50,7 @@
 
 #define CALLBACK_RECORDER_NAME "Callback"
 #define ENDLESS_RECORDER_NAME "Endless"
+#define FILE_RECORDER_NAME "File"
 #define FUCHSIA_RECORDER_NAME "Fuchsia"
 #define MACOS_RECORDER_NAME "Macos"
 #define RING_RECORDER_NAME "Ring"
@@ -392,6 +394,7 @@
 #ifndef PRODUCT
   void PrintJSON(JSONStream* stream) const;
 #endif
+  void PrintJSON(JSONWriter* writer) const;
 
   ThreadId thread() const { return thread_; }
 
@@ -456,6 +459,13 @@
 
   intptr_t arguments_length() const { return arguments_.length(); }
 
+  TimelineEvent* next() const {
+    return next_;
+  }
+  void set_next(TimelineEvent* next) {
+    next_ = next;
+  }
+
  private:
   void StreamInit(TimelineStream* stream) { stream_ = stream; }
   void Init(EventType event_type, const char* label);
@@ -518,6 +528,7 @@
   ThreadId thread_;
   Dart_Port isolate_id_;
   uint64_t isolate_group_id_;
+  TimelineEvent* next_;
 
   friend class TimelineEventRecorder;
   friend class TimelineEventEndlessRecorder;
@@ -1006,6 +1017,31 @@
 };
 #endif  // defined(DART_HOST_OS_MACOS)
 
+class TimelineEventFileRecorder : public TimelineEventPlatformRecorder {
+ public:
+  explicit TimelineEventFileRecorder(const char* path);
+  virtual ~TimelineEventFileRecorder();
+
+  const char* name() const { return FILE_RECORDER_NAME; }
+  intptr_t Size() { return 0; }
+
+  void Drain();
+
+ private:
+  void CompleteEvent(TimelineEvent* event);
+  void OnEvent(TimelineEvent* event) { UNREACHABLE(); }
+  void Write(const char* buffer) { Write(buffer, strlen(buffer)); }
+  void Write(const char* buffer, intptr_t len);
+
+  Monitor monitor_;
+  TimelineEvent* head_;
+  TimelineEvent* tail_;
+  void* file_;
+  bool first_;
+  bool shutting_down_;
+  ThreadJoinId thread_id_;
+};
+
 class DartTimelineEventHelpers : public AllStatic {
  public:
   static void ReportTaskEvent(Thread* thread,
diff --git a/tools/VERSION b/tools/VERSION
index 74dc52d..20084da 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 32
+PRERELEASE 33
 PRERELEASE_PATCH 0
\ No newline at end of file