Version 2.17.0-195.0.dev

Merge commit '4ffb83acaf3f019f7796cd5966d444db3ecfddcd' into 'dev'
diff --git a/DEPS b/DEPS
index da98049..0258c53 100644
--- a/DEPS
+++ b/DEPS
@@ -83,7 +83,7 @@
   "boolean_selector_rev": "437e7f06c7e416bed91e16ae1df453555897e945",
   "boringssl_gen_rev": "ced85ef0a00bbca77ce5a91261a5f2ae61b1e62f",
   "boringssl_rev" : "87f316d7748268eb56f2dc147bd593254ae93198",
-  "browser-compat-data_tag": "v1.0.22",
+  "browser-compat-data_tag": "ac8cae697014da1ff7124fba33b0b4245cc6cd1b", # v1.0.22
   "browser_launcher_rev": "c6cc1025d6901926cf022e144ba109677e3548f1",
   "characters_rev": "6ec389c4dfa8fce14820dc5cbf6e693202e7e052",
   "charcode_rev": "84ea427711e24abf3b832923959caa7dd9a8514b",
@@ -121,10 +121,10 @@
   "http_parser_rev": "202391286ddc13c4c3c284ac5b511f04697250ed",
   "http_rev": "1e42ffa181b263f7790f276a5465832bff7ce615",
   "icu_rev" : "81d656878ec611cb0b42d52c82e9dae93920d9ba",
-  "intl_tag": "0.17.0-nullsafety",
+  "intl_tag": "9669926609e7efc17dfd74fbb44ec719a7e573cc", # 0.17.0-nullsafety
   "jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
   "json_rpc_2_rev": "7e00f893440a72de0637970325e4ea44bd1e8c8e",
-  "linter_tag": "1.19.2",
+  "linter_tag": "4eaae25b00f3c29935ab0d804b8711cf9a645519", # 1.19.2
   "lints_tag": "f9670df2a66e0ec12eb51554e70c1cbf56c8f5d0",
   "logging_rev": "dfbe88b890c3b4f7bc06da5a7b3b43e9e263b688",
   "markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
@@ -147,7 +147,7 @@
   "rust_revision": "b7856f695d65a8ebc846754f97d15814bcb1c244",
   "shelf_static_rev": "202ec1a53c9a830c17cf3b718d089cf7eba568ad",
   "shelf_packages_handler_rev": "78302e67c035047e6348e692b0c1182131f0fe35",
-  "shelf_proxy_tag": "v1.0.0",
+  "shelf_proxy_tag": "ccd311f64d8689e7a145d703ba267975d6df9e28", # 1.0.0
   "shelf_rev": "46483f896cc4308ee3d8e997030ae799b72aa16a",
   "shelf_web_socket_rev": "24fb8a04befa75a94ac63a27047b231d1a22aab4",
   "source_map_stack_trace_rev": "80709f2d2fe5086ab50d744a28a2d26ea4384a1b",
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index be632ea..c1b807a 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -4777,6 +4777,13 @@
       the user edit the variable name after the operation, all occurrences of
       the name could be edited simultaneously.
     </p>
+    <p>
+      Edit groups may have a length of 0 and function as tabstops where there
+      is no default text, for example, an edit that inserts an <tt>if</tt>
+      statement might provide an empty group between parens where a condition
+      should be typed. For this reason, it's also valid for a group to contain
+      only a single position that is not linked to others.
+    </p>
     
   <dl><dt class="field"><b>positions: List&lt;<a href="#type_Position">Position</a>&gt;</b></dt><dd>
         
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index e2c434e..d4ce078 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -229,8 +229,13 @@
   }
 
   /// The list of current analysis sessions in all contexts.
-  List<AnalysisSession> get currentSessions {
-    return driverMap.values.map((driver) => driver.currentSession).toList();
+  Future<List<AnalysisSession>> get currentSessions async {
+    var sessions = <AnalysisSession>[];
+    for (var driver in driverMap.values) {
+      await driver.applyPendingFileChanges();
+      sessions.add(driver.currentSession);
+    }
+    return sessions;
   }
 
   /// A table mapping [Folder]s to the [AnalysisDriver]s associated with them.
@@ -297,6 +302,18 @@
     return null;
   }
 
+  /// Return an [AnalysisSession] in which the file with the given [path]
+  /// should be analyzed, preferring the one in which it is analyzed, then
+  /// the one where it is referenced, then the first, otherwise `null`.
+  Future<AnalysisSession?> getAnalysisSession(String path) async {
+    var analysisDriver = getAnalysisDriver(path);
+    if (analysisDriver != null) {
+      await analysisDriver.applyPendingFileChanges();
+      return analysisDriver.currentSession;
+    }
+    return null;
+  }
+
   DartdocDirectiveInfo getDartdocDirectiveInfoFor(ResolvedUnitResult result) {
     return getDartdocDirectiveInfoForSession(result.session);
   }
@@ -395,8 +412,12 @@
       return null;
     }
 
-    var session = getAnalysisDriver(path)?.currentSession;
-    var result = await session?.getParsedUnit2(path);
+    var session = await getAnalysisSession(path);
+    if (session == null) {
+      return null;
+    }
+
+    var result = await session.getParsedUnit2(path);
     return result is ParsedUnitResult ? result : null;
   }
 
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 9f3043e..85363b6 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -174,12 +174,11 @@
     var id = ++_latestGetSuggestionDetailsId;
     while (id == _latestGetSuggestionDetailsId && timer.elapsed < timeout) {
       try {
-        var analysisDriver = server.getAnalysisDriver(file);
-        if (analysisDriver == null) {
+        var session = await server.getAnalysisSession(file);
+        if (session == null) {
           server.sendResponse(Response.fileNotAnalyzed(request, 'libraryId'));
           return;
         }
-        var session = analysisDriver.currentSession;
 
         var completion = params.label;
         var builder = ChangeBuilder(session: session);
@@ -231,12 +230,11 @@
     var id = ++_latestGetSuggestionDetailsId;
     while (id == _latestGetSuggestionDetailsId && !budget.isEmpty) {
       try {
-        var analysisDriver = server.getAnalysisDriver(file);
-        if (analysisDriver == null) {
+        var session = await server.getAnalysisSession(file);
+        if (session == null) {
           server.sendResponse(Response.fileNotAnalyzed(request, file));
           return;
         }
-        var session = analysisDriver.currentSession;
 
         var completion = params.completion;
         var builder = ChangeBuilder(session: session);
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 3949a6f..ba1fbce 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -557,13 +557,14 @@
     if (driver == null) {
       return errorFixesList;
     }
+    await driver.applyPendingFileChanges();
     var session = driver.currentSession;
     var sourceFactory = driver.sourceFactory;
     var errors = analyzeAnalysisOptions(
       optionsFile.createSource(),
       content,
       sourceFactory,
-      driver.currentSession.analysisContext.contextRoot.root.path,
+      session.analysisContext.contextRoot.root.path,
     );
     var options = _getOptions(sourceFactory, content);
     if (options == null) {
@@ -602,7 +603,9 @@
       for (var error in result.errors) {
         var errorLine = lineInfo.getLocation(error.offset).lineNumber;
         if (errorLine == requestLine) {
-          var workspace = DartChangeWorkspace(server.currentSessions);
+          var workspace = DartChangeWorkspace(
+            await server.currentSessions,
+          );
           var context = DartFixContextImpl(
               server.instrumentationService, workspace, result, error);
 
@@ -652,11 +655,10 @@
     var document =
         parseFragment(content, container: MANIFEST_TAG, generateSpans: true);
     var validator = ManifestValidator(manifestFile.createSource());
-    var driver = server.getAnalysisDriver(file);
-    if (driver == null) {
+    var session = await server.getAnalysisSession(file);
+    if (session == null) {
       return errorFixesList;
     }
-    var session = driver.currentSession;
     var errors = validator.validate(content, true);
     for (var error in errors) {
       var generator = ManifestFixGenerator(error, content, document);
@@ -688,8 +690,8 @@
     if (content == null) {
       return errorFixesList;
     }
-    var driver = server.getAnalysisDriver(file);
-    if (driver == null) {
+    var session = await server.getAnalysisSession(file);
+    if (session == null) {
       return errorFixesList;
     }
     YamlDocument document;
@@ -704,7 +706,6 @@
     }
     var validator =
         PubspecValidator(resourceProvider, pubspecFile.createSource());
-    var session = driver.currentSession;
     var errors = validator.validate(yamlContent.nodes);
     for (var error in errors) {
       var generator =
@@ -736,7 +737,9 @@
     if (result != null) {
       var context = DartAssistContextImpl(
         server.instrumentationService,
-        DartChangeWorkspace(server.currentSessions),
+        DartChangeWorkspace(
+          await server.currentSessions,
+        ),
         result,
         offset,
         length,
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
index 4d4f523..c4d005f 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/fix_all.dart
@@ -43,7 +43,9 @@
     }
 
     return result.mapResult((result) async {
-      final workspace = DartChangeWorkspace(server.currentSessions);
+      final workspace = DartChangeWorkspace(
+        await server.currentSessions,
+      );
       final processor =
           BulkFixProcessor(server.instrumentationService, workspace);
 
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 521927d..a1e8437 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -253,7 +253,9 @@
     try {
       var context = DartAssistContextImpl(
         server.instrumentationService,
-        DartChangeWorkspace(server.currentSessions),
+        DartChangeWorkspace(
+          await server.currentSessions,
+        ),
         unit,
         offset,
         length,
@@ -344,7 +346,9 @@
         if (errorLine < range.start.line || errorLine > range.end.line) {
           continue;
         }
-        var workspace = DartChangeWorkspace(server.currentSessions);
+        var workspace = DartChangeWorkspace(
+          await server.currentSessions,
+        );
         var context = DartFixContextImpl(
             server.instrumentationService, workspace, unit, error);
         final fixes = await fixContributor.computeFixes(context);
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
index fa99c2b..cd60079 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
@@ -66,8 +66,7 @@
     _latestCompletionItem = item;
     while (item == _latestCompletionItem && timer.elapsed < timeout) {
       try {
-        final analysisDriver = server.getAnalysisDriver(file);
-        final session = analysisDriver?.currentSession;
+        final session = await server.getAnalysisSession(file);
 
         // We shouldn't not get a driver/session, but if we did perhaps the file
         // was removed from the analysis set so assume the request is no longer
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
index 06fdd44..ed196a1 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color_presentation.dart
@@ -123,6 +123,7 @@
         SourceRange(editStart.result, editEnd.result - editStart.result);
 
     final sessionHelper = AnalysisSessionHelper(unit.session);
+    final analysisContext = unit.session.analysisContext;
     final flutter = Flutter.instance;
     final colorType = await sessionHelper.getClass(flutter.widgetsUri, 'Color');
     if (colorType == null) {
@@ -132,6 +133,13 @@
       return success([]);
     }
 
+    // If this file is outside of analysis roots, we cannot build edits for it
+    // so return null to signal to the client that it should not try to modify
+    // the source.
+    if (!analysisContext.contextRoot.isAnalyzed(unit.path)) {
+      return success([]);
+    }
+
     final colorValue = _colorValueForComponents(alpha, red, green, blue);
     final colorValueHex =
         '0x${colorValue.toRadixString(16).toUpperCase().padLeft(8, '0')}';
diff --git a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
index caa3706..c8cd8e6 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/move_file.dart
@@ -55,6 +55,7 @@
     }
 
     driver = drivers.first;
+    await driver.applyPendingFileChanges();
     _session = driver.currentSession;
     if (!resourceProvider.getResource(oldFile).exists) {
       return RefactoringStatus.fatal('$oldFile does not exist.');
@@ -113,8 +114,8 @@
     // If this element is a library, update outgoing references inside the file.
     if (element == libraryElement.definingCompilationUnit) {
       // Handle part-of directives in this library
-      var libraryResult = await driver.currentSession
-          .getResolvedLibraryByElement(libraryElement);
+      var libraryResult =
+          await _session.getResolvedLibraryByElement(libraryElement);
       if (libraryResult is! ResolvedLibraryResult) {
         return;
       }
diff --git a/pkg/analysis_server/test/abstract_context.dart b/pkg/analysis_server/test/abstract_context.dart
index 51d5c28..58804ac 100644
--- a/pkg/analysis_server/test/abstract_context.dart
+++ b/pkg/analysis_server/test/abstract_context.dart
@@ -63,7 +63,7 @@
 
   Folder get sdkRoot => newFolder('/sdk');
 
-  AnalysisSession get session => contextFor(testPackageRootPath).currentSession;
+  Future<AnalysisSession> get session => sessionFor(testPackageRootPath);
 
   String? get testPackageLanguageVersion => latestLanguageVersion;
 
@@ -87,6 +87,7 @@
     var analysisContext = contextFor(testPackageRootPath);
     var files = analysisContext.contextRoot.analyzedFiles().toList();
     for (var path in files) {
+      await analysisContext.applyPendingFileChanges();
       await analysisContext.currentSession.getResolvedUnit(path);
     }
   }
@@ -168,10 +169,16 @@
 
   Future<ResolvedUnitResult> resolveFile(String path) async {
     path = convertPath(path);
-    var session = contextFor(path).currentSession;
+    var session = await sessionFor(path);
     return await session.getResolvedUnit(path) as ResolvedUnitResult;
   }
 
+  Future<AnalysisSession> sessionFor(String path) async {
+    var analysisContext = _contextFor(path);
+    await analysisContext.applyPendingFileChanges();
+    return analysisContext.currentSession;
+  }
+
   @mustCallSuper
   void setUp() {
     if (!_lintRulesAreRegistered) {
diff --git a/pkg/analysis_server/test/abstract_single_unit.dart b/pkg/analysis_server/test/abstract_single_unit.dart
index 0c09690..4def5df 100644
--- a/pkg/analysis_server/test/abstract_single_unit.dart
+++ b/pkg/analysis_server/test/abstract_single_unit.dart
@@ -65,7 +65,8 @@
   }
 
   Future<void> resolveFile2(String path) async {
-    var result = await session.getResolvedUnit(path) as ResolvedUnitResult;
+    var result =
+        await (await session).getResolvedUnit(path) as ResolvedUnitResult;
     testAnalysisResult = result;
     testCode = result.content;
     testUnit = result.unit;
diff --git a/pkg/analysis_server/test/analysis/get_navigation_test.dart b/pkg/analysis_server/test/analysis/get_navigation_test.dart
index 1f81f60..793dad6 100644
--- a/pkg/analysis_server/test/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/get_navigation_test.dart
@@ -66,6 +66,21 @@
     assertHasRegion('String]');
   }
 
+  Future<void> test_comment_toolSeeCodeComment() async {
+    var examplePath = 'examples/api/foo.dart';
+    newFile(convertPath('$testFolder/$examplePath'), content: '');
+    addTestFile('''
+/// {@tool dartpad}
+/// ** See code in $examplePath **
+/// {@end-tool}
+String main() {
+}''');
+    await waitForTasksFinished();
+    await _getNavigation(testFile, testCode.indexOf(examplePath), 1);
+    expect(regions, hasLength(1));
+    assertHasRegion(examplePath, examplePath.length);
+  }
+
   Future<void> test_constructorInvocation() async {
     // Check that a constructor invocation navigates to the constructor and not
     // the class.
diff --git a/pkg/analysis_server/test/analysis/update_content_test.dart b/pkg/analysis_server/test/analysis/update_content_test.dart
index f85f7d4..0f1313f 100644
--- a/pkg/analysis_server/test/analysis/update_content_test.dart
+++ b/pkg/analysis_server/test/analysis/update_content_test.dart
@@ -157,20 +157,20 @@
     var driver2 = server.getAnalysisDriver(filePath2)!;
 
     // no sources
-    expect(_getUserSources(driver1), isEmpty);
-    expect(_getUserSources(driver2), isEmpty);
+    expect(await _getUserSources(driver1), isEmpty);
+    expect(await _getUserSources(driver2), isEmpty);
 
     // add an overlay - new Source in context1
     server.updateContent('1', {filePath1: AddContentOverlay('')});
-    expect(_getUserSources(driver1), [filePath1]);
-    expect(_getUserSources(driver2), isEmpty);
+    expect(await _getUserSources(driver1), [filePath1]);
+    expect(await _getUserSources(driver2), isEmpty);
 
     // remove the overlay - no sources
     server.updateContent('2', {filePath1: RemoveContentOverlay()});
 
     // The file isn't removed from the list of added sources.
 //    expect(_getUserSources(driver1), isEmpty);
-    expect(_getUserSources(driver2), isEmpty);
+    expect(await _getUserSources(driver2), isEmpty);
   }
 
   @failingTest
@@ -259,8 +259,9 @@
     expect(overlay, const TypeMatcher<RemoveContentOverlay>());
   }
 
-  List<String> _getUserSources(AnalysisDriver driver) {
+  Future<List<String>> _getUserSources(AnalysisDriver driver) async {
     var sources = <String>[];
+    await driver.applyPendingFileChanges();
     driver.addedFiles.forEach((path) {
       if (path.startsWith(convertPath('/User/'))) {
         sources.add(path);
diff --git a/pkg/analysis_server/test/lsp/document_color_test.dart b/pkg/analysis_server/test/lsp/document_color_test.dart
index 55dbf74..755752a 100644
--- a/pkg/analysis_server/test/lsp/document_color_test.dart
+++ b/pkg/analysis_server/test/lsp/document_color_test.dart
@@ -71,6 +71,29 @@
     expect(colors, isEmpty);
   }
 
+  Future<void> test_outsideAnalysisRoot() async {
+    const content = '''
+    import 'package:flutter/material.dart';
+
+    const white = [[Color(0xFFFFFFFF)]];
+    ''';
+    colorRange = rangeFromMarkers(content);
+
+    final outsideRootFilePath = convertPath('/home/other/test.dart');
+    newFile(outsideRootFilePath, content: withoutMarkers(content));
+    await initialize();
+
+    final colorPresentations = await getColorPresentation(
+      Uri.file(outsideRootFilePath).toString(),
+      colorRange,
+      Color(alpha: 1, red: 1, green: 0, blue: 0),
+    );
+
+    /// Because this file is not editable (it's outside the analysis roots) an
+    /// empty result will be returned.
+    expect(colorPresentations, isEmpty);
+  }
+
   Future<void> test_simpleColor() async {
     const content = '''
     import 'package:flutter/material.dart';
@@ -106,8 +129,11 @@
     String label, {
     String? colorCode,
     String? importUri,
+    bool withEdits = true,
   }) {
-    final edit = TextEdit(range: colorRange, newText: colorCode ?? label);
+    final edit = withEdits
+        ? TextEdit(range: colorRange, newText: colorCode ?? label)
+        : null;
     final additionalEdits = importUri != null
         ? [TextEdit(range: importRange, newText: "import '$importUri';\n\n")]
         : null;
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
index c05f315..371d0a0 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_contributor_util.dart
@@ -558,7 +558,8 @@
       DartCompletionRequest request);
 
   Future computeSuggestions({int times = 200}) async {
-    result = await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+    result =
+        await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
 
     // Build the request
     var request = DartCompletionRequest.forResolvedUnit(
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart b/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
index be920e7..e1c149a 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_manager_test.dart
@@ -51,7 +51,7 @@
 
     // Build the request
     var resolvedUnit =
-        await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
     request = DartCompletionRequest.forResolvedUnit(
       resolvedUnit: resolvedUnit,
       offset: completionOffset,
diff --git a/pkg/analysis_server/test/services/correction/organize_directives_test.dart b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
index 8ecae36..737ad25 100644
--- a/pkg/analysis_server/test/services/correction/organize_directives_test.dart
+++ b/pkg/analysis_server/test/services/correction/organize_directives_test.dart
@@ -911,7 +911,8 @@
 
   Future<void> _computeUnitAndErrors(String code) async {
     addTestSource(code);
-    var result = await session.getResolvedUnit(testFile) as ResolvedUnitResult;
+    var result =
+        await (await session).getResolvedUnit(testFile) as ResolvedUnitResult;
     testUnit = result.unit;
     testErrors = result.errors;
   }
diff --git a/pkg/analysis_server/test/services/correction/sort_members_test.dart b/pkg/analysis_server/test/services/correction/sort_members_test.dart
index ab012ef..ff4bf5f 100644
--- a/pkg/analysis_server/test/services/correction/sort_members_test.dart
+++ b/pkg/analysis_server/test/services/correction/sort_members_test.dart
@@ -1512,7 +1512,8 @@
 
   Future<void> _parseTestUnit(String code) async {
     addTestSource(code);
-    var result = await session.getParsedUnit2(testFile) as ParsedUnitResult;
+    var result =
+        await (await session).getParsedUnit2(testFile) as ParsedUnitResult;
     lineInfo = result.lineInfo;
     testUnit = result.unit;
   }
diff --git a/pkg/analysis_server/test/services/refactoring/move_file_test.dart b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
index 983df07..7c2c008 100644
--- a/pkg/analysis_server/test/services/refactoring/move_file_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/move_file_test.dart
@@ -65,7 +65,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('$testPackageLibPath/222/new_name.dart',
         oldFile: file.path);
@@ -89,7 +89,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('/home/test0/test1/test3/lib/111/name.dart',
         oldFile: file.path);
@@ -113,7 +113,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('/home/test0/test1/test2/test3/lib/111/name.dart',
         oldFile: file.path);
@@ -137,7 +137,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('/home/test0/test1/lib/111/name.dart',
         oldFile: file.path);
@@ -159,7 +159,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('$testPackageLibPath/222/new_name.dart',
         oldFile: file.path);
@@ -181,7 +181,7 @@
     // testAnalysisResult manually here, the path is referenced through the
     // referenced File object to run on Windows:
     testAnalysisResult =
-        await session.getResolvedUnit(file.path) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(file.path) as ResolvedUnitResult;
 
     _createRefactoring('$testPackageLibPath/new_name.dart', oldFile: file.path);
     await _assertSuccessfulRefactoring();
diff --git a/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart b/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
index 3c99d77..037bb02 100644
--- a/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/closing_labels_computer_test.dart
@@ -398,7 +398,7 @@
   Future<List<ClosingLabel>> _computeElements(String sourceContent) async {
     newFile(sourcePath, content: sourceContent);
     var result =
-        await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
     var computer = DartUnitClosingLabelsComputer(result.lineInfo, result.unit);
     return computer.compute();
   }
diff --git a/pkg/analysis_server/test/src/computer/color_computer_test.dart b/pkg/analysis_server/test/src/computer/color_computer_test.dart
index 1178812..c6ec12a 100644
--- a/pkg/analysis_server/test/src/computer/color_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/color_computer_test.dart
@@ -130,12 +130,12 @@
     newFile(testPath, content: dartCode);
     if (otherCode != null) {
       newFile(otherPath, content: otherCode);
-      final otherResult =
-          await session.getResolvedUnit(otherPath) as ResolvedUnitResult;
+      final otherResult = await (await session).getResolvedUnit(otherPath)
+          as ResolvedUnitResult;
       expectNoErrors(otherResult);
     }
     final result =
-        await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
     expectNoErrors(result);
 
     computer = ColorComputer(result);
diff --git a/pkg/analysis_server/test/src/computer/folding_computer_test.dart b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
index bba4d0b..b0a445f 100644
--- a/pkg/analysis_server/test/src/computer/folding_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
@@ -582,7 +582,7 @@
   Future<List<FoldingRegion>> _computeRegions(String sourceContent) async {
     newFile(sourcePath, content: sourceContent);
     var result =
-        await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
     var computer = DartUnitFoldingComputer(result.lineInfo, result.unit);
     return computer.compute();
   }
diff --git a/pkg/analysis_server/test/src/computer/highlights_computer_test.dart b/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
index d1a90d3..0314f65 100644
--- a/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/highlights_computer_test.dart
@@ -114,7 +114,7 @@
     this.content = content;
     newFile(sourcePath, content: content);
     var result =
-        await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
 
     if (hasErrors) {
       expect(result.errors, isNotEmpty);
diff --git a/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart b/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
index 133ab24..4cf9605 100644
--- a/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/import_elements_computer_test.dart
@@ -50,7 +50,8 @@
   Future<void> createBuilder(String content) async {
     originalContent = content;
     newFile(path, content: content);
-    var result = await session.getResolvedUnit(path) as ResolvedUnitResult;
+    var result =
+        await (await session).getResolvedUnit(path) as ResolvedUnitResult;
     computer = ImportElementsComputer(resourceProvider, result);
   }
 
diff --git a/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart b/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
index cf1cf9a..e3140d3 100644
--- a/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/imported_elements_computer_test.dart
@@ -471,7 +471,7 @@
     // TODO(brianwilkerson) Automatically extract the selection from the content.
     newFile(sourcePath, content: content);
     var result =
-        await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
     var computer = ImportedElementsComputer(
         result.unit, content.indexOf(selection), selection.length);
     importedElements = computer.compute();
diff --git a/pkg/analysis_server/test/src/computer/outline_computer_test.dart b/pkg/analysis_server/test/src/computer/outline_computer_test.dart
index 79818f0..46d85a7 100644
--- a/pkg/analysis_server/test/src/computer/outline_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/outline_computer_test.dart
@@ -33,7 +33,7 @@
     testCode = code;
     newFile(testPath, content: code);
     var resolveResult =
-        await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
     return DartUnitOutlineComputer(
       resolveResult,
       withBasicFlutter: true,
diff --git a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
index ee07e86..0a3539b 100644
--- a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart
@@ -194,7 +194,7 @@
       String sourceContent, int offset) async {
     newFile(sourcePath, content: sourceContent);
     var result =
-        await session.getResolvedUnit(sourcePath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(sourcePath) as ResolvedUnitResult;
     var computer = DartSelectionRangeComputer(result.unit, offset);
     return computer.compute();
   }
diff --git a/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart b/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
index f59bb45..e8fd170 100644
--- a/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
+++ b/pkg/analysis_server/test/src/flutter/flutter_outline_computer_test.dart
@@ -586,7 +586,7 @@
     testCode = code;
     newFile(testPath, content: code);
     resolveResult =
-        await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+        await (await session).getResolvedUnit(testPath) as ResolvedUnitResult;
     computer = FlutterOutlineComputer(resolveResult);
     return computer.compute();
   }
diff --git a/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart b/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
index 9dc4e9f..51b9dc0 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/assist_processor.dart
@@ -31,8 +31,8 @@
   AssistKind get kind;
 
   /// The workspace in which fixes contributor operates.
-  ChangeWorkspace get workspace {
-    return DartChangeWorkspace([session]);
+  Future<ChangeWorkspace> get workspace async {
+    return DartChangeWorkspace([await session]);
   }
 
   @override
@@ -194,7 +194,7 @@
   Future<List<Assist>> _computeAssists() async {
     var context = DartAssistContextImpl(
       TestInstrumentationService(),
-      workspace,
+      await workspace,
       testAnalysisResult,
       _offset,
       _length,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
index 2b5f910..d75d912 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/add_missing_parameter_required_test.dart
@@ -161,8 +161,8 @@
   ChangeWorkspace? _workspace;
 
   @override
-  ChangeWorkspace get workspace {
-    return _workspace ?? super.workspace;
+  Future<ChangeWorkspace> get workspace async {
+    return _workspace ?? await super.workspace;
   }
 
   Future<void> test_function_inPackage_inWorkspace() async {
@@ -174,8 +174,8 @@
     );
 
     _workspace = DartChangeWorkspace([
-      session,
-      getContext('/home/aaa').currentSession,
+      await session,
+      await sessionFor('/home/aaa'),
     ]);
 
     await resolveTestCode('''
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
index f6ccc74..ba29ed2 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
@@ -78,7 +78,7 @@
     var statement = body.block.statements[0] as ExpressionStatement;
     var node = statement.expression;
     var template = CodeTemplate(CodeTemplateKind.expression, components, null);
-    var builder = ChangeBuilder(session: session);
+    var builder = ChangeBuilder(session: await session);
     var context = TemplateContext(node, CorrectionUtils(testAnalysisResult));
     await builder.addDartFileEdit(testFile, (builder) {
       builder.addInsertion(0, (builder) {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
index 4205071..e6d1f6c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_manager_test.dart
@@ -35,7 +35,7 @@
 
     var testFile = convertPath('$testPackageLibPath/test.dart');
     addSource(testFile, '');
-    var result = await session.getResolvedLibraryValid(testFile);
+    var result = await (await session).getResolvedLibraryValid(testFile);
     var sets = manager.forLibrary(result.element);
     expect(sets, hasLength(2));
   }
@@ -46,7 +46,7 @@
     addSource('/home/test/pubspec.yaml', '');
     var testFile = convertPath('$testPackageLibPath/test.dart');
     addSource(testFile, '');
-    var result = await session.getResolvedLibraryValid(testFile);
+    var result = await (await session).getResolvedLibraryValid(testFile);
     var sets = manager.forLibrary(result.element);
     expect(sets, hasLength(0));
   }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
index 87e18e9..c0f1f6e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
@@ -33,8 +33,8 @@
   late String resultCode;
 
   /// The workspace in which fixes contributor operates.
-  ChangeWorkspace get workspace {
-    return DartChangeWorkspace([session]);
+  Future<ChangeWorkspace> get workspace async {
+    return DartChangeWorkspace([await session]);
   }
 
   /// Find the error that is to be fixed by computing the errors in the file,
@@ -97,8 +97,8 @@
   bool get useConfigFiles => false;
 
   /// The workspace in which fixes contributor operates.
-  DartChangeWorkspace get workspace {
-    return DartChangeWorkspace([session]);
+  Future<DartChangeWorkspace> get workspace async {
+    return DartChangeWorkspace([await session]);
   }
 
   Future<void> assertHasFix(String expected) async {
@@ -124,7 +124,8 @@
     var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
     var analysisContext = contextFor(testFile);
     tracker.addContext(analysisContext);
-    var processor = BulkFixProcessor(TestInstrumentationService(), workspace,
+    var processor = BulkFixProcessor(
+        TestInstrumentationService(), await workspace,
         useConfigFiles: useConfigFiles);
     await processor.fixErrors([analysisContext]);
     return processor;
@@ -196,7 +197,7 @@
   Future<List<Fix>> _computeFixes(AnalysisError error) async {
     var context = DartFixContextImpl(
       TestInstrumentationService(),
-      workspace,
+      await workspace,
       testAnalysisResult,
       error,
     );
@@ -500,7 +501,7 @@
   Future<List<Fix>> _computeFixes(AnalysisError error) async {
     var context = DartFixContextImpl(
       TestInstrumentationService(),
-      workspace,
+      await workspace,
       testAnalysisResult,
       error,
     );
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java b/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
index e858201..2ff6c65 100644
--- a/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
+++ b/pkg/analysis_server/tool/spec/generated/java/types/LinkedEditGroup.java
@@ -30,6 +30,11 @@
  * wanted to let the user edit the variable name after the operation, all occurrences of the name
  * could be edited simultaneously.
  *
+ * Edit groups may have a length of 0 and function as tabstops where there is no default text, for
+ * example, an edit that inserts an if statement might provide an empty group between parens where
+ * a condition should be typed. For this reason, it's also valid for a group to contain only a
+ * single position that is not linked to others.
+ *
  * @coverage dart.server.generated.types
  */
 @SuppressWarnings("unused")
diff --git a/pkg/analyzer/lib/dart/analysis/analysis_context.dart b/pkg/analyzer/lib/dart/analysis/analysis_context.dart
index 124fe2d..b30e1fc 100644
--- a/pkg/analyzer/lib/dart/analysis/analysis_context.dart
+++ b/pkg/analyzer/lib/dart/analysis/analysis_context.dart
@@ -35,4 +35,8 @@
   /// Return the workspace for containing the context root.
   @Deprecated('Use contextRoot.workspace instead')
   Workspace get workspace;
+
+  /// Return a [Future] that completes after pending file changes are applied,
+  /// so that [currentSession] can be used to compute results.
+  Future<void> applyPendingFileChanges();
 }
diff --git a/pkg/analyzer/lib/error/listener.dart b/pkg/analyzer/lib/error/listener.dart
index 807499c..113f4bb 100644
--- a/pkg/analyzer/lib/error/listener.dart
+++ b/pkg/analyzer/lib/error/listener.dart
@@ -70,8 +70,9 @@
   /// is used to compute the location of the error.
   void reportErrorForElement(ErrorCode errorCode, Element element,
       [List<Object>? arguments]) {
-    reportErrorForOffset(errorCode, element.nonSynthetic.nameOffset,
-        element.nameLength, arguments);
+    var nonSynthetic = element.nonSynthetic;
+    reportErrorForOffset(
+        errorCode, nonSynthetic.nameOffset, nonSynthetic.nameLength, arguments);
   }
 
   /// Report a diagnostic with the given [code] and [arguments]. The
diff --git a/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
index bd2a210..2667185 100644
--- a/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
+++ b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
@@ -77,6 +77,12 @@
     return _driver.currentSession;
   }
 
+  /// Return a [Future] that completes after pending file changes are applied,
+  /// so that [currentSession] can be used to compute results.
+  Future<void> applyPendingFileChanges() {
+    return _driver.applyPendingFileChanges();
+  }
+
   /// The file with the given [path] might have changed - updated, added or
   /// removed. Or not, we don't know. Or it might have, but then changed back.
   ///
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 8bdb3d8..c1d72a8 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -84,6 +84,8 @@
   /// The version of data format, should be incremented on every format change.
   static const int DATA_VERSION = 209;
 
+  static const bool _applyFileChangesSynchronously = true;
+
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
   static int allowedNumberOfContextsToWrite = 10;
@@ -140,6 +142,12 @@
   /// The set of priority files, that should be analyzed sooner.
   final _priorityFiles = <String>{};
 
+  /// The file changes that should be applied before processing requests.
+  final List<_FileChange> _pendingFileChanges = [];
+
+  /// The completers to complete after [_pendingFileChanges] are applied.
+  final _pendingFileChangesCompleters = <Completer<void>>[];
+
   /// The mapping from the files for which analysis was requested using
   /// [getResult] to the [Completer]s to report the result.
   final _requestedFiles = <String, List<Completer<ResolvedUnitResult>>>{};
@@ -287,7 +295,8 @@
 
   @override
   bool get hasFilesToAnalyze {
-    return _fileTracker.hasChangedFiles ||
+    return _pendingFileChanges.isNotEmpty ||
+        _fileTracker.hasChangedFiles ||
         _requestedFiles.isNotEmpty ||
         _requestedParts.isNotEmpty ||
         _fileTracker.hasPendingFiles ||
@@ -407,6 +416,9 @@
         }
       }
     }
+    if (_pendingFileChanges.isNotEmpty) {
+      return AnalysisDriverPriority.general;
+    }
     if (_fileTracker.hasChangedFiles) {
       return AnalysisDriverPriority.changedFiles;
     }
@@ -425,7 +437,8 @@
     if (_errorsRequestedParts.isNotEmpty ||
         _requestedParts.isNotEmpty ||
         _partsToAnalyze.isNotEmpty ||
-        _unitElementRequestedParts.isNotEmpty) {
+        _unitElementRequestedParts.isNotEmpty ||
+        _pendingFileChangesCompleters.isNotEmpty) {
       return AnalysisDriverPriority.general;
     }
     return AnalysisDriverPriority.nothing;
@@ -439,12 +452,30 @@
     }
     if (file_paths.isDart(resourceProvider.pathContext, path)) {
       _priorityResults.clear();
-      _removePotentiallyAffectedLibraries(path);
-      _fileTracker.addFile(path);
+      if (_applyFileChangesSynchronously) {
+        _removePotentiallyAffectedLibraries(path);
+        _fileTracker.addFile(path);
+      } else {
+        _pendingFileChanges.add(
+          _FileChange(path, _FileChangeKind.add),
+        );
+      }
       _scheduler.notify(this);
     }
   }
 
+  /// Return a [Future] that completes after pending file changes are applied,
+  /// so that [currentSession] can be used to compute results.
+  Future<void> applyPendingFileChanges() {
+    if (_pendingFileChanges.isNotEmpty) {
+      var completer = Completer<void>();
+      _pendingFileChangesCompleters.add(completer);
+      return completer.future;
+    } else {
+      return Future.value();
+    }
+  }
+
   /// The file with the given [path] might have changed - updated, added or
   /// removed. Or not, we don't know. Or it might have, but then changed back.
   ///
@@ -468,8 +499,14 @@
     }
     if (file_paths.isDart(resourceProvider.pathContext, path)) {
       _priorityResults.clear();
-      _removePotentiallyAffectedLibraries(path);
-      _fileTracker.changeFile(path);
+      if (_applyFileChangesSynchronously) {
+        _removePotentiallyAffectedLibraries(path);
+        _fileTracker.changeFile(path);
+      } else {
+        _pendingFileChanges.add(
+          _FileChange(path, _FileChangeKind.change),
+        );
+      }
       _scheduler.notify(this);
     }
   }
@@ -630,6 +667,8 @@
       return InvalidPathResult();
     }
 
+    _applyPendingFileChanges();
+
     FileState file = _fileTracker.getFile(path);
     return FileResultImpl(
         currentSession, path, file.uri, file.lineInfo, file.isPart);
@@ -987,6 +1026,8 @@
       );
     }
 
+    _applyPendingFileChanges();
+
     var completer = Completer<UnitElementResult>();
     _unitElementRequestedFiles
         .putIfAbsent(path, () => <Completer<UnitElementResult>>[])
@@ -1063,6 +1104,8 @@
       return InvalidPathResult();
     }
 
+    _applyPendingFileChanges();
+
     FileState file = _fileTracker.getFile(path);
     RecordingErrorListener listener = RecordingErrorListener();
     CompilationUnit unit = file.parse(listener);
@@ -1342,8 +1385,14 @@
     if (file_paths.isDart(resourceProvider.pathContext, path)) {
       _lastProducedSignatures.remove(path);
       _priorityResults.clear();
-      _removePotentiallyAffectedLibraries(path);
-      _fileTracker.removeFile(path);
+      if (_applyFileChangesSynchronously) {
+        _removePotentiallyAffectedLibraries(path);
+        _fileTracker.removeFile(path);
+      } else {
+        _pendingFileChanges.add(
+          _FileChange(path, _FileChangeKind.remove),
+        );
+      }
       _scheduler.notify(this);
     }
   }
@@ -1414,6 +1463,33 @@
     }
   }
 
+  void _applyPendingFileChanges() {
+    for (var fileChange in _pendingFileChanges) {
+      var path = fileChange.path;
+      _removePotentiallyAffectedLibraries(path);
+      switch (fileChange.kind) {
+        case _FileChangeKind.add:
+          _fileTracker.addFile(path);
+          break;
+        case _FileChangeKind.change:
+          _fileTracker.changeFile(path);
+          break;
+        case _FileChangeKind.remove:
+          _fileTracker.removeFile(path);
+          break;
+      }
+    }
+    _pendingFileChanges.clear();
+
+    if (_pendingFileChangesCompleters.isNotEmpty) {
+      var completers = _pendingFileChangesCompleters.toList();
+      _pendingFileChangesCompleters.clear();
+      for (var completer in completers) {
+        completer.complete();
+      }
+    }
+  }
+
   /// There was an exception during a file analysis, we don't know why.
   /// But it might have been caused by an inconsistency of files state, and
   /// the library context state. Reset the library context, and hope that
@@ -2139,6 +2215,12 @@
 
       await _hasWork.signal;
 
+      for (var driver in _drivers) {
+        if (driver is AnalysisDriver) {
+          driver._applyPendingFileChanges();
+        }
+      }
+
       // Transition to analyzing if there are files to analyze.
       if (_hasFilesToAnalyze) {
         _statusSupport.transitionToAnalyzing();
@@ -2498,6 +2580,20 @@
   String toString() => '$exception\n$stackTrace';
 }
 
+class _FileChange {
+  final String path;
+  final _FileChangeKind kind;
+
+  _FileChange(this.path, this.kind);
+
+  @override
+  String toString() {
+    return '[path: $path][kind: $kind]';
+  }
+}
+
+enum _FileChangeKind { add, change, remove }
+
 /// Task that computes the list of files that were added to the driver and
 /// declare a class member with the given [name].
 class _FilesDefiningClassMemberNameTask {
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
index a83f20f..b00038a 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
@@ -53,4 +53,9 @@
   Workspace get workspace {
     return contextRoot.workspace;
   }
+
+  @override
+  Future<void> applyPendingFileChanges() {
+    return driver.applyPendingFileChanges();
+  }
 }
diff --git a/pkg/analyzer/lib/src/dart/analysis/session.dart b/pkg/analyzer/lib/src/dart/analysis/session.dart
index c635635..acdf27e 100644
--- a/pkg/analyzer/lib/src/dart/analysis/session.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/session.dart
@@ -55,41 +55,65 @@
   driver.AnalysisDriver getDriver() => _driver;
 
   @override
-  Future<SomeErrorsResult> getErrors(String path) {
+  Future<SomeErrorsResult> getErrors(String path) async {
     _checkConsistency();
-    return _driver.getErrors(path);
+    try {
+      return await _driver.getErrors(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @Deprecated('Use getFile2() instead')
   @override
   SomeFileResult getFile(String path) {
     _checkConsistency();
-    return _driver.getFileSync(path);
+    try {
+      return _driver.getFileSync(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
   Future<SomeFileResult> getFile2(String path) async {
     _checkConsistency();
-    return _driver.getFile(path);
+    try {
+      return await _driver.getFile(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
-  Future<SomeLibraryElementResult> getLibraryByUri(String uri) {
+  Future<SomeLibraryElementResult> getLibraryByUri(String uri) async {
     _checkConsistency();
-    return _driver.getLibraryByUri(uri);
+    try {
+      return await _driver.getLibraryByUri(uri);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @Deprecated('Use getParsedLibrary2() instead')
   @override
   SomeParsedLibraryResult getParsedLibrary(String path) {
     _checkConsistency();
-    return _driver.getParsedLibrary(path);
+    try {
+      return _driver.getParsedLibrary(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
   Future<SomeParsedLibraryResult> getParsedLibrary2(String path) async {
     _checkConsistency();
-    return _driver.getParsedLibrary2(path);
+    try {
+      return await _driver.getParsedLibrary2(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @Deprecated('Use getParsedLibraryByElement2() instead')
@@ -101,7 +125,11 @@
       return NotElementOfThisSessionResult();
     }
 
-    return _driver.getParsedLibraryByUri(element.source.uri);
+    try {
+      return _driver.getParsedLibraryByUri(element.source.uri);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
@@ -114,53 +142,79 @@
       return NotElementOfThisSessionResult();
     }
 
-    return _driver.getParsedLibraryByUri2(element.source.uri);
+    try {
+      return await _driver.getParsedLibraryByUri2(element.source.uri);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @Deprecated('Use getParsedUnit2() instead')
   @override
   SomeParsedUnitResult getParsedUnit(String path) {
     _checkConsistency();
-    return _driver.parseFileSync(path);
+    try {
+      return _driver.parseFileSync(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
   Future<SomeParsedUnitResult> getParsedUnit2(String path) async {
     _checkConsistency();
-    return _driver.parseFile(path);
+    try {
+      return await _driver.parseFile(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
-  Future<SomeResolvedLibraryResult> getResolvedLibrary(String path) {
+  Future<SomeResolvedLibraryResult> getResolvedLibrary(String path) async {
     _checkConsistency();
-    return _driver.getResolvedLibrary(path);
+    try {
+      return await _driver.getResolvedLibrary(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
   Future<SomeResolvedLibraryResult> getResolvedLibraryByElement(
     LibraryElement element,
-  ) {
+  ) async {
     _checkConsistency();
 
     if (element.session != this) {
-      return Future.value(
-        NotElementOfThisSessionResult(),
-      );
+      return NotElementOfThisSessionResult();
     }
 
-    return _driver.getResolvedLibraryByUri(element.source.uri);
+    try {
+      return await _driver.getResolvedLibraryByUri(element.source.uri);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
-  Future<SomeResolvedUnitResult> getResolvedUnit(String path) {
+  Future<SomeResolvedUnitResult> getResolvedUnit(String path) async {
     _checkConsistency();
-    return _driver.getResult(path);
+    try {
+      return await _driver.getResult(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   @override
   Future<SomeUnitElementResult> getUnitElement(String path) {
     _checkConsistency();
-    return _driver.getUnitElement(path);
+    try {
+      return _driver.getUnitElement(path);
+    } finally {
+      _checkConsistency();
+    }
   }
 
   /// Check to see that results from this session will be consistent, and throw
diff --git a/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart b/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
index 623f429..0cd2e14 100644
--- a/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
+++ b/pkg/analyzer/lib/src/error/duplicate_definition_verifier.dart
@@ -93,7 +93,9 @@
           member.name,
           setterScope: member.isStatic ? staticSetters : instanceSetters,
         );
-        _checkValuesDeclarationInEnum(member.name);
+        if (!(member.isStatic && member.isSetter)) {
+          _checkValuesDeclarationInEnum(member.name);
+        }
       }
     }
 
diff --git a/pkg/analyzer/test/src/dart/analysis/dependency/base.dart b/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
index 0c2aefa..fa356af 100644
--- a/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/dependency/base.dart
@@ -61,7 +61,10 @@
 //    }
 
     newFile(path, content: content);
-    driverFor(path).changeFile(path);
+
+    var analysisDriver = driverFor(path);
+    analysisDriver.changeFile(path);
+    await analysisDriver.applyPendingFileChanges();
 
     var units = await _resolveLibrary(path);
     var uri = units.first.declaredElement!.source.uri;
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
index cabe570..421716f 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_caching_test.dart
@@ -64,7 +64,10 @@
 }
 ''');
 
-    driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+    var analysisDriver = driverFor(testFilePathPlatform);
+    analysisDriver.changeFile(testFilePathPlatform);
+    await analysisDriver.applyPendingFileChanges();
+
     await resolveTestCode(r'''
 class A {
   factory A() =;
@@ -80,7 +83,10 @@
 }
 ''');
 
-    driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+    var analysisDriver = driverFor(testFilePathPlatform);
+    analysisDriver.changeFile(testFilePathPlatform);
+    await analysisDriver.applyPendingFileChanges();
+
     await resolveTestCode(r'''
 class A {
   factory A() =
@@ -96,7 +102,10 @@
 }
 ''');
 
-    driverFor(testFilePathPlatform).changeFile(testFilePathPlatform);
+    var analysisDriver = driverFor(testFilePathPlatform);
+    analysisDriver.changeFile(testFilePathPlatform);
+    await analysisDriver.applyPendingFileChanges();
+
     await resolveTestCode(r'''
 class A {
   const
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 6a62c91..1aba4d2 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -407,10 +407,12 @@
     var b = convertPath('/test/lib/b.dart');
 
     driver.addFile(a);
+    await driver.applyPendingFileChanges();
     expect(driver.addedFiles, contains(a));
     expect(driver.addedFiles, isNot(contains(b)));
 
     driver.removeFile(a);
+    await driver.applyPendingFileChanges();
     expect(driver.addedFiles, isNot(contains(a)));
     expect(driver.addedFiles, isNot(contains(b)));
   }
@@ -830,6 +832,12 @@
     // Change `b.dart`, also removes `c.dart` and `d.dart` that import it.
     // But `a.dart` and `d.dart` is not affected.
     driver.changeFile(b.path);
+    await driver.applyPendingFileChanges();
+
+    // We have a new session.
+    var session2 = driver.currentSession;
+    expect(session2, isNot(session1));
+
     driver.assertLoadedLibraryUriSet(
       excluded: [
         'package:test/b.dart',
@@ -842,10 +850,6 @@
       ],
     );
 
-    // We have a new session.
-    var session2 = driver.currentSession;
-    expect(session2, isNot(session1));
-
     // `a.dart` and `e.dart` moved to the new session.
     // Invalidated libraries stuck with the old session.
     expect(a_element.session, session2);
@@ -896,6 +900,7 @@
     // Removes `c.dart` that imports `b.dart`.
     // But `d.dart` is not affected.
     driver.changeFile(a.path);
+    await driver.applyPendingFileChanges();
     driver.assertLoadedLibraryUriSet(
       excluded: [
         'package:test/b.dart',
@@ -1001,9 +1006,7 @@
 
     // Notify the driver about the change.
     driver.changeFile(testFile);
-
-    // The file was changed, so it is scheduled for analysis.
-    expect(driver.test.fileTracker.isFilePending(testFile), isTrue);
+    await driver.applyPendingFileChanges();
 
     // We get a new result.
     {
@@ -1411,6 +1414,7 @@
 
     // Notify the driver that the file was changed.
     driver.changeFile(a);
+    await driver.applyPendingFileChanges();
 
     // So, `class A {}` is declared now.
     expect((await driver.getFileValid(a)).lineInfo.lineCount, 2);
@@ -2588,6 +2592,7 @@
 
     // Notify the driver that the file was changed.
     driver.changeFile(a);
+    await driver.applyPendingFileChanges();
 
     // So, `class A {}` is declared now.
     {
diff --git a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
index b1a26bd..bcf846f 100644
--- a/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/resolve_for_completion_test.dart
@@ -25,7 +25,7 @@
   String get testFilePathPlatform => convertPath(testFilePath);
 
   test_class__fieldDeclaration_type_namedType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   var f1 = 0;
   doub^ f2 = null;
@@ -37,7 +37,7 @@
   }
 
   test_class__fieldDeclaration_type_namedType_typeArgument_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   var f1 = 0;
   List<doub^>? f2 = null;
@@ -49,7 +49,7 @@
   }
 
   test_class_extends_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A extends foo^ {}
 ''');
 
@@ -57,7 +57,7 @@
   }
 
   test_class_fieldDeclaration_initializer() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   var f1 = 0;
   var f2 = foo^;
@@ -71,7 +71,7 @@
   }
 
   test_class_implements_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A implements foo^ {}
 ''');
 
@@ -79,7 +79,7 @@
   }
 
   test_class_methodDeclaration_body() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {}
 
 class B {
@@ -101,7 +101,7 @@
   }
 
   test_class_methodDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   void foo^() {
     print(0);
@@ -113,7 +113,7 @@
   }
 
   test_class_methodDeclaration_returnType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   doub^ foo() {}
 }
@@ -123,7 +123,7 @@
   }
 
   test_class_with_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A with foo^ {}
 ''');
 
@@ -131,7 +131,7 @@
   }
 
   test_constructorDeclaration_body() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {}
 
 class B {
@@ -153,7 +153,7 @@
   }
 
   test_constructorDeclaration_fieldFormalParameter_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   final int f;
   A(this.^);
@@ -166,7 +166,7 @@
   }
 
   test_constructorDeclaration_fieldInitializer_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {}
 
 class B {
@@ -186,7 +186,7 @@
   }
 
   test_constructorDeclaration_fieldInitializer_value() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   var f;
 
@@ -203,7 +203,7 @@
   }
 
   test_constructorDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   A.foo^() {
     print(0);
@@ -215,7 +215,7 @@
   }
 
   test_constructorDeclaration_superFormalParameter_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {
   A(int first, double second);
   A.named(int third);
@@ -232,7 +232,7 @@
   }
 
   test_doubleLiteral() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v = 1.2^;
 ''');
 
@@ -240,7 +240,7 @@
   }
 
   test_extension_methodDeclaration_body() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 extension E on int {
   void foo1() {}
 
@@ -260,7 +260,7 @@
   }
 
   test_extension_methodDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 extension E on int {
   void foo^() {
     print(0);
@@ -272,7 +272,7 @@
   }
 
   test_extension_methodDeclaration_returnType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 extension E on int {
   doub^ foo() {}
 }
@@ -282,7 +282,7 @@
   }
 
   test_extension_on_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 extension E on int^ {
   void foo() {}
 }
@@ -292,7 +292,7 @@
   }
 
   test_functionDeclaration_body() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void foo1() {}
 
 void foo2() {
@@ -310,7 +310,7 @@
   }
 
   test_functionDeclaration_body_withSemicolon() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void foo1() {}
 
 void foo2() {
@@ -328,7 +328,7 @@
   }
 
   test_functionDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void foo^() {
   print(0);
 }
@@ -338,7 +338,7 @@
   }
 
   test_functionDeclaration_returnType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 doub^ f() {}
 ''');
 
@@ -346,7 +346,7 @@
   }
 
   test_importDirective_show_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 import 'dart:async';
 import 'dart:math' show ^;
 import 'dart:io';
@@ -358,7 +358,7 @@
   }
 
   test_importDirective_uri() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 import 'dart:async';
 import 'dart:ma^'
 import 'dart:io';
@@ -370,7 +370,7 @@
   }
 
   test_integerLiteral() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v = 0^;
 ''');
 
@@ -378,7 +378,7 @@
   }
 
   test_localVariableDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void f() {
   var foo^
 }
@@ -388,7 +388,7 @@
   }
 
   test_localVariableDeclaration_type_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void f() {
   doub^ a;
 }
@@ -398,7 +398,7 @@
   }
 
   test_mixin_implements_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 mixin M implements foo^ {}
 ''');
 
@@ -406,7 +406,7 @@
   }
 
   test_mixin_methodDeclaration_body() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 class A {}
 
 mixin M {
@@ -428,7 +428,7 @@
   }
 
   test_mixin_methodDeclaration_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 mixin M {
   void foo^() {
     print(0);
@@ -440,7 +440,7 @@
   }
 
   test_mixin_methodDeclaration_returnType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 mixin M {
   doub^ foo() {}
 }
@@ -450,7 +450,7 @@
   }
 
   test_mixin_on_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 mixin M on foo^ {}
 ''');
 
@@ -464,7 +464,7 @@
     await testDriver.getFile(testFilePathPlatform);
 
     // Should call `changeFile()`, and the driver must re-read the file.
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v1 = 0;
 var v2 = v1.^;
 ''');
@@ -475,7 +475,7 @@
   }
 
   test_simpleFormalParameter_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void f(doub^) {}
 ''');
 
@@ -483,7 +483,7 @@
   }
 
   test_simpleFormalParameter_type_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void f(doub^ a) {}
 ''');
 
@@ -491,7 +491,7 @@
   }
 
   test_topLevelVariable_initializer() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v1 = 0;
 var v2 = foo^;
 var v3 = 1;
@@ -503,7 +503,7 @@
   }
 
   test_topLevelVariable_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v1 = 0;
 var v2^
 var v3 = 0;
@@ -513,7 +513,7 @@
   }
 
   test_topLevelVariable_type_namedType_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v1 = 0;
 doub^ v2 = null;
 var v3 = 1;
@@ -523,7 +523,7 @@
   }
 
   test_topLevelVariable_type_namedType_typeArgument_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 var v1 = 0;
 List<doub^>? v2 = null;
 var v3 = 1;
@@ -533,7 +533,7 @@
   }
 
   test_typedef_name_nothing() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 typedef F^
 ''');
 
@@ -541,7 +541,7 @@
   }
 
   test_typeParameter_name() async {
-    var result = _resolveTestCode(r'''
+    var result = await _resolveTestCode(r'''
 void f<T^>() {
   print(0);
 }
@@ -564,10 +564,14 @@
     return offset;
   }
 
-  ResolvedForCompletionResultImpl _resolveTestCode(String content) {
+  Future<ResolvedForCompletionResultImpl> _resolveTestCode(
+    String content,
+  ) async {
     var path = testFilePathPlatform;
     var offset = _newFileWithOffset(path, content);
+
     testDriver.changeFile(path);
+    await testDriver.applyPendingFileChanges();
 
     var performance = OperationPerformanceImpl('<root>');
     var result = testDriver.resolveForCompletion(
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 6c5e71d..69278e4 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -735,7 +735,6 @@
 }
 ''';
     newFile(other, content: otherCode);
-    driver.addFile(other);
 
     await resolveTestCode('''
 class A {
diff --git a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
index f20aedb..249ce8d 100644
--- a/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/getter_not_subtype_setter_types_test.dart
@@ -326,6 +326,17 @@
     ]);
   }
 
+  test_enum_static_generatedGetter_thisSetter_index() async {
+    await assertErrorsInCode('''
+enum E {
+  v;
+  static set values(int _) {}
+}
+''', [
+      error(CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES, 5, 1),
+    ]);
+  }
+
   test_extension_instance() async {
     await assertErrorsInCode('''
 extension E on Object {
diff --git a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
index ceca9ef..7fb0b41 100644
--- a/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
@@ -53,7 +53,10 @@
 
     // Remove the overlay in the same way as AnalysisServer.
     deleteFile(filePath);
-    driverFor(testFilePath).removeFile(filePath);
+
+    var analysisDriver = driverFor(testFilePath);
+    analysisDriver.removeFile(filePath);
+    await analysisDriver.applyPendingFileChanges();
 
     await resolveTestFile();
     assertErrorsInResult([
diff --git a/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart b/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
index 3deb2aa..bef10e6 100644
--- a/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/values_declaration_in_enum_test.dart
@@ -115,13 +115,11 @@
   }
 
   test_setter_static() async {
-    await assertErrorsInCode(r'''
+    await assertNoErrorsInCode(r'''
 enum E {
   v;
   static set values(_) {}
 }
-''', [
-      error(CompileTimeErrorCode.VALUES_DECLARATION_IN_ENUM, 27, 6),
-    ]);
+''');
   }
 }
diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
index 317d7c5..18026d6 100644
--- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
+++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
@@ -1333,9 +1333,8 @@
     return '{$entriesStr}';
   }
 
-  /// TODO(scheglov) Make [type] non-nullable?
-  String? _typeStr(DartType? type) {
-    return type?.getDisplayString(withNullability: true);
+  String? _typeStr(DartType type) {
+    return type.getDisplayString(withNullability: true);
   }
 
   void _withIndent(void Function() f) {
@@ -1535,8 +1534,12 @@
 
   void _writeType(String name, DartType? type) {
     if (_withResolution) {
-      var typeStr = _typeStr(type);
-      _writelnWithIndent('$name: $typeStr');
+      if (type != null) {
+        var typeStr = _typeStr(type);
+        _writelnWithIndent('$name: $typeStr');
+      } else {
+        _writelnWithIndent('$name: null');
+      }
     }
   }
 
diff --git a/pkg/analyzer_plugin/doc/api.html b/pkg/analyzer_plugin/doc/api.html
index 0c0d3d7..1df2f8e 100644
--- a/pkg/analyzer_plugin/doc/api.html
+++ b/pkg/analyzer_plugin/doc/api.html
@@ -1507,6 +1507,13 @@
       the user edit the variable name after the operation, all occurrences of
       the name could be edited simultaneously.
     </p>
+    <p>
+      Edit groups may have a length of 0 and function as tabstops where there
+      is no default text, for example, an edit that inserts an <tt>if</tt>
+      statement might provide an empty group between parens where a condition
+      should be typed. For this reason, it's also valid for a group to contain
+      only a single position that is not linked to others.
+    </p>
     
   <dl><dt class="field"><b>positions: List&lt;<a href="#type_Position">Position</a>&gt;</b></dt><dd>
         
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index be0b7fc..646c18a 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -359,6 +359,15 @@
   SourceEdit get sourceEdit => SourceEdit(offset, length, _buffer.toString());
 
   @override
+  void addEmptyLinkedEdit(String groupName) {
+    var start = offset + _buffer.length;
+    var position = Position(fileEditBuilder.fileEdit.file, start);
+    fileEditBuilder.changeBuilder._lockedPositions.add(position);
+    var group = fileEditBuilder.changeBuilder.getLinkedEditGroup(groupName);
+    group.addPosition(position, 0);
+  }
+
+  @override
   void addLinkedEdit(String groupName,
       void Function(LinkedEditBuilder builder) buildLinkedEdit) {
     var builder = createLinkedEditBuilder();
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
index 804c3c4c..b49c38a 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
@@ -75,6 +75,14 @@
 ///
 /// Clients may not extend, implement or mix-in this class.
 abstract class EditBuilder {
+  /// Add an empty region that is part of the linked edit group with the given
+  /// [groupName].
+  ///
+  /// Empty linked edits are locations where the user expects to be able to tab
+  /// to but there is no default (or suggested) text. For example inside the
+  /// parens of an `if` statement.
+  void addEmptyLinkedEdit(String groupName);
+
   /// Add a region of text that is part of the linked edit group with the given
   /// [groupName]. The [buildLinkedEdit] function is used to write the content
   /// of the region of text and to add suggestions for other possible values for
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index 3f13d2a..56ec6a3 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -208,9 +208,11 @@
           var parentPath =
               _computeParentWithExamplesAPI(node, resourceProvider);
           if (parentPath != null) {
+            var start = token.offset + startIndex;
+            var end = token.offset + endIndex;
             computer.collector.addRegion(
-                token.offset + startIndex,
-                token.offset + endIndex,
+                start,
+                end - start,
                 protocol.ElementKind.LIBRARY,
                 protocol.Location(
                     resourceProvider.pathContext.join(parentPath, pathSnippet),
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index ae38afa..7ff8493 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -38,7 +38,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', isRequiredNamed: true);
@@ -69,7 +69,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', isCovariant: true, isRequiredNamed: true);
@@ -88,7 +88,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', isRequiredNamed: true);
@@ -110,7 +110,7 @@
 ''';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', isRequiredNamed: true);
@@ -137,7 +137,7 @@
     addSource(path, 'class A {}');
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', interfaces: [typeA]);
@@ -152,7 +152,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, '');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', isAbstract: true);
@@ -166,7 +166,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, '');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', membersWriter: () {
@@ -183,7 +183,7 @@
     addSource(path, 'class A {}');
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', mixins: [typeA]);
@@ -200,7 +200,7 @@
     DartType typeA = await _getType(path, 'A');
     DartType typeB = await _getType(path, 'B');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', mixins: [typeB], superclass: typeA);
@@ -215,7 +215,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, '');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C', nameGroupName: 'name');
@@ -236,7 +236,7 @@
     addSource(path, 'class B {}');
     DartType typeB = await _getType(path, 'B');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeClassDeclaration('C',
@@ -257,7 +257,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, 'class C {}');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(9, (builder) {
         builder.writeConstructorDeclaration('A', bodyWriter: () {
@@ -278,7 +278,7 @@
 }
 ''');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(42, (builder) {
         builder.writeConstructorDeclaration('A', fieldNames: ['a', 'bb']);
@@ -292,7 +292,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, 'class C {}');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(9, (builder) {
         builder.writeConstructorDeclaration('A', initializerWriter: () {
@@ -308,7 +308,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, 'class C {}');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(9, (builder) {
         builder.writeConstructorDeclaration('A', parameterWriter: () {
@@ -320,12 +320,41 @@
     expect(edit.replacement, equalsIgnoringWhitespace('A(int a, {this.b});'));
   }
 
+  Future<void> test_writeEmptyLinkedEditGroup() async {
+    var path = convertPath('/home/test/lib/test.dart');
+    addSource(path, '');
+
+    var builder = await newBuilder();
+    await builder.addDartFileEdit(path, (builder) {
+      builder.addInsertion(0, (builder) {
+        builder.write('if (');
+        builder.addEmptyLinkedEdit('condition');
+        builder.writeln(') {');
+        builder.write('  ');
+        builder.addEmptyLinkedEdit('body');
+        builder.writeln();
+        builder.writeln('}');
+      });
+    });
+
+    var linkedEditGroups = builder.sourceChange.linkedEditGroups;
+    expect(linkedEditGroups, hasLength(2));
+    // inside parens at `if ()`
+    expect(linkedEditGroups[0].length, 0);
+    expect(linkedEditGroups[0].positions, hasLength(1));
+    expect(linkedEditGroups[0].positions[0].offset, 4);
+    // after the indent inside the block
+    expect(linkedEditGroups[1].length, 0);
+    expect(linkedEditGroups[1].positions, hasLength(1));
+    expect(linkedEditGroups[1].positions[0].offset, 10);
+  }
+
   Future<void> test_writeFieldDeclaration_initializerWriter() async {
     var path = convertPath('/home/test/lib/test.dart');
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', initializerWriter: () {
@@ -342,7 +371,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isConst: true);
@@ -357,7 +386,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isConst: true, isFinal: true);
@@ -373,7 +402,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isConst: true, type: typeA);
@@ -388,7 +417,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isFinal: true);
@@ -404,7 +433,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isFinal: true, type: typeA);
@@ -419,7 +448,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', isStatic: true);
@@ -434,7 +463,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', nameGroupName: 'name');
@@ -458,7 +487,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeFieldDeclaration('f', type: typeA, typeGroupName: 'type');
@@ -482,7 +511,7 @@
     var content = '';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeFunctionDeclaration('fib', bodyWriter: () {
@@ -500,7 +529,7 @@
     var content = '';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeFunctionDeclaration('fib', nameGroupName: 'name');
@@ -522,7 +551,7 @@
     var content = '';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeFunctionDeclaration('fib', parameterWriter: () {
@@ -542,7 +571,7 @@
 
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeFunctionDeclaration('fib',
@@ -564,7 +593,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeGetterDeclaration('g', bodyWriter: () {
@@ -581,7 +610,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeGetterDeclaration('g', isStatic: true);
@@ -596,7 +625,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeGetterDeclaration('g', nameGroupName: 'name');
@@ -620,7 +649,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeGetterDeclaration('g',
@@ -646,7 +675,7 @@
 import 'foo.dart';
 ''');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeImportedName([
@@ -665,7 +694,7 @@
 import 'bar.dart';
 ''');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeImportedName([
@@ -683,7 +712,7 @@
     var content = '';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeImportedName([
@@ -709,7 +738,7 @@
     var content = 'test';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addReplacement(SourceRange(0, 4), (builder) {
         builder.writeImportedName([
@@ -739,7 +768,7 @@
     addSource(path, content);
     await resolveFile(path);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration('foo', initializerWriter: () {
@@ -760,7 +789,7 @@
     addSource(path, content);
     await resolveFile(path);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration('foo', nameGroupName: 'name');
@@ -786,7 +815,7 @@
     addSource(path, content);
     await resolveFile(path);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration('foo', isConst: true);
@@ -806,7 +835,7 @@
     addSource(path, content);
     await resolveFile(path);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration('foo', isFinal: true);
@@ -828,7 +857,7 @@
 
     var A = unit.declarations[1] as ClassDeclaration;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration(
@@ -859,7 +888,7 @@
 
     var A = unit.declarations[1] as ClassDeclaration;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration(
@@ -895,7 +924,7 @@
 
     var A = unit.declarations[1] as ClassDeclaration;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(11, (builder) {
         builder.writeLocalVariableDeclaration(
@@ -924,7 +953,7 @@
     addSource(path, 'class A {}');
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeMixinDeclaration('M', interfaces: [typeA]);
@@ -942,7 +971,7 @@
     DartType typeA = await _getType(path, 'A');
     DartType typeB = await _getType(path, 'B');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeMixinDeclaration('M',
@@ -958,7 +987,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, '');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeMixinDeclaration('M', membersWriter: () {
@@ -974,7 +1003,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, '');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeMixinDeclaration('M', nameGroupName: 'name');
@@ -995,7 +1024,7 @@
     addSource(path, 'class A {}');
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeMixinDeclaration('M', superclassConstraints: [typeA]);
@@ -1010,7 +1039,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a');
@@ -1025,7 +1054,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', isCovariant: true);
@@ -1041,7 +1070,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameter('a', type: typeA);
@@ -1068,7 +1097,7 @@
     var invocation = statement.expression as MethodInvocation;
     var argument = invocation.argumentList.arguments[0];
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(2, (builder) {
         builder.writeParameterMatchingArgument(argument, 0, <String>{});
@@ -1088,7 +1117,7 @@
     var parameters = f.functionExpression.parameters;
     var elements = parameters?.parameters.map((p) => p.declaredElement!);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameters(elements!);
@@ -1108,7 +1137,7 @@
     var parameters = f.functionExpression.parameters;
     var elements = parameters?.parameters.map((p) => p.declaredElement!);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameters(elements!);
@@ -1128,7 +1157,7 @@
     var parameters = f.functionExpression.parameters;
     var elements = parameters?.parameters.map((p) => p.declaredElement!);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameters(elements!);
@@ -1147,7 +1176,7 @@
     var parameters = f.functionExpression.parameters;
     var elements = parameters?.parameters.map((p) => p.declaredElement!);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParameters(elements!, requiredTypes: true);
@@ -1170,7 +1199,7 @@
     var statement = body.block.statements[0] as ExpressionStatement;
     var invocation = statement.expression as MethodInvocation;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParametersMatchingArguments(invocation.argumentList);
@@ -1196,7 +1225,7 @@
     var statement = body.block.statements[0] as ExpressionStatement;
     var invocation = statement.expression as MethodInvocation;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeParametersMatchingArguments(invocation.argumentList);
@@ -1223,7 +1252,7 @@
     var aElement = await _getClassElement(aPath, 'A');
     var fooElement = aElement.methods[0];
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeReference(fooElement);
@@ -1245,7 +1274,7 @@
 
     var aElement = await _getTopLevelAccessorElement(aPath, 'a');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeReference(aElement);
@@ -1267,7 +1296,7 @@
 
     var aElement = await _getTopLevelAccessorElement(aPath, 'a');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeReference(aElement);
@@ -1287,7 +1316,7 @@
 
     var aElement = await _getTopLevelAccessorElement(aPath, 'a');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeReference(aElement);
@@ -1309,7 +1338,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeSetterDeclaration('s', bodyWriter: () {
@@ -1326,7 +1355,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeSetterDeclaration('s', isStatic: true);
@@ -1341,7 +1370,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeSetterDeclaration('s', nameGroupName: 'name');
@@ -1365,7 +1394,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeSetterDeclaration('s',
@@ -1390,7 +1419,7 @@
     addSource(path, content);
     var unit = (await resolveFile(path)).unit;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         var typeProvider = unit.declaredElement!.library.typeProvider;
@@ -1432,7 +1461,7 @@
     var typeA = await _getType(path, 'A');
     var typeBofA = await _getType(path, 'B', typeArguments: [typeA]);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(typeBofA);
@@ -1448,7 +1477,7 @@
     addSource(path, content);
     DartType typeC = await _getType(path, 'C');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(typeC, groupName: 'type');
@@ -1469,7 +1498,7 @@
     addSource(path, content);
     DartType typeC = await _getType(path, 'C');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(typeC,
@@ -1504,7 +1533,7 @@
       nullabilitySuffix: NullabilitySuffix.star,
     );
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length, (builder) {
         // "T" cannot be written, because we are outside of "A".
@@ -1528,7 +1557,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(null);
@@ -1563,7 +1592,7 @@
       return '_prefix${nextPrefixIndex++}';
     }
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(0, (builder) {
         builder.writeType(a1.instantiate(
@@ -1610,7 +1639,7 @@
     addSource(path, content);
     var unit = (await resolveFile(path)).unit;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         var typeProvider = unit.declaredElement!.library.typeProvider;
@@ -1627,7 +1656,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(typeA, required: true);
@@ -1642,7 +1671,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(null, required: true);
@@ -1658,7 +1687,7 @@
     addSource(path, content);
     DartType typeA = await _getType(path, 'A');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(typeA);
@@ -1682,7 +1711,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeTypes([]);
@@ -1699,7 +1728,7 @@
     DartType typeA = await _getType(path, 'A');
     DartType typeB = await _getType(path, 'B');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeTypes([typeA, typeB]);
@@ -1714,7 +1743,7 @@
     var content = 'class A {}';
     addSource(path, content);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeTypes(null);
@@ -1731,7 +1760,7 @@
     DartType typeA = await _getType(path, 'A');
     DartType typeB = await _getType(path, 'B');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeTypes([typeA, typeB], prefix: 'implements ');
@@ -1748,7 +1777,7 @@
 
     var f = await _getTopLevelAccessorElement(path, 'v');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 1, (builder) {
         builder.writeType(f.returnType);
@@ -1793,7 +1822,7 @@
     var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
     var body = findNode.functionBody('{}');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.convertFunctionFromSyncToAsync(body, resolvedUnit.typeProvider);
     });
@@ -1810,7 +1839,7 @@
     var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
     var body = findNode.functionBody('{}');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.convertFunctionFromSyncToAsync(body, resolvedUnit.typeProvider);
     });
@@ -1839,7 +1868,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     newFile(path, content: initialCode);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(34, (builder) {
         builder.writeln('  3 +  4;');
@@ -1891,7 +1920,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     newFile(path, content: initialCode);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.format(SourceRange(37, 39));
     });
@@ -1920,7 +1949,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     newFile(path, content: initialCode);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     var future = Future.wait([
       builder.addDartFileEdit(path, (builder) {
         builder.addSimpleInsertion(0, '11');
@@ -1938,7 +1967,7 @@
     var path = convertPath('/home/test/lib/test.dart');
     newFile(path, content: initialCode);
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addSimpleInsertion(0, '11');
     });
@@ -1959,7 +1988,7 @@
     var findNode = FindNode(resolvedUnit.content, resolvedUnit.unit);
     var type = findNode.typeAnnotation('String');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.replaceTypeWithFuture(type, resolvedUnit.typeProvider);
     });
@@ -2474,7 +2503,7 @@
   }) async {
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, initialCode);
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       for (var i = 0; i < uriList.length; ++i) {
         var uri = Uri.parse(uriList[i]);
@@ -3049,7 +3078,7 @@
 
     var displayBuffer = displayText != null ? StringBuffer() : null;
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       builder.addInsertion(content.length - 2, (builder) {
         builder.writeOverride(
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
index 9bff4bb..10af9f1 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/dart_change_builder_mixin.dart
@@ -28,5 +28,7 @@
   }
 
   /// Return a newly created Dart change builder.
-  ChangeBuilder newBuilder() => ChangeBuilder(session: session);
+  Future<ChangeBuilder> newBuilder() async {
+    return ChangeBuilder(session: await session);
+  }
 }
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
index cd2eefa..80fcd16 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/dart/import_library_element_test.dart
@@ -469,12 +469,12 @@
     newFile(path, content: initialCode);
 
     var requestedResult =
-        await session.getLibraryByUri(uriStr) as LibraryElementResult;
+        await (await session).getLibraryByUri(uriStr) as LibraryElementResult;
     var requestedLibrary = requestedResult.element;
     var requestedElement = requestedLibrary.exportNamespace.get(name);
     expect(requestedElement, isNotNull, reason: '`$name` in $uriStr');
 
-    var builder = newBuilder();
+    var builder = await newBuilder();
     await builder.addDartFileEdit(path, (builder) {
       var uri = Uri.parse(uriStr);
       var result = builder.importLibraryElement(uri);
diff --git a/pkg/analyzer_plugin/test/support/abstract_context.dart b/pkg/analyzer_plugin/test/support/abstract_context.dart
index 426644f..9a8066b 100644
--- a/pkg/analyzer_plugin/test/support/abstract_context.dart
+++ b/pkg/analyzer_plugin/test/support/abstract_context.dart
@@ -46,7 +46,7 @@
 
   Folder get sdkRoot => newFolder('/sdk');
 
-  AnalysisSession get session => contextFor(testPackageRootPath).currentSession;
+  Future<AnalysisSession> get session async => sessionFor(testPackageRootPath);
 
   /// The file system-specific `analysis_options.yaml` path.
   String get testPackageAnalysisOptionsPath =>
@@ -98,10 +98,16 @@
   }
 
   Future<ResolvedUnitResult> resolveFile(String path) async {
-    var session = contextFor(path).currentSession;
+    var session = await sessionFor(path);
     return await session.getResolvedUnit(path) as ResolvedUnitResult;
   }
 
+  Future<AnalysisSession> sessionFor(String path) async {
+    var analysisContext = contextFor(path);
+    await analysisContext.applyPendingFileChanges();
+    return analysisContext.currentSession;
+  }
+
   void setUp() {
     createMockSdk(
       resourceProvider: resourceProvider,
diff --git a/pkg/analyzer_plugin/tool/spec/common_types_spec.html b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
index 9a3d257..a12e921 100644
--- a/pkg/analyzer_plugin/tool/spec/common_types_spec.html
+++ b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
@@ -1008,6 +1008,13 @@
       the user edit the variable name after the operation, all occurrences of
       the name could be edited simultaneously.
     </p>
+    <p>
+      Edit groups may have a length of 0 and function as tabstops where there
+      is no default text, for example, an edit that inserts an <tt>if</tt>
+      statement might provide an empty group between parens where a condition
+      should be typed. For this reason, it's also valid for a group to contain
+      only a single position that is not linked to others.
+    </p>
     <object>
       <field name="positions">
         <list>
diff --git a/pkg/compiler/lib/compiler.dart b/pkg/compiler/lib/compiler.dart
index b9ade95..a57731b 100644
--- a/pkg/compiler/lib/compiler.dart
+++ b/pkg/compiler/lib/compiler.dart
@@ -247,7 +247,6 @@
   return compiler.run().then((bool success) {
     return new CompilationResult(compiler,
         isSuccess: success,
-        kernelInitializedCompilerState:
-            compiler.kernelLoader.initializedCompilerState);
+        kernelInitializedCompilerState: compiler.initializedCompilerState);
   });
 }
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index d1ddb50..918c9fa 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -7,6 +7,7 @@
 import 'dart:async' show Future;
 import 'dart:convert' show jsonEncode;
 
+import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
 import 'package:kernel/ast.dart' as ir;
 
 import '../compiler.dart' as api;
@@ -46,9 +47,9 @@
 import 'kernel/front_end_adapter.dart' show CompilerFileSystem;
 import 'kernel/kernel_strategy.dart';
 import 'kernel/kernel_world.dart';
-import 'kernel/loader.dart' show KernelLoaderTask, KernelResult;
 import 'null_compiler_output.dart' show NullCompilerOutput;
 import 'options.dart' show CompilerOptions;
+import 'phase/load_kernel.dart' as load_kernel;
 import 'serialization/task.dart';
 import 'serialization/serialization.dart';
 import 'serialization/strategies.dart';
@@ -105,7 +106,9 @@
   Entity get currentElement => _reporter.currentElement;
 
   List<CompilerTask> tasks;
-  KernelLoaderTask kernelLoader;
+  GenericTask loadKernelTask;
+  fe.InitializedCompilerState initializedCompilerState;
+  bool forceSerializationForTesting = false;
   GlobalTypeInferenceTask globalInference;
   CodegenWorldBuilder _codegenWorldBuilder;
 
@@ -179,7 +182,7 @@
       // [enqueueTask] is created earlier because it contains the resolution
       // world objects needed by other tasks.
       enqueueTask = GenericTask('Enqueue', measurer),
-      kernelLoader = KernelLoaderTask(options, provider, reporter, measurer),
+      loadKernelTask = GenericTask('kernel loader', measurer),
       kernelFrontEndTask,
       globalInference = GlobalTypeInferenceTask(this),
       deferredLoadTask = frontendStrategy.createDeferredLoadTask(this),
@@ -191,6 +194,8 @@
       userHandlerTask = GenericTask('Diagnostic handler', measurer),
       userProviderTask = GenericTask('Input provider', measurer)
     ];
+
+    initializedCompilerState = options.kernelInitializedCompilerState;
   }
 
   /// Creates the backend strategy.
@@ -259,15 +264,15 @@
   /// Dumps a list of unused [ir.Library]'s in the [KernelResult]. This *must*
   /// be called before [setMainAndTrimComponent], because that method will
   /// discard the unused [ir.Library]s.
-  void dumpUnusedLibraries(KernelResult result) {
-    var usedUris = result.libraries.toSet();
+  void dumpUnusedLibraries(ir.Component component, List<Uri> libraries) {
+    var usedUris = libraries.toSet();
     bool isUnused(ir.Library l) => !usedUris.contains(l.importUri);
     String libraryString(ir.Library library) {
       return '${library.importUri}(${library.fileUri})';
     }
 
     var unusedLibraries =
-        result.component.libraries.where(isUnused).map(libraryString).toList();
+        component.libraries.where(isUnused).map(libraryString).toList();
     unusedLibraries.sort();
     var jsonLibraries = jsonEncode(unusedLibraries);
     outputProvider.createOutputSink(options.outputUri.pathSegments.last,
@@ -277,10 +282,31 @@
     reporter.reportInfo(
         reporter.createMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
       'text': "${unusedLibraries.length} unused libraries out of "
-          "${result.component.libraries.length}. Dumping to JSON."
+          "${component.libraries.length}. Dumping to JSON."
     }));
   }
 
+  /// Trims a component down to only the provided library uris.
+  ir.Component trimComponent(
+      ir.Component component, List<Uri> librariesToInclude) {
+    var irLibraryMap = <Uri, ir.Library>{};
+    var irLibraries = <ir.Library>[];
+    for (var library in component.libraries) {
+      irLibraryMap[library.importUri] = library;
+    }
+    for (var library in librariesToInclude) {
+      irLibraries.add(irLibraryMap[library]);
+    }
+    var mainMethod = component.mainMethodName;
+    var componentMode = component.mode;
+    final trimmedComponent = ir.Component(
+        libraries: irLibraries,
+        uriToSource: component.uriToSource,
+        nameRoot: component.root);
+    trimmedComponent.setMainMethodAndMode(mainMethod, true, componentMode);
+    return trimmedComponent;
+  }
+
   Future runInternal() async {
     clearState();
     var compilationTarget = options.compilationTarget;
@@ -331,25 +357,26 @@
       await generateJavaScriptCode(globalTypeInferenceResults,
           indices: closedWorldAndIndices.indices);
     } else {
-      KernelResult result = await kernelLoader.load();
+      final input = load_kernel.Input(options, provider, reporter,
+          initializedCompilerState, forceSerializationForTesting);
+      load_kernel.Output output =
+          await loadKernelTask.measure(() async => load_kernel.run(input));
+      if (output == null || compilationFailed) return;
       reporter.log("Kernel load complete");
-      if (result == null) return;
-      if (compilationFailed) {
-        return;
-      }
       if (retainDataForTesting) {
-        componentForTesting = result.component;
+        componentForTesting = output.component;
       }
 
-      frontendStrategy.registerLoadedLibraries(result);
+      ir.Component component = output.component;
+      List<Uri> libraries = output.libraries;
+      frontendStrategy.registerLoadedLibraries(component, libraries);
 
       if (options.modularMode) {
-        await runModularAnalysis(result);
+        await runModularAnalysis(component, output.moduleLibraries);
       } else {
         List<ModuleData> data;
         if (options.hasModularAnalysisInputs) {
-          data =
-              await serializationTask.deserializeModuleData(result.component);
+          data = await serializationTask.deserializeModuleData(component);
         }
         frontendStrategy.registerModuleData(data);
 
@@ -360,16 +387,16 @@
         // 'trimmed' elements.
         if (options.fromDill) {
           if (options.dumpUnusedLibraries) {
-            dumpUnusedLibraries(result);
+            dumpUnusedLibraries(component, libraries);
           }
           if (options.entryUri != null) {
-            result.trimComponent();
+            component = trimComponent(component, libraries);
           }
         }
         if (options.cfeOnly) {
-          await serializationTask.serializeComponent(result.component);
+          await serializationTask.serializeComponent(component);
         } else {
-          await compileFromKernel(result.rootLibraryUri, result.libraries);
+          await compileFromKernel(output.rootLibraryUri, libraries);
         }
       }
     }
@@ -471,18 +498,18 @@
     return closedWorld;
   }
 
-  void runModularAnalysis(KernelResult result) {
+  void runModularAnalysis(
+      ir.Component component, Iterable<Uri> moduleLibraries) {
     _userCodeLocations
-        .addAll(result.moduleLibraries.map((module) => CodeLocation(module)));
+        .addAll(moduleLibraries.map((module) => CodeLocation(module)));
     selfTask.measureSubtask('runModularAnalysis', () {
-      var included = result.moduleLibraries.toSet();
+      var included = moduleLibraries.toSet();
       var elementMap = frontendStrategy.elementMap;
-      var moduleData = computeModuleData(result.component, included, options,
-          reporter, environment, elementMap);
+      var moduleData = computeModuleData(
+          component, included, options, reporter, environment, elementMap);
       if (compilationFailed) return;
-      serializationTask.testModuleSerialization(moduleData, result.component);
-      serializationTask.serializeModuleData(
-          moduleData, result.component, included);
+      serializationTask.testModuleSerialization(moduleData, component);
+      serializationTask.serializeModuleData(moduleData, component, included);
     });
   }
 
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index f1a2641..1a6b1bb 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -44,7 +44,7 @@
 import '../universe/world_impact.dart';
 import '../util/enumset.dart';
 import 'element_map.dart';
-import 'loader.dart';
+import 'element_map_impl.dart';
 import 'native_basic_data.dart';
 
 /// Front end strategy that loads '.dill' files and builds a resolved element
@@ -229,13 +229,13 @@
   }
 
   /// Registers a set of loaded libraries with this strategy.
-  void registerLoadedLibraries(KernelResult kernelResult) {
-    _elementMap.addComponent(kernelResult.component);
+  void registerLoadedLibraries(ir.Component component, List<Uri> libraries) {
+    _elementMap.addComponent(component);
     _irAnnotationData = processAnnotations(
-        ModularCore(kernelResult.component, _elementMap.constantEvaluator));
+        ModularCore(component, _elementMap.constantEvaluator));
     _annotationProcessor = KernelAnnotationProcessor(
         elementMap, nativeBasicDataBuilder, _irAnnotationData);
-    for (Uri uri in kernelResult.libraries) {
+    for (Uri uri in libraries) {
       LibraryEntity library = elementEnvironment.lookupLibrary(uri);
       if (maybeEnableNative(library.canonicalUri)) {
         _annotationProcessor.extractNativeAnnotations(library);
diff --git a/pkg/compiler/lib/src/kernel/loader.dart b/pkg/compiler/lib/src/kernel/loader.dart
deleted file mode 100644
index 8e7520e..0000000
--- a/pkg/compiler/lib/src/kernel/loader.dart
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright (c) 2012, 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 dart2js.library_loader;
-
-import 'dart:async';
-
-import 'package:front_end/src/fasta/kernel/utils.dart';
-import 'package:kernel/ast.dart' as ir;
-import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
-
-import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
-import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
-import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
-
-import '../../compiler.dart' as api;
-import '../commandline_options.dart' show Flags;
-import '../common/tasks.dart' show CompilerTask, Measurer;
-import '../common.dart';
-import '../options.dart';
-
-import 'front_end_adapter.dart';
-import 'dart2js_target.dart' show Dart2jsTarget;
-
-/// A task that produces the kernel IR representation of the application.
-///
-/// It supports loading both .dart source files or pre-compiled .dill files.
-/// When given .dart source files, it invokes the common front-end (CFE)
-/// to produce the corresponding kernel IR representation.
-class KernelLoaderTask extends CompilerTask {
-  final DiagnosticReporter _reporter;
-
-  final api.CompilerInput _compilerInput;
-
-  final CompilerOptions _options;
-
-  /// Shared state between compilations.
-  fe.InitializedCompilerState initializedCompilerState;
-
-  // TODO(johnniwinther): Remove this when #34942 is fixed.
-  /// Force in-memory serialization/deserialization of the loaded component.
-  ///
-  /// This is used for testing.
-  bool forceSerialization = false;
-
-  KernelLoaderTask(
-      this._options, this._compilerInput, this._reporter, Measurer measurer)
-      : initializedCompilerState = _options.kernelInitializedCompilerState,
-        super(measurer);
-
-  @override
-  String get name => 'kernel loader';
-
-  static Library _findEntryLibrary(Component component, Uri entryUri) {
-    var entryLibrary = component.libraries
-        .firstWhere((l) => l.fileUri == entryUri, orElse: () => null);
-    if (entryLibrary == null) {
-      throw ArgumentError('Entry uri $entryUri not found in dill.');
-    }
-    return entryLibrary;
-  }
-
-  static ir.Reference _findMainMethod(Library entryLibrary) {
-    var mainMethod = entryLibrary.procedures
-        .firstWhere((p) => p.name.text == 'main', orElse: () => null);
-
-    // In some cases, a main method is defined in another file, and then
-    // exported. In these cases, we search for the main method in
-    // [additionalExports].
-    ir.Reference mainMethodReference;
-    if (mainMethod == null) {
-      mainMethodReference = entryLibrary.additionalExports.firstWhere(
-          (p) => p.canonicalName.name == 'main',
-          orElse: () => null);
-    } else {
-      mainMethodReference = mainMethod.reference;
-    }
-    if (mainMethodReference == null) {
-      throw ArgumentError(
-          'Entry uri ${entryLibrary.fileUri} has no main method.');
-    }
-    return mainMethodReference;
-  }
-
-  /// Loads an entire Kernel [Component] from a file on disk.
-  Future<KernelResult> load() {
-    return measure(() async {
-      String targetName =
-          _options.compileForServer ? "dart2js_server" : "dart2js";
-
-      // We defer selecting the platform until we've resolved the null safety
-      // mode.
-      String getPlatformFilename() {
-        String unsoundMarker = _options.useLegacySubtyping ? "_unsound" : "";
-        return "${targetName}_platform$unsoundMarker.dill";
-      }
-
-      // If we are passed an [entryUri] and building from dill, then we lookup
-      // the [entryLibrary] in the built component.
-      Library entryLibrary;
-      var resolvedUri = _options.compilationTarget;
-      ir.Component component;
-      List<Uri> moduleLibraries = const [];
-
-      void inferNullSafetyMode(bool isSound) {
-        if (_options.nullSafetyMode == NullSafetyMode.unspecified) {
-          _options.nullSafetyMode =
-              isSound ? NullSafetyMode.sound : NullSafetyMode.unsound;
-        }
-      }
-
-      void validateNullSafetyMode() {
-        assert(_options.nullSafetyMode != NullSafetyMode.unspecified);
-      }
-
-      if (_options.fromDill) {
-        component = ir.Component();
-        Future<void> read(Uri uri) async {
-          api.Input input = await _compilerInput.readFromUri(uri,
-              inputKind: api.InputKind.binary);
-          BinaryBuilder(input.data).readComponent(component);
-        }
-
-        await read(resolvedUri);
-
-        if (_options.modularMode) {
-          moduleLibraries =
-              component.libraries.map((lib) => lib.importUri).toList();
-        }
-
-        var isStrongDill =
-            component.mode == ir.NonNullableByDefaultCompiledMode.Strong;
-        var incompatibleNullSafetyMode =
-            isStrongDill ? NullSafetyMode.unsound : NullSafetyMode.sound;
-        if (_options.nullSafetyMode == incompatibleNullSafetyMode) {
-          var dillMode = isStrongDill ? 'sound' : 'unsound';
-          var option =
-              isStrongDill ? Flags.noSoundNullSafety : Flags.soundNullSafety;
-          throw ArgumentError("$resolvedUri was compiled with $dillMode null "
-              "safety and is incompatible with the '$option' option");
-        }
-        inferNullSafetyMode(isStrongDill);
-        validateNullSafetyMode();
-
-        // Modular compiles do not include the platform on the input dill
-        // either.
-        if (_options.platformBinaries != null) {
-          var platformUri =
-              _options.platformBinaries.resolve(getPlatformFilename());
-          // Modular analysis can be run on the sdk by providing directly the
-          // path to the platform.dill file. In that case, we do not load the
-          // platform file implicitly.
-          // TODO(joshualitt): Change how we detect this case so it is less
-          // brittle.
-          if (platformUri != resolvedUri) await read(platformUri);
-        }
-
-        // Concatenate dills.
-        if (_options.dillDependencies != null) {
-          for (Uri dependency in _options.dillDependencies) {
-            await read(dependency);
-          }
-        }
-
-        if (_options.entryUri != null) {
-          entryLibrary = _findEntryLibrary(component, _options.entryUri);
-          var mainMethod = _findMainMethod(entryLibrary);
-          component.setMainMethodAndMode(mainMethod, true, component.mode);
-        }
-      } else {
-        bool verbose = false;
-        Target target =
-            Dart2jsTarget(targetName, TargetFlags(), options: _options);
-        fe.FileSystem fileSystem = CompilerFileSystem(_compilerInput);
-        fe.Verbosity verbosity = _options.verbosity;
-        fe.DiagnosticMessageHandler onDiagnostic =
-            (fe.DiagnosticMessage message) {
-          if (fe.Verbosity.shouldPrint(verbosity, message)) {
-            reportFrontEndMessage(_reporter, message);
-          }
-        };
-        fe.CompilerOptions options = fe.CompilerOptions()
-          ..target = target
-          ..librariesSpecificationUri = _options.librariesSpecificationUri
-          ..packagesFileUri = _options.packageConfig
-          ..explicitExperimentalFlags = _options.explicitExperimentalFlags
-          ..verbose = verbose
-          ..fileSystem = fileSystem
-          ..onDiagnostic = onDiagnostic
-          ..verbosity = verbosity;
-        bool isLegacy =
-            await fe.uriUsesLegacyLanguageVersion(resolvedUri, options);
-        inferNullSafetyMode(!isLegacy);
-
-        List<Uri> dependencies = [];
-        if (_options.platformBinaries != null) {
-          dependencies
-              .add(_options.platformBinaries.resolve(getPlatformFilename()));
-        }
-        if (_options.dillDependencies != null) {
-          dependencies.addAll(_options.dillDependencies);
-        }
-
-        initializedCompilerState = fe.initializeCompiler(
-            initializedCompilerState,
-            target,
-            _options.librariesSpecificationUri,
-            dependencies,
-            _options.packageConfig,
-            explicitExperimentalFlags: _options.explicitExperimentalFlags,
-            nnbdMode: _options.useLegacySubtyping
-                ? fe.NnbdMode.Weak
-                : fe.NnbdMode.Strong,
-            invocationModes: _options.cfeInvocationModes,
-            verbosity: verbosity);
-        component = await fe.compile(initializedCompilerState, verbose,
-            fileSystem, onDiagnostic, resolvedUri);
-        if (component == null) return null;
-        validateNullSafetyMode();
-      }
-
-      if (forceSerialization) {
-        // TODO(johnniwinther): Remove this when #34942 is fixed.
-        List<int> data = serializeComponent(component);
-        component = ir.Component();
-        BinaryBuilder(data).readComponent(component);
-      }
-      return _toResult(entryLibrary, component, moduleLibraries);
-    });
-  }
-
-  KernelResult _toResult(
-      Library entryLibrary, ir.Component component, List<Uri> moduleLibraries) {
-    Uri rootLibraryUri = null;
-    Iterable<ir.Library> libraries = component.libraries;
-    if (!_options.modularMode) {
-      // For non-modular builds we should always have a [mainMethod] at this
-      // point.
-      if (component.mainMethod == null) {
-        // TODO(sigmund): move this so that we use the same error template
-        // from the CFE.
-        _reporter.reportError(_reporter.createMessage(NO_LOCATION_SPANNABLE,
-            MessageKind.GENERIC, {'text': "No 'main' method found."}));
-      }
-
-      // If we are building from dill and are passed an [entryUri], then we use
-      // that to find the appropriate [entryLibrary]. Otherwise, we fallback to
-      // the [enclosingLibrary] of the [mainMethod].
-      // NOTE: Under some circumstances, the [entryLibrary] exports the
-      // [mainMethod] from another library, and thus the [enclosingLibrary] of
-      // the [mainMethod] may not be the same as the [entryLibrary].
-      var root = entryLibrary ?? component.mainMethod.enclosingLibrary;
-      rootLibraryUri = root.importUri;
-
-      // Filter unreachable libraries: [Component] was built by linking in the
-      // entire SDK libraries, not all of them are used. We include anything
-      // that is reachable from `main`. Note that all internal libraries that
-      // the compiler relies on are reachable from `dart:core`.
-      var seen = Set<Library>();
-      search(ir.Library current) {
-        if (!seen.add(current)) return;
-        for (ir.LibraryDependency dep in current.dependencies) {
-          search(dep.targetLibrary);
-        }
-      }
-
-      search(root);
-
-      // Libraries dependencies do not show implicit imports to `dart:core`.
-      var dartCore = component.libraries.firstWhere((lib) {
-        return lib.importUri.isScheme('dart') && lib.importUri.path == 'core';
-      });
-      search(dartCore);
-
-      libraries = libraries.where(seen.contains);
-    }
-    return KernelResult(component, rootLibraryUri,
-        libraries.map((lib) => lib.importUri).toList(), moduleLibraries);
-  }
-}
-
-/// Result of invoking the CFE to produce the kernel IR.
-class KernelResult {
-  ir.Component component;
-
-  /// The [Uri] of the root library containing main.
-  /// Note: rootLibraryUri will be null for some modules, for example in the
-  /// case of dependent libraries processed modularly.
-  final Uri rootLibraryUri;
-
-  /// Returns the [Uri]s of all libraries that have been loaded that are
-  /// reachable from the [rootLibraryUri].
-  ///
-  /// Note that [component] may contain some libraries that are excluded here.
-  final Iterable<Uri> libraries;
-
-  /// When running only dart2js modular analysis, returns the [Uri]s for
-  /// libraries loaded in the input module.
-  ///
-  /// This excludes other libraries reachable from them that were loaded as
-  /// dependencies. The result of [moduleLibraries] is always a subset of
-  /// [libraries].
-  final Iterable<Uri> moduleLibraries;
-
-  KernelResult(this.component, this.rootLibraryUri, this.libraries,
-      this.moduleLibraries);
-
-  void trimComponent() {
-    var irLibraryMap = <Uri, Library>{};
-    var irLibraries = <Library>[];
-    for (var library in component.libraries) {
-      irLibraryMap[library.importUri] = library;
-    }
-    for (var library in libraries) {
-      irLibraries.add(irLibraryMap[library]);
-    }
-    var mainMethod = component.mainMethodName;
-    var componentMode = component.mode;
-    component = ir.Component(
-        libraries: irLibraries,
-        uriToSource: component.uriToSource,
-        nameRoot: component.root);
-    component.setMainMethodAndMode(mainMethod, true, componentMode);
-  }
-
-  @override
-  String toString() =>
-      'root=$rootLibraryUri,libraries=$libraries,module=$moduleLibraries';
-}
diff --git a/pkg/compiler/lib/src/phase/load_kernel.dart b/pkg/compiler/lib/src/phase/load_kernel.dart
new file mode 100644
index 0000000..1db4a15
--- /dev/null
+++ b/pkg/compiler/lib/src/phase/load_kernel.dart
@@ -0,0 +1,337 @@
+// 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:front_end/src/fasta/kernel/utils.dart';
+import 'package:kernel/ast.dart' as ir;
+import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
+
+import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
+import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
+import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
+
+import '../../compiler.dart' as api;
+import '../commandline_options.dart';
+import '../common.dart';
+import '../kernel/front_end_adapter.dart';
+import '../kernel/dart2js_target.dart' show Dart2jsTarget;
+import '../options.dart';
+
+class Input {
+  final CompilerOptions options;
+  final api.CompilerInput compilerInput;
+  final DiagnosticReporter reporter;
+
+  /// Shared state between compilations. Only used when loading from source.
+  final fe.InitializedCompilerState initializedCompilerState;
+
+  // TODO(johnniwinther): Remove this when #34942 is fixed.
+  /// Force in-memory serialization/deserialization of the loaded component.
+  ///
+  /// This is used for testing.
+  final bool forceSerialization;
+
+  Input(this.options, this.compilerInput, this.reporter,
+      this.initializedCompilerState, this.forceSerialization);
+}
+
+/// Result of invoking the CFE to produce the kernel IR.
+class Output {
+  final ir.Component component;
+
+  /// The [Uri] of the root library containing main.
+  /// Note: rootLibraryUri will be null for some modules, for example in the
+  /// case of dependent libraries processed modularly.
+  final Uri rootLibraryUri;
+
+  /// Returns the [Uri]s of all libraries that have been loaded that are
+  /// reachable from the [rootLibraryUri].
+  ///
+  /// Note that [component] may contain some libraries that are excluded here.
+  final Iterable<Uri> libraries;
+
+  /// When running only dart2js modular analysis, returns the [Uri]s for
+  /// libraries loaded in the input module.
+  ///
+  /// This excludes other libraries reachable from them that were loaded as
+  /// dependencies. The result of [moduleLibraries] is always a subset of
+  /// [libraries].
+  final Iterable<Uri> moduleLibraries;
+
+  final fe.InitializedCompilerState initializedCompilerState;
+
+  Output(this.component, this.rootLibraryUri, this.libraries,
+      this.moduleLibraries, this.initializedCompilerState);
+}
+
+Library _findEntryLibrary(Component component, Uri entryUri) {
+  var entryLibrary = component.libraries
+      .firstWhere((l) => l.fileUri == entryUri, orElse: () => null);
+  if (entryLibrary == null) {
+    throw ArgumentError('Entry uri $entryUri not found in dill.');
+  }
+  return entryLibrary;
+}
+
+ir.Reference _findMainMethod(Library entryLibrary) {
+  var mainMethod = entryLibrary.procedures
+      .firstWhere((p) => p.name.text == 'main', orElse: () => null);
+
+  // In some cases, a main method is defined in another file, and then
+  // exported. In these cases, we search for the main method in
+  // [additionalExports].
+  ir.Reference mainMethodReference;
+  if (mainMethod == null) {
+    mainMethodReference = entryLibrary.additionalExports
+        .firstWhere((p) => p.canonicalName.name == 'main', orElse: () => null);
+  } else {
+    mainMethodReference = mainMethod.reference;
+  }
+  if (mainMethodReference == null) {
+    throw ArgumentError(
+        'Entry uri ${entryLibrary.fileUri} has no main method.');
+  }
+  return mainMethodReference;
+}
+
+String _getPlatformFilename(CompilerOptions options, String targetName) {
+  String unsoundMarker = options.useLegacySubtyping ? "_unsound" : "";
+  return "${targetName}_platform$unsoundMarker.dill";
+}
+
+void _inferNullSafetyMode(CompilerOptions options, bool isSound) {
+  if (options.nullSafetyMode == NullSafetyMode.unspecified) {
+    options.nullSafetyMode =
+        isSound ? NullSafetyMode.sound : NullSafetyMode.unsound;
+  }
+}
+
+void _validateNullSafetyMode(CompilerOptions options) {
+  assert(options.nullSafetyMode != NullSafetyMode.unspecified);
+}
+
+class _LoadFromKernelResult {
+  final ir.Component component;
+  final Library entryLibrary;
+  final List<Uri> moduleLibraries;
+
+  _LoadFromKernelResult(
+      this.component, this.entryLibrary, this.moduleLibraries);
+}
+
+Future<_LoadFromKernelResult> _loadFromKernel(CompilerOptions options,
+    api.CompilerInput compilerInput, String targetName) async {
+  Library entryLibrary;
+  var resolvedUri = options.compilationTarget;
+  ir.Component component = ir.Component();
+  List<Uri> moduleLibraries = [];
+
+  Future<void> read(Uri uri) async {
+    api.Input input =
+        await compilerInput.readFromUri(uri, inputKind: api.InputKind.binary);
+    BinaryBuilder(input.data).readComponent(component);
+  }
+
+  await read(resolvedUri);
+
+  if (options.modularMode) {
+    moduleLibraries = component.libraries.map((lib) => lib.importUri).toList();
+  }
+
+  var isStrongDill =
+      component.mode == ir.NonNullableByDefaultCompiledMode.Strong;
+  var incompatibleNullSafetyMode =
+      isStrongDill ? NullSafetyMode.unsound : NullSafetyMode.sound;
+  if (options.nullSafetyMode == incompatibleNullSafetyMode) {
+    var dillMode = isStrongDill ? 'sound' : 'unsound';
+    var option = isStrongDill ? Flags.noSoundNullSafety : Flags.soundNullSafety;
+    throw ArgumentError("$resolvedUri was compiled with $dillMode null "
+        "safety and is incompatible with the '$option' option");
+  }
+  _inferNullSafetyMode(options, isStrongDill);
+  _validateNullSafetyMode(options);
+
+  // Modular compiles do not include the platform on the input dill
+  // either.
+  if (options.platformBinaries != null) {
+    var platformUri = options.platformBinaries
+        .resolve(_getPlatformFilename(options, targetName));
+    // Modular analysis can be run on the sdk by providing directly the
+    // path to the platform.dill file. In that case, we do not load the
+    // platform file implicitly.
+    // TODO(joshualitt): Change how we detect this case so it is less
+    // brittle.
+    if (platformUri != resolvedUri) await read(platformUri);
+  }
+
+  // Concatenate dills.
+  if (options.dillDependencies != null) {
+    for (Uri dependency in options.dillDependencies) {
+      await read(dependency);
+    }
+  }
+
+  if (options.entryUri != null) {
+    entryLibrary = _findEntryLibrary(component, options.entryUri);
+    var mainMethod = _findMainMethod(entryLibrary);
+    component.setMainMethodAndMode(mainMethod, true, component.mode);
+  }
+  return _LoadFromKernelResult(component, entryLibrary, moduleLibraries);
+}
+
+class _LoadFromSourceResult {
+  final ir.Component component;
+  final fe.InitializedCompilerState initializedCompilerState;
+
+  _LoadFromSourceResult(this.component, this.initializedCompilerState);
+}
+
+Future<_LoadFromSourceResult> _loadFromSource(
+    CompilerOptions options,
+    api.CompilerInput compilerInput,
+    DiagnosticReporter reporter,
+    fe.InitializedCompilerState initializedCompilerState,
+    String targetName) async {
+  bool verbose = false;
+  Target target = Dart2jsTarget(targetName, TargetFlags(), options: options);
+  fe.FileSystem fileSystem = CompilerFileSystem(compilerInput);
+  fe.Verbosity verbosity = options.verbosity;
+  fe.DiagnosticMessageHandler onDiagnostic = (fe.DiagnosticMessage message) {
+    if (fe.Verbosity.shouldPrint(verbosity, message)) {
+      reportFrontEndMessage(reporter, message);
+    }
+  };
+  fe.CompilerOptions feOptions = fe.CompilerOptions()
+    ..target = target
+    ..librariesSpecificationUri = options.librariesSpecificationUri
+    ..packagesFileUri = options.packageConfig
+    ..explicitExperimentalFlags = options.explicitExperimentalFlags
+    ..verbose = verbose
+    ..fileSystem = fileSystem
+    ..onDiagnostic = onDiagnostic
+    ..verbosity = verbosity;
+  Uri resolvedUri = options.compilationTarget;
+  bool isLegacy = await fe.uriUsesLegacyLanguageVersion(resolvedUri, feOptions);
+  _inferNullSafetyMode(options, !isLegacy);
+
+  List<Uri> dependencies = [];
+  if (options.platformBinaries != null) {
+    dependencies.add(options.platformBinaries
+        .resolve(_getPlatformFilename(options, targetName)));
+  }
+  if (options.dillDependencies != null) {
+    dependencies.addAll(options.dillDependencies);
+  }
+
+  initializedCompilerState = fe.initializeCompiler(
+      initializedCompilerState,
+      target,
+      options.librariesSpecificationUri,
+      dependencies,
+      options.packageConfig,
+      explicitExperimentalFlags: options.explicitExperimentalFlags,
+      nnbdMode:
+          options.useLegacySubtyping ? fe.NnbdMode.Weak : fe.NnbdMode.Strong,
+      invocationModes: options.cfeInvocationModes,
+      verbosity: verbosity);
+  ir.Component component = await fe.compile(
+      initializedCompilerState, verbose, fileSystem, onDiagnostic, resolvedUri);
+  _validateNullSafetyMode(options);
+  return _LoadFromSourceResult(component, initializedCompilerState);
+}
+
+Output _createOutput(
+    CompilerOptions options,
+    DiagnosticReporter reporter,
+    Library entryLibrary,
+    ir.Component component,
+    List<Uri> moduleLibraries,
+    fe.InitializedCompilerState initializedCompilerState) {
+  Uri rootLibraryUri = null;
+  Iterable<ir.Library> libraries = component.libraries;
+  if (!options.modularMode) {
+    // For non-modular builds we should always have a [mainMethod] at this
+    // point.
+    if (component.mainMethod == null) {
+      // TODO(sigmund): move this so that we use the same error template
+      // from the CFE.
+      reporter.reportError(reporter.createMessage(NO_LOCATION_SPANNABLE,
+          MessageKind.GENERIC, {'text': "No 'main' method found."}));
+    }
+
+    // If we are building from dill and are passed an [entryUri], then we use
+    // that to find the appropriate [entryLibrary]. Otherwise, we fallback to
+    // the [enclosingLibrary] of the [mainMethod].
+    // NOTE: Under some circumstances, the [entryLibrary] exports the
+    // [mainMethod] from another library, and thus the [enclosingLibrary] of
+    // the [mainMethod] may not be the same as the [entryLibrary].
+    var root = entryLibrary ?? component.mainMethod.enclosingLibrary;
+    rootLibraryUri = root.importUri;
+
+    // Filter unreachable libraries: [Component] was built by linking in the
+    // entire SDK libraries, not all of them are used. We include anything
+    // that is reachable from `main`. Note that all internal libraries that
+    // the compiler relies on are reachable from `dart:core`.
+    var seen = Set<Library>();
+    search(ir.Library current) {
+      if (!seen.add(current)) return;
+      for (ir.LibraryDependency dep in current.dependencies) {
+        search(dep.targetLibrary);
+      }
+    }
+
+    search(root);
+
+    // Libraries dependencies do not show implicit imports to `dart:core`.
+    var dartCore = component.libraries.firstWhere((lib) {
+      return lib.importUri.isScheme('dart') && lib.importUri.path == 'core';
+    });
+    search(dartCore);
+
+    libraries = libraries.where(seen.contains);
+  }
+  return Output(
+      component,
+      rootLibraryUri,
+      libraries.map((lib) => lib.importUri).toList(),
+      moduleLibraries,
+      initializedCompilerState);
+}
+
+/// Loads an entire Kernel [Component] from a file on disk.
+Future<Output> run(Input input) async {
+  CompilerOptions options = input.options;
+  api.CompilerInput compilerInput = input.compilerInput;
+  DiagnosticReporter reporter = input.reporter;
+
+  String targetName = options.compileForServer ? "dart2js_server" : "dart2js";
+
+  Library entryLibrary;
+  ir.Component component;
+  List<Uri> moduleLibraries = const [];
+  fe.InitializedCompilerState initializedCompilerState =
+      input.initializedCompilerState;
+  if (options.fromDill) {
+    _LoadFromKernelResult result =
+        await _loadFromKernel(options, compilerInput, targetName);
+    component = result.component;
+    entryLibrary = result.entryLibrary;
+    moduleLibraries = result.moduleLibraries;
+  } else {
+    _LoadFromSourceResult result = await _loadFromSource(options, compilerInput,
+        reporter, input.initializedCompilerState, targetName);
+    component = result.component;
+    initializedCompilerState = result.initializedCompilerState;
+  }
+  if (component == null) return null;
+  if (input.forceSerialization) {
+    // TODO(johnniwinther): Remove this when #34942 is fixed.
+    List<int> data = serializeComponent(component);
+    component = ir.Component();
+    BinaryBuilder(data).readComponent(component);
+  }
+  return _createOutput(options, reporter, entryLibrary, component,
+      moduleLibraries, initializedCompilerState);
+}
diff --git a/pkg/compiler/test/analyses/analysis_helper.dart b/pkg/compiler/test/analyses/analysis_helper.dart
index 763818b..fecbbef 100644
--- a/pkg/compiler/test/analyses/analysis_helper.dart
+++ b/pkg/compiler/test/analyses/analysis_helper.dart
@@ -15,7 +15,7 @@
 import 'package:compiler/src/ir/scope.dart';
 import 'package:compiler/src/ir/static_type.dart';
 import 'package:compiler/src/ir/util.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
 import 'package:expect/expect.dart';
 import 'package:front_end/src/api_prototype/constant_evaluator.dart' as ir;
 import 'package:front_end/src/api_unstable/dart2js.dart'
@@ -61,8 +61,15 @@
         packageConfig: packageConfig,
         entryPoint: entryPoint,
         options: options);
-    KernelResult result = await compiler.kernelLoader.load();
-    new DynamicVisitor(compiler.reporter, result.component, allowedListPath,
+    load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+        compiler.options,
+        compiler.provider,
+        compiler.reporter,
+        compiler.initializedCompilerState,
+        false));
+    compiler.frontendStrategy
+        .registerLoadedLibraries(result.component, result.libraries);
+    DynamicVisitor(compiler.reporter, result.component, allowedListPath,
             analyzedUrisFilter)
         .run(verbose: verbose, generate: generate);
   });
diff --git a/pkg/compiler/test/analyses/static_type_visitor_test.dart b/pkg/compiler/test/analyses/static_type_visitor_test.dart
index 4ec5093..3c504c0 100644
--- a/pkg/compiler/test/analyses/static_type_visitor_test.dart
+++ b/pkg/compiler/test/analyses/static_type_visitor_test.dart
@@ -7,7 +7,7 @@
 import 'package:async_helper/async_helper.dart';
 import 'package:compiler/src/compiler.dart';
 import 'package:compiler/src/ir/static_type.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
 import 'package:kernel/ast.dart' as ir;
 import 'package:kernel/class_hierarchy.dart' as ir;
 import 'package:kernel/core_types.dart' as ir;
@@ -26,7 +26,12 @@
     Compiler compiler = await compilerFor(
         memorySourceFiles: {'main.dart': source},
         entryPoint: Uri.parse('memory:main.dart'));
-    KernelResult result = await compiler.kernelLoader.load();
+    load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+        compiler.options,
+        compiler.provider,
+        compiler.reporter,
+        compiler.initializedCompilerState,
+        false));
     ir.Component component = result.component;
     StaticTypeVisitor visitor = new Visitor(component);
     component.accept(visitor);
diff --git a/pkg/compiler/test/end_to_end/dill_loader_test.dart b/pkg/compiler/test/end_to_end/dill_loader_test.dart
index 6e22f60..fe24e88 100644
--- a/pkg/compiler/test/end_to_end/dill_loader_test.dart
+++ b/pkg/compiler/test/end_to_end/dill_loader_test.dart
@@ -10,7 +10,7 @@
 import 'package:compiler/src/elements/entities.dart'
     show LibraryEntity, ClassEntity;
 import 'package:compiler/src/kernel/dart2js_target.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
 import 'package:expect/expect.dart';
 import 'package:front_end/src/api_unstable/dart2js.dart';
 import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent;
@@ -42,8 +42,14 @@
         memorySourceFiles: {'main.dill': kernelBinary},
         diagnosticHandler: diagnostics,
         outputProvider: output);
-    KernelResult result = await compiler.kernelLoader.load();
-    compiler.frontendStrategy.registerLoadedLibraries(result);
+    load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+        compiler.options,
+        compiler.provider,
+        compiler.reporter,
+        compiler.initializedCompilerState,
+        false));
+    compiler.frontendStrategy
+        .registerLoadedLibraries(result.component, result.libraries);
 
     Expect.equals(0, diagnostics.errors.length);
     Expect.equals(0, diagnostics.warnings.length);
diff --git a/pkg/compiler/test/end_to_end/modular_loader_test.dart b/pkg/compiler/test/end_to_end/modular_loader_test.dart
index 00abbf1..33fc298 100644
--- a/pkg/compiler/test/end_to_end/modular_loader_test.dart
+++ b/pkg/compiler/test/end_to_end/modular_loader_test.dart
@@ -10,7 +10,7 @@
 import 'package:compiler/src/elements/entities.dart'
     show LibraryEntity, ClassEntity;
 import 'package:compiler/src/kernel/dart2js_target.dart';
-import 'package:compiler/src/kernel/loader.dart';
+import 'package:compiler/src/phase/load_kernel.dart' as load_kernel;
 import 'package:expect/expect.dart';
 import 'package:front_end/src/api_prototype/front_end.dart';
 import 'package:front_end/src/api_prototype/memory_file_system.dart';
@@ -43,8 +43,14 @@
         memorySourceFiles: {'a.dill': aDill, 'b.dill': bDill, 'c.dill': cDill},
         diagnosticHandler: diagnostics,
         outputProvider: output);
-    KernelResult result = await compiler.kernelLoader.load();
-    compiler.frontendStrategy.registerLoadedLibraries(result);
+    load_kernel.Output result = await load_kernel.run(load_kernel.Input(
+        compiler.options,
+        compiler.provider,
+        compiler.reporter,
+        compiler.initializedCompilerState,
+        false));
+    compiler.frontendStrategy
+        .registerLoadedLibraries(result.component, result.libraries);
 
     Expect.equals(0, diagnostics.errors.length);
     Expect.equals(0, diagnostics.warnings.length);
diff --git a/pkg/compiler/test/helpers/memory_compiler.dart b/pkg/compiler/test/helpers/memory_compiler.dart
index 7131e00..31866ca 100644
--- a/pkg/compiler/test/helpers/memory_compiler.dart
+++ b/pkg/compiler/test/helpers/memory_compiler.dart
@@ -109,8 +109,8 @@
     beforeRun(compiler);
   }
   bool isSuccess = await compiler.run();
-  fe.InitializedCompilerState compilerState = kernelInitializedCompilerState =
-      compiler.kernelLoader.initializedCompilerState;
+  fe.InitializedCompilerState compilerState =
+      kernelInitializedCompilerState = compiler.initializedCompilerState;
   return new CompilationResult(compiler,
       isSuccess: isSuccess, kernelInitializedCompilerState: compilerState);
 }
diff --git a/pkg/compiler/test/serialization/serialization_test_helper.dart b/pkg/compiler/test/serialization/serialization_test_helper.dart
index a29a79c..2df1808 100644
--- a/pkg/compiler/test/serialization/serialization_test_helper.dart
+++ b/pkg/compiler/test/serialization/serialization_test_helper.dart
@@ -105,7 +105,7 @@
       options: commonOptions,
       outputProvider: collector,
       beforeRun: (Compiler compiler) {
-        compiler.kernelLoader.forceSerialization = true;
+        compiler.forceSerializationForTesting = true;
       });
   Expect.isTrue(result.isSuccess);
   Map<OutputType, Map<String, String>> expectedOutput = collector.clear();
@@ -119,7 +119,7 @@
       options: commonOptions,
       outputProvider: collector2,
       beforeRun: (Compiler compiler) {
-        compiler.kernelLoader.forceSerialization = true;
+        compiler.forceSerializationForTesting = true;
         compiler.stopAfterClosedWorld = true;
       });
   Expect.isTrue(result2.isSuccess);
@@ -136,7 +136,7 @@
           ['--out=$dillUri', '${Flags.writeClosedWorld}=$closedWorldUri'],
       outputProvider: collector3a,
       beforeRun: (Compiler compiler) {
-        compiler.kernelLoader.forceSerialization = true;
+        compiler.forceSerializationForTesting = true;
       });
   Expect.isTrue(result3a.isSuccess);
   Expect.isTrue(collector3a.binaryOutputMap.containsKey(dillUri));
@@ -164,7 +164,7 @@
           ],
       outputProvider: collector3b,
       beforeRun: (Compiler compiler) {
-        compiler.kernelLoader.forceSerialization = true;
+        compiler.forceSerializationForTesting = true;
         compiler.stopAfterTypeInference = true;
       });
   Expect.isTrue(result3b.isSuccess);
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index be47b4c..8b6b46e 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -5,6 +5,9 @@
 import 'dart:io';
 import 'dart:typed_data';
 
+import 'package:dart2native/dart2native_macho.dart'
+    show writeAppendedMachOExecutable;
+
 // Maximum page size across all supported architectures (arm64 macOS has 16K
 // pages, some arm64 Linux distributions have 64K pages).
 const elfPageSize = 65536;
@@ -14,6 +17,11 @@
 
 Future writeAppendedExecutable(
     String dartaotruntimePath, String payloadPath, String outputPath) async {
+  if (Platform.isMacOS) {
+    return await writeAppendedMachOExecutable(
+        dartaotruntimePath, payloadPath, outputPath);
+  }
+
   final dartaotruntime = File(dartaotruntimePath);
   final int dartaotruntimeLength = dartaotruntime.lengthSync();
 
diff --git a/pkg/dart2native/lib/dart2native_macho.dart b/pkg/dart2native/lib/dart2native_macho.dart
new file mode 100644
index 0000000..7f3b786
--- /dev/null
+++ b/pkg/dart2native/lib/dart2native_macho.dart
@@ -0,0 +1,334 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:dart2native/macho.dart';
+import 'package:dart2native/macho_parser.dart';
+
+const String kSnapshotSegmentName = "__CUSTOM";
+const String kSnapshotSectionName = "__dart_app_snap";
+const int kMinimumSegmentSize = 0x4000;
+// Since arm64 macOS has 16K pages, which is larger than the 4K pages on x64
+// macOS, we use this larger page size to ensure the MachO file is aligned
+// properly on all architectures.
+const int kSegmentAlignment = 0x4000;
+
+int align(int size, int base) {
+  final int over = size % base;
+  if (over != 0) {
+    return size + (base - over);
+  }
+  return size;
+}
+
+// Utility for aligning parts of MachO headers to the defined sizes.
+int vmSizeAlign(int size) {
+  return align(max(size, kMinimumSegmentSize), kSegmentAlignment);
+}
+
+// Returns value + amount only if the original value is within the bounds
+// defined by [withinStart, withinStart + withinSize).
+Uint32 addIfWithin(
+    Uint32 value, Uint64 amount, Uint64 withinStart, Uint64 withinSize) {
+  final intWithinStart = withinStart.asInt();
+  final intWithinSize = withinSize.asInt();
+
+  if (value >= intWithinStart && value < (intWithinStart + intWithinSize)) {
+    return (value.asUint64() + amount).asUint32();
+  } else {
+    return value;
+  }
+}
+
+// Trims a bytestring that an arbitrary number of null characters on the end of
+// it.
+String trimmedBytestring(Uint8List bytestring) {
+  return String.fromCharCodes(bytestring.takeWhile((value) => value != 0));
+}
+
+// Simplifies casting so we get null values back instead of exceptions.
+T? cast<T>(x) => x is T ? x : null;
+
+// Inserts a segment definition into a MachOFile. This does NOT insert the
+// actual segment into the file. It only inserts the definition of that segment
+// into the MachO header.
+//
+// In addition to simply specifying the definition for the segment, this
+// function also moves the existing __LINKEDIT segment to the end of the header
+// definition as is required by the MachO specification (or at least MacOS's
+// implementation of it). In doing so there are several offsets in the original
+// __LINKEDIT segment that must be updated to point to their new location
+// because the __LINKEDIT segment and sections are now in a different
+// place. This function takes care of those shifts as well.
+//
+// Returns the original, unmodified, __LINKEDIT segment.
+Future<MachOSegmentCommand64> insertSegmentDefinition(MachOFile file,
+    File segment, String segmentName, String sectionName) async {
+  // Load in the data to be inserted.
+  final segmentData = await segment.readAsBytes();
+
+  // Find the existing __LINKEDIT segment
+  final linkedit = cast<MachOSegmentCommand64>(file.commands
+      .where((segment) =>
+          segment.asType() is MachOSegmentCommand64 &&
+          MachOConstants.SEG_LINKEDIT ==
+              trimmedBytestring((segment as MachOSegmentCommand64).segname))
+      .first);
+
+  final linkeditIndex = file.commands.indexWhere((segment) =>
+      segment.asType() is MachOSegmentCommand64 &&
+      MachOConstants.SEG_LINKEDIT ==
+          trimmedBytestring((segment as MachOSegmentCommand64).segname));
+
+  if (linkedit == null) {
+    throw FormatException(
+        "Could not find a __LINKEDIT section in the specified binary.");
+  } else {
+    // Create the new segment.
+    final Uint8List segname = Uint8List(16);
+    segname.setRange(0, segmentName.length, ascii.encode(segmentName));
+    segname.fillRange(segmentName.length, 16, 0);
+
+    final Uint64 vmaddr = linkedit.vmaddr;
+    final Uint64 vmsize = Uint64(vmSizeAlign(segmentData.length));
+    final Uint64 fileoff = linkedit.fileoff;
+    final Uint64 filesize = vmsize;
+    final Int32 maxprot = MachOConstants.VM_PROT_READ;
+    final Int32 initprot = maxprot;
+    final Uint32 nsects = Uint32(1);
+
+    final Uint8List sectname = Uint8List(16);
+    sectname.setRange(0, sectionName.length, ascii.encode(sectionName));
+    sectname.fillRange(sectionName.length, 16, 0);
+
+    final Uint64 addr = vmaddr;
+    final Uint64 size = Uint64(segmentData.length);
+    final Uint32 offset = fileoff.asUint32();
+    final Uint32 flags = MachOConstants.S_REGULAR;
+
+    final Uint32 zero = Uint32(0);
+
+    final loadCommandDefinitionSize = 4 * 2;
+    final sectionDefinitionSize = 16 * 2 + 8 * 2 + 4 * 8;
+    final segmentDefinitionSize = 16 + 8 * 4 + 4 * 4;
+    final commandSize = loadCommandDefinitionSize +
+        segmentDefinitionSize +
+        sectionDefinitionSize;
+
+    final loadCommand =
+        MachOLoadCommand(MachOConstants.LC_SEGMENT_64, Uint32(commandSize));
+
+    final section = MachOSection64(sectname, segname, addr, size, offset, zero,
+        zero, zero, flags, zero, zero, zero);
+
+    final segment = MachOSegmentCommand64(Uint32(commandSize), segname, vmaddr,
+        vmsize, fileoff, filesize, maxprot, initprot, nsects, zero, [section]);
+
+    // Setup the new linkedit command.
+    final shiftedLinkeditVmaddr = linkedit.vmaddr + segment.vmsize;
+    final shiftedLinkeditFileoff = linkedit.fileoff + segment.filesize;
+    final shiftedLinkedit = MachOSegmentCommand64(
+        linkedit.cmdsize,
+        linkedit.segname,
+        shiftedLinkeditVmaddr,
+        linkedit.vmsize,
+        shiftedLinkeditFileoff,
+        linkedit.filesize,
+        linkedit.maxprot,
+        linkedit.initprot,
+        linkedit.nsects,
+        linkedit.flags,
+        linkedit.sections);
+
+    // Shift all of the related commands that need to reference the new file
+    // position of the linkedit segment.
+    for (var i = 0; i < file.commands.length; i++) {
+      final command = file.commands[i];
+
+      final offsetAmount = segment.filesize;
+      final withinStart = linkedit.fileoff;
+      final withinSize = linkedit.filesize;
+
+      // For the specific command that we need to adjust, we need to move the
+      // commands' various offsets forward by the new segment's size in the file
+      // (segment.filesize). However, we need to ensure that when we move the
+      // offset forward, we exclude cases where the offset was originally
+      // outside of the linkedit segment (i.e. offset < linkedit.fileoff or
+      // offset >= linkedit.fileoff + linkedit.filesize). The DRY-ing function
+      // addIfWithin takes care of that repeated logic.
+      if (command is MachODyldInfoCommand) {
+        file.commands[i] = MachODyldInfoCommand(
+            command.cmd,
+            command.cmdsize,
+            addIfWithin(
+                command.rebase_off, offsetAmount, withinStart, withinSize),
+            command.rebase_size,
+            addIfWithin(
+                command.bind_off, offsetAmount, withinStart, withinSize),
+            command.bind_size,
+            addIfWithin(
+                command.weak_bind_off, offsetAmount, withinStart, withinSize),
+            command.weak_bind_size,
+            addIfWithin(
+                command.lazy_bind_off, offsetAmount, withinStart, withinSize),
+            command.lazy_bind_size,
+            addIfWithin(
+                command.export_off, offsetAmount, withinStart, withinSize),
+            command.export_size);
+      } else if (command is MachOSymtabCommand) {
+        file.commands[i] = MachOSymtabCommand(
+            command.cmdsize,
+            addIfWithin(command.symoff, offsetAmount, withinStart, withinSize),
+            command.nsyms,
+            addIfWithin(command.stroff, offsetAmount, withinStart, withinSize),
+            command.strsize);
+      } else if (command is MachODysymtabCommand) {
+        file.commands[i] = MachODysymtabCommand(
+            command.cmdsize,
+            command.ilocalsym,
+            command.nlocalsym,
+            command.iextdefsym,
+            command.nextdefsym,
+            command.iundefsym,
+            command.nundefsym,
+            addIfWithin(command.tocoff, offsetAmount, withinStart, withinSize),
+            command.ntoc,
+            addIfWithin(
+                command.modtaboff, offsetAmount, withinStart, withinSize),
+            command.nmodtab,
+            addIfWithin(
+                command.extrefsymoff, offsetAmount, withinStart, withinSize),
+            command.nextrefsyms,
+            addIfWithin(
+                command.indirectsymoff, offsetAmount, withinStart, withinSize),
+            command.nindirectsyms,
+            addIfWithin(
+                command.extreloff, offsetAmount, withinStart, withinSize),
+            command.nextrel,
+            addIfWithin(
+                command.locreloff, offsetAmount, withinStart, withinSize),
+            command.nlocrel);
+      } else if (command is MachOLinkeditDataCommand) {
+        file.commands[i] = MachOLinkeditDataCommand(
+            command.cmd,
+            command.cmdsize,
+            addIfWithin(command.dataoff, offsetAmount, withinStart, withinSize),
+            command.datasize);
+      }
+    }
+
+    // Now we need to build the new header from these modified pieces.
+    file.header = MachOHeader(
+        file.header!.magic,
+        file.header!.cputype,
+        file.header!.cpusubtype,
+        file.header!.filetype,
+        file.header!.ncmds + Uint32(1),
+        file.header!.sizeofcmds + loadCommand.cmdsize,
+        file.header!.flags,
+        file.header!.reserved);
+
+    file.commands[linkeditIndex] = shiftedLinkedit;
+    file.commands.insert(linkeditIndex, segment);
+  }
+
+  return linkedit;
+}
+
+// Pipe from one file stream into another. We do this in chunks to avoid
+// excessive memory load.
+Future<int> pipeStream(RandomAccessFile from, RandomAccessFile to,
+    {int? numToWrite, int chunkSize = 1 << 30}) async {
+  int numWritten = 0;
+  final int fileLength = from.lengthSync();
+  while (from.positionSync() != fileLength) {
+    final int availableBytes = fileLength - from.positionSync();
+    final int numToRead = numToWrite == null
+        ? min(availableBytes, chunkSize)
+        : min(numToWrite - numWritten, min(availableBytes, chunkSize));
+
+    final buffer = await from.read(numToRead);
+    await to.writeFrom(buffer);
+
+    numWritten += numToRead;
+
+    if (numToWrite != null && numWritten >= numToWrite) {
+      break;
+    }
+  }
+
+  return numWritten;
+}
+
+// Writes an "appended" dart runtime + script snapshot file in a format
+// compatible with MachO executables.
+Future writeAppendedMachOExecutable(
+    String dartaotruntimePath, String payloadPath, String outputPath) async {
+  File originalExecutableFile = File(dartaotruntimePath);
+
+  MachOFile machOFile = MachOFile();
+  await machOFile.loadFromFile(originalExecutableFile);
+
+  // Insert the new segment that contains our snapshot data.
+  File newSegmentFile = File(payloadPath);
+
+  // Note that these two values MUST match the ones in
+  // runtime/bin/snapshot_utils.cc, which looks specifically for the snapshot in
+  // this segment/section.
+  final linkeditCommand = await insertSegmentDefinition(
+      machOFile, newSegmentFile, kSnapshotSegmentName, kSnapshotSectionName);
+
+  // Write out the new executable, with the same contents except the new header.
+  File outputFile = File(outputPath);
+  RandomAccessFile stream = await outputFile.open(mode: FileMode.write);
+
+  // Write the MachO header.
+  machOFile.writeSync(stream);
+  final int headerBytesWritten = stream.positionSync();
+
+  RandomAccessFile newSegmentFileStream = await newSegmentFile.open();
+  RandomAccessFile originalFileStream = await originalExecutableFile.open();
+  await originalFileStream.setPosition(headerBytesWritten);
+
+  // Write the unchanged data from the original file.
+  await pipeStream(originalFileStream, stream,
+      numToWrite: linkeditCommand.fileoff.asInt() - headerBytesWritten);
+
+  // Write the inserted section data, ensuring that the data is padded to the
+  // segment size.
+  await pipeStream(newSegmentFileStream, stream);
+  final int newSegmentLength = newSegmentFileStream.lengthSync();
+  final int alignedSegmentSize = vmSizeAlign(newSegmentLength);
+  await stream.writeFrom(List.filled(alignedSegmentSize - newSegmentLength, 0));
+
+  // Copy the rest of the file from the original to the new one.
+  await pipeStream(originalFileStream, stream);
+
+  await stream.close();
+
+  if (machOFile.hasCodeSignature) {
+    // After writing the modified file, we perform ad-hoc signing (no identity)
+    // similar to the linker (the linker-signed option flag) to ensure that any
+    // LC_CODE_SIGNATURE block has the correct CD hashes. This is necessary for
+    // platforms where signature verification is always on (e.g., OS X on M1).
+    final signingProcess = await Process.run(
+        'codesign', ['-o', 'linker-signed', '-s', '-', outputPath]);
+    if (signingProcess.exitCode != 0) {
+      print('Subcommand terminated with exit code ${signingProcess.exitCode}.');
+      if (signingProcess.stdout.isNotEmpty) {
+        print('Subcommand stdout:');
+        print(signingProcess.stdout);
+      }
+      if (signingProcess.stderr.isNotEmpty) {
+        print('Subcommand stderr:');
+        print(signingProcess.stderr);
+      }
+      throw 'Could not sign the new executable';
+    }
+  }
+}
diff --git a/pkg/dart2native/lib/macho.dart b/pkg/dart2native/lib/macho.dart
new file mode 100644
index 0000000..7aaccb8
--- /dev/null
+++ b/pkg/dart2native/lib/macho.dart
@@ -0,0 +1,2416 @@
+// 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.
+
+// This file is a reimplementation of the header file mach-o/loader.h, which is
+// part of the Apple system headers. All comments, which detail the format of
+// Mach-O files, have been reproduced from the orginal header.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+// Extensions for writing the custom byte types (defined below) to streams
+// (RandomAccessFile).
+extension ByteWriter on RandomAccessFile {
+  void writeUint32(Uint32 value) {
+    final intValue = value.asInt();
+    for (int i = 0; i < 4; i++) {
+      writeByteSync((intValue >> (8 * i)) & 0xff);
+    }
+  }
+
+  void writeUint64(Uint64 value) {
+    final intValue = value.asInt();
+    for (int i = 0; i < 8; i++) {
+      writeByteSync((intValue >> (8 * i)) & 0xff);
+    }
+  }
+
+  void writeInt32(Int32 value) {
+    final intValue = value.asInt();
+    for (int i = 0; i < 4; i++) {
+      writeByteSync((intValue >> (8 * i)) & 0xff);
+    }
+  }
+}
+
+// The dart ffi library doesn't have definitions for integer operations (among
+// others) on ffi types. Since we use those operations prolifically in handling
+// MachO files, these are here as a convenience to avoid polluting the code with
+// casts and operations.
+abstract class IntLike {
+  final int _data;
+  const IntLike(this._data);
+
+  int asInt() => _data;
+
+  @override
+  String toString() => asInt().toString();
+}
+
+class Uint64 extends IntLike {
+  const Uint64(int data) : super(data);
+
+  Uint64 operator +(Uint64 other) {
+    return Uint64(_data + other._data);
+  }
+
+  Uint32 asUint32() {
+    if (_data < 0 || _data >= (1 << 32)) {
+      throw FormatException(
+          "Attempted to cast a Uint64 to a Uint32, but the value will not fit in "
+          "32bits: $_data");
+    }
+
+    return Uint32(_data);
+  }
+
+  bool operator <(int other) {
+    // All positively encoded integers are less than negatively encoded ones.
+    if (_data < 0 && other > 0) {
+      return false;
+    }
+    if (other < 0) {
+      return true;
+    }
+    return _data < other;
+  }
+
+  bool operator >(int other) {
+    // All negatively encoded integers are greater than positively encoded ones.
+    if (_data < 0 && other > 0) {
+      return true;
+    }
+    if (other < 0) {
+      return false;
+    }
+    return _data > other;
+  }
+
+  bool operator ==(other) {
+    if (other is Uint64) {
+      return _data == other._data;
+    } else {
+      return false;
+    }
+  }
+}
+
+class Int32 extends IntLike {
+  const Int32(int data) : super(data);
+
+  Int32 operator |(Int32 other) {
+    return Int32(_data | other._data);
+  }
+
+  bool operator ==(other) {
+    if (other is Int32) {
+      return _data == other._data;
+    } else {
+      return false;
+    }
+  }
+}
+
+class Uint16 extends IntLike {
+  const Uint16(int data) : super(data);
+}
+
+class Uint32 extends IntLike {
+  const Uint32(int data) : super(data);
+
+  Uint32 operator |(Uint32 other) {
+    return Uint32(_data | other._data);
+  }
+
+  Uint32 operator +(Uint32 other) {
+    return Uint32(_data + other._data);
+  }
+
+  Uint32 operator &(Uint32 other) {
+    return Uint32(_data & other._data);
+  }
+
+  Uint32 operator >>(Uint32 other) {
+    return Uint32(_data >> other._data);
+  }
+
+  bool operator <(int other) {
+    return _data < other;
+  }
+
+  bool operator >(int other) {
+    return _data > other;
+  }
+
+  bool operator >=(int other) {
+    return _data >= other;
+  }
+
+  bool operator ==(other) {
+    if (other is Uint32) {
+      return _data == other._data;
+    } else {
+      return false;
+    }
+  }
+
+  Uint64 asUint64() {
+    return Uint64(_data);
+  }
+}
+
+// A load command is simply a part of the MachO header that indicates there is
+// typed schema that a consumer of the headers can use to understand how to load
+// and run various parts of the file (e.g. where to find the TEXT and DATA
+// sections). Every load command with a known schema in a MachO header should
+// extend this abstract class. This class does not appear in the original MachO
+// definitions, but is useful for the object-oriented nature of this
+// implementation.
+abstract class IMachOLoadCommand<T> {
+  /* type of load command (uint32_t) */
+  final Uint32 cmd;
+  /* total size of command in bytes (uint32_t) */
+  final Uint32 cmdsize;
+
+  IMachOLoadCommand(this.cmd, this.cmdsize);
+  T asType();
+
+  void writeSync(RandomAccessFile stream) {
+    stream.writeUint32(cmd);
+    stream.writeUint32(cmdsize);
+    writeContentsSync(stream);
+  }
+
+  // Subclasses need to implement this serializer, which should NOT
+  // attempt to serialize the cmd and the cmdsize to the stream. That
+  // logic is handled by the parent class automatically.
+  void writeContentsSync(RandomAccessFile stream);
+}
+
+// In cases where it's not necessary to actually deserialize a load command into
+// its schema, we use this catch-all class.
+class MachOGenericLoadCommand
+    extends IMachOLoadCommand<MachOGenericLoadCommand> {
+  final Uint8List contents;
+
+  MachOGenericLoadCommand(cmd, cmdsize, this.contents) : super(cmd, cmdsize);
+
+  @override
+  MachOGenericLoadCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeFromSync(contents);
+  }
+}
+
+// There are two types of headers: 32bit and 64bit. The only difference is that
+// 64bit headers have a reserved field. This class does not appear in the
+// original header definitions, but is useful for the object-oriented nature of
+// this implementation.
+abstract class IMachOHeader {
+  /* mach magic number identifier (uint32_t) */
+  final Uint32 magic;
+  /* cpu specifier (uint32_t) */
+  final Uint32 cputype;
+  /* machine specifier (uint32_t) */
+  final Uint32 cpusubtype;
+  /* type of file (uint32_t) */
+  final Uint32 filetype;
+  /* number of load commands (uint32_t) */
+  final Uint32 ncmds;
+  /* the size of all the load commands (uint32_t) */
+  final Uint32 sizeofcmds;
+  /* flags (uint32_t) */
+  final Uint32 flags;
+
+  /* reserved (uint32_t) */
+  final Uint32 reserved; // Only used in 64bit MachO files
+
+  IMachOHeader(
+    this.magic,
+    this.cputype,
+    this.cpusubtype,
+    this.filetype,
+    this.ncmds,
+    this.sizeofcmds,
+    this.flags,
+    this.reserved,
+  );
+}
+
+/*
+* The 32-bit mach header appears at the very beginning of the object file for
+* 32-bit architectures.
+*/
+class MachOHeader32 extends IMachOHeader {
+  MachOHeader32(
+    Uint32 magic,
+    Uint32 cputype,
+    Uint32 cpusubtype,
+    Uint32 filetype,
+    Uint32 ncmds,
+    Uint32 sizeofcmds,
+    Uint32 flags,
+  ) : super(
+          magic,
+          cputype,
+          cpusubtype,
+          filetype,
+          ncmds,
+          sizeofcmds,
+          flags,
+          Uint32(0),
+        );
+}
+
+/*
+* The 64-bit mach header appears at the very beginning of object files for
+* 64-bit architectures.
+*/
+class MachOHeader extends IMachOHeader {
+  MachOHeader(
+    Uint32 magic,
+    Uint32 cputype,
+    Uint32 cpusubtype,
+    Uint32 filetype,
+    Uint32 ncmds,
+    Uint32 sizeofcmds,
+    Uint32 flags,
+    Uint32 reserved,
+  ) : super(
+          magic,
+          cputype,
+          cpusubtype,
+          filetype,
+          ncmds,
+          sizeofcmds,
+          flags,
+          reserved,
+        );
+}
+
+/*
+* The load commands directly follow the mach_header.  The total size of all
+* of the commands is given by the sizeofcmds field in the mach_header.  All
+* load commands must have as their first two fields cmd and cmdsize.  The cmd
+* field is filled in with a constant for that command type.  Each command type
+* has a structure specifically for it.  The cmdsize field is the size in bytes
+* of the particular load command structure plus anything that follows it that
+* is a part of the load command (i.e. section structures, strings, etc.).  To
+* advance to the next load command the cmdsize can be added to the offset or
+* pointer of the current load command.  The cmdsize for 32-bit architectures
+* MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
+* of 8 bytes (these are forever the maximum alignment of any load commands).
+* The padded bytes must be zero.  All tables in the object file must also
+* follow these rules so the file can be memory mapped.  Otherwise the pointers
+* to these tables will not work well or at all on some machines.  With all
+* padding zeroed like objects will compare byte for byte.
+*/
+class MachOLoadCommand {
+  /* type of load command (uint32_t) */
+  final Uint32 cmd;
+  /* total size of command in bytes (uint32_t) */
+  final Uint32 cmdsize;
+
+  MachOLoadCommand(this.cmd, this.cmdsize);
+}
+
+/*
+* The segment load command indicates that a part of this file is to be
+* mapped into the task's address space.  The size of this segment in memory,
+* vmsize, maybe equal to or larger than the amount to map from this file,
+* filesize.  The file is mapped starting at fileoff to the beginning of
+* the segment in memory, vmaddr.  The rest of the memory of the segment,
+* if any, is allocated zero fill on demand.  The segment's maximum virtual
+* memory protection and initial virtual memory protection are specified
+* by the maxprot and initprot fields.  If the segment has sections then the
+* section structures directly follow the segment command and their size is
+* reflected in cmdsize.
+*/
+class MachOSegmentCommand extends IMachOLoadCommand<MachOSegmentCommand> {
+  /* For 32Bit Architectures */
+  final Uint8List segname; /* segment name */
+  final Uint32 vmaddr; /* memory address of this segment (uint32_t) */
+  final Uint32 vmsize; /* memory size of this segment (uint32_t) */
+  final Uint32 fileoff; /* file offset of this segment (uint32_t) */
+  final Uint32 filesize; /* amount to map from the file (uint32_t) */
+  final Int32 maxprot; /* maximum VM protection (int32) */
+  final Int32 initprot; /* initial VM protection (int32) */
+  final Uint32 nsects; /* number of sections in segment (uint32_t) */
+  final Uint32 flags; /* flags (uint32_t) */
+
+  MachOSegmentCommand(
+    Uint32 cmdsize,
+    this.segname,
+    this.vmaddr,
+    this.vmsize,
+    this.fileoff,
+    this.filesize,
+    this.maxprot,
+    this.initprot,
+    this.nsects,
+    this.flags,
+  ) : super(MachOConstants.LC_SEGMENT, cmdsize);
+
+  @override
+  MachOSegmentCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeFromSync(segname);
+    stream.writeUint32(vmaddr);
+    stream.writeUint32(vmsize);
+    stream.writeUint32(fileoff);
+    stream.writeUint32(filesize);
+    stream.writeInt32(maxprot);
+    stream.writeInt32(initprot);
+    stream.writeUint32(nsects);
+    stream.writeUint32(flags);
+  }
+}
+
+/*
+* The 64-bit segment load command indicates that a part of this file is to be
+* mapped into a 64-bit task's address space.  If the 64-bit segment has
+* sections then section_64 structures directly follow the 64-bit segment
+* command and their size is reflected in cmdsize.
+*/
+class MachOSegmentCommand64 extends IMachOLoadCommand<MachOSegmentCommand64> {
+  /* For 64Bit Architectures */
+  final Uint8List segname; //[16] /* segment name */
+  final Uint64 vmaddr; /* memory address of this segment (uint64_t) */
+  final Uint64 vmsize; /* memory size of this segment (uint64_t) */
+  final Uint64 fileoff; /* file offset of this segment (uint64_t) */
+  final Uint64 filesize; /* amount to map from the file (uint64_t) */
+  final Int32 maxprot; /* maximum VM protection (int32) */
+  final Int32 initprot; /* initial VM protection (int32) */
+  final Uint32 nsects; /* number of sections in segment (uint32_t) */
+  final Uint32 flags; /* flags (uint32_t) */
+
+  final List<MachOSection64> sections;
+
+  MachOSegmentCommand64(
+    Uint32 cmdsize,
+    this.segname,
+    this.vmaddr,
+    this.vmsize,
+    this.fileoff,
+    this.filesize,
+    this.maxprot,
+    this.initprot,
+    this.nsects,
+    this.flags,
+    this.sections,
+  ) : super(MachOConstants.LC_SEGMENT_64, cmdsize);
+
+  @override
+  MachOSegmentCommand64 asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeFromSync(segname);
+    stream.writeUint64(vmaddr);
+    stream.writeUint64(vmsize);
+    stream.writeUint64(fileoff);
+    stream.writeUint64(filesize);
+    stream.writeInt32(maxprot);
+    stream.writeInt32(initprot);
+    stream.writeUint32(nsects);
+    stream.writeUint32(flags);
+
+    sections.forEach((section) {
+      section.writeContentsSync(stream);
+    });
+  }
+}
+
+/*
+* A segment is made up of zero or more sections.  Non-MH_OBJECT files have
+* all of their segments with the proper sections in each, and padded to the
+* specified segment alignment when produced by the link editor.  The first
+* segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header
+* and load commands of the object file before its first section.  The zero
+* fill sections are always last in their segment (in all formats).  This
+* allows the zeroed segment padding to be mapped into memory where zero fill
+* sections might be. The gigabyte zero fill sections, those with the section
+* type S_GB_ZEROFILL, can only be in a segment with sections of this type.
+* These segments are then placed after all other segments.
+*
+* The MH_OBJECT format has all of its sections in one segment for
+* compactness.  There is no padding to a specified segment boundary and the
+* mach_header and load commands are not part of the segment.
+*
+* Sections with the same section name, sectname, going into the same segment,
+* segname, are combined by the link editor.  The resulting section is aligned
+* to the maximum alignment of the combined sections and is the new section's
+* alignment.  The combined sections are aligned to their original alignment in
+* the combined section.  Any padded bytes to get the specified alignment are
+* zeroed.
+*
+* The format of the relocation entries referenced by the reloff and nreloc
+* fields of the section structure for mach object files is described in the
+* header file <reloc.h>.
+*/
+class MachOSection {
+  /* for 32-bit architectures */
+  final Uint8List sectname; /* name of this section */
+  final Uint8List segname; /* segment this section goes in */
+  final Uint32 addr; /* memory address of this section (uint32_t) */
+  final Uint32 size; /* size in bytes of this section (uint32_t) */
+  final Uint32 offset; /* file offset of this section (uint32_t) */
+  final Uint32 align; /* section alignment (power of 2) (uint32_t) */
+  final Uint32 reloff; /* file offset of relocation entries (uint32_t) */
+  final Uint32 nreloc; /* number of relocation entries (uint32_t) */
+  final Uint32 flags; /* flags (section type and attributes)(uint32_t) */
+  final Uint32 reserved1; /* reserved (for offset or index) (uint32_t) */
+  final Uint32 reserved2; /* reserved (for count or sizeof) (uint32_t) */
+
+  MachOSection(
+    this.sectname,
+    this.segname,
+    this.addr,
+    this.size,
+    this.offset,
+    this.align,
+    this.reloff,
+    this.nreloc,
+    this.flags,
+    this.reserved1,
+    this.reserved2,
+  ) {
+    if (segname.length != 16) {
+      throw ArgumentError("segname must be 16 bytes exactly");
+    }
+  }
+}
+
+class MachOSection64 {
+  /* for 64-bit architectures */
+  final Uint8List sectname; //[16] /* name of this section */
+  final Uint8List segname; //[16] /* segment this section goes in */
+  final Uint64 addr; /* memory address of this section (uint64_t) */
+  final Uint64 size; /* size in bytes of this section (uint64_t) */
+  final Uint32 offset; /* file offset of this section (uint32_t) */
+  final Uint32 align; /* section alignment (power of 2) (uint32_t) */
+  final Uint32 reloff; /* file offset of relocation entries (uint32_t) */
+  final Uint32 nreloc; /* number of relocation entries (uint32_t) */
+  final Uint32 flags; /* flags (section type and attributes)(uint32_t) */
+  final Uint32 reserved1; /* reserved (for offset or index) (uint32_t) */
+  final Uint32 reserved2; /* reserved (for count or sizeof) (uint32_t) */
+  final Uint32 reserved3; /* reserved (uint32_t) */
+
+  MachOSection64(
+    this.sectname,
+    this.segname,
+    this.addr,
+    this.size,
+    this.offset,
+    this.align,
+    this.reloff,
+    this.nreloc,
+    this.flags,
+    this.reserved1,
+    this.reserved2,
+    this.reserved3,
+  ) {
+    if (segname.length != 16) {
+      throw ArgumentError("segname must be 16 bytes exactly");
+    }
+  }
+
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeFromSync(sectname);
+    stream.writeFromSync(segname);
+    stream.writeUint64(addr);
+    stream.writeUint64(size);
+    stream.writeUint32(offset);
+    stream.writeUint32(align);
+    stream.writeUint32(reloff);
+    stream.writeUint32(nreloc);
+    stream.writeUint32(flags);
+    stream.writeUint32(reserved1);
+    stream.writeUint32(reserved2);
+    stream.writeUint32(reserved3);
+  }
+}
+
+// This is a stand-in for the lc_str union in the MachO header.
+class MachOStr {
+  final int offset;
+  final Uint8List ptr;
+
+  MachOStr(this.offset, this.ptr);
+  // part of the schema so it doesn't contribute to
+  // the size of this schema.
+
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeInt32(Int32(offset));
+    stream.writeFromSync(ptr);
+  }
+}
+
+/*
+ * Fixed virtual memory shared libraries are identified by two things.  The
+ * target pathname (the name of the library as found for execution), and the
+ * minor version number.  The address of where the headers are loaded is in
+ * header_addr. (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOFvmlib {
+  final MachOStr name; /* library's target pathname */
+  final Uint32 minor_version; /* library's minor version number (uint32_t) */
+  final Uint32 header_addr; /* library's header address (uint32_t) */
+
+  MachOFvmlib(
+    this.name,
+    this.minor_version,
+    this.header_addr,
+  );
+
+  void writeContentsSync(RandomAccessFile stream) {
+    name.writeContentsSync(stream);
+    stream.writeUint32(minor_version);
+    stream.writeUint32(header_addr);
+  }
+}
+
+/*
+ * A fixed virtual shared library (filetype == MH_FVMLIB in the mach header)
+ * contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library.
+ * An object that uses a fixed virtual shared library also contains a
+ * fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses.
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOFvmlibCommand extends IMachOLoadCommand<MachOFvmlibCommand> {
+  final MachOFvmlib fvmlib; /* the library identification */
+
+  MachOFvmlibCommand(
+    Uint32 cmdsize,
+    this.fvmlib,
+  ) : super(MachOConstants.LC_IDFVMLIB, cmdsize);
+
+  @override
+  MachOFvmlibCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    fvmlib.writeContentsSync(stream);
+  }
+}
+
+/*
+ * Dynamicly linked shared libraries are identified by two things.  The
+ * pathname (the name of the library as found for execution), and the
+ * compatibility version number.  The pathname must match and the compatibility
+ * number in the user of the library must be greater than or equal to the
+ * library being used.  The time stamp is used to record the time a library was
+ * built and copied into user so it can be use to determined if the library used
+ * at runtime is exactly the same as used to built the program.
+ */
+class MachODylib {
+  final MachOStr name; /* library's path name */
+  final Uint32 timestamp; /* library's build time stamp (uint32_t) */
+  final Uint32
+      current_version; /* library's current version number (uint32_t) */
+  final Uint32
+      compatibility_version; /* library's compatibility vers number(uint32_t) */
+
+  MachODylib(
+    this.name,
+    this.timestamp,
+    this.current_version,
+    this.compatibility_version,
+  );
+
+  void writeContentsSync(RandomAccessFile stream) {
+    name.writeContentsSync(stream);
+    stream.writeUint32(timestamp);
+    stream.writeUint32(current_version);
+    stream.writeUint32(compatibility_version);
+  }
+}
+
+/*
+ * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
+ * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
+ * An object that uses a dynamically linked shared library also contains a
+ * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
+ * LC_REEXPORT_DYLIB) for each library it uses.
+ */
+class MachODylibCommand extends IMachOLoadCommand<MachODylibCommand> {
+  final MachODylib dylib; /* the library identification */
+
+  MachODylibCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    this.dylib,
+  ) : super(cmd, cmdsize) {
+    if (this.cmd != MachOConstants.LC_ID_DYLIB &&
+        this.cmd != MachOConstants.LC_LOAD_WEAK_DYLIB &&
+        this.cmd != MachOConstants.LC_REEXPORT_DYLIB) {
+      throw ArgumentError(
+          "cmd was not one of LC_ID_DYLIB (${MachOConstants.LC_ID_DYLIB}), "
+          "LC_LOAD_WEAK_DYLIB (${MachOConstants.LC_LOAD_WEAK_DYLIB}), "
+          "LC_REEXPORT_DYLIB (${MachOConstants.LC_REEXPORT_DYLIB}): $cmd");
+    }
+  }
+
+  @override
+  MachODylibCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    dylib.writeContentsSync(stream);
+  }
+}
+
+/*
+ * A dynamically linked shared library may be a subframework of an umbrella
+ * framework.  If so it will be linked with "-umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the umbrella framework. A subframework
+ * can only be linked against by its umbrella framework or other subframeworks
+ * that are part of the same umbrella framework.  Otherwise the static link
+ * editor produces an error and states to link against the umbrella framework.
+ * The name of the umbrella framework for subframeworks is recorded in the
+ * following structure.
+ */
+class MachOSubFrameworkCommand
+    extends IMachOLoadCommand<MachOSubFrameworkCommand> {
+  final MachOStr umbrella; /* the umbrella framework name */
+
+  MachOSubFrameworkCommand(
+    Uint32 cmdsize,
+    this.umbrella,
+  ) : super(MachOConstants.LC_SUB_FRAMEWORK, cmdsize);
+
+  @override
+  MachOSubFrameworkCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    umbrella.writeContentsSync(stream);
+  }
+}
+
+/*
+ * For dynamically linked shared libraries that are subframework of an umbrella
+ * framework they can allow clients other than the umbrella framework or other
+ * subframeworks in the same umbrella framework.  To do this the subframework
+ * is built with "-allowable_client client_name" and an LC_SUB_CLIENT load
+ * command is created for each -allowable_client flag.  The client_name is
+ * usually a framework name.  It can also be a name used for bundles clients
+ * where the bundle is built with "-client_name client_name".
+ */
+class MachOSubClientCommand extends IMachOLoadCommand<MachOSubClientCommand> {
+  final MachOStr client; /* the client name */
+
+  MachOSubClientCommand(
+    Uint32 cmdsize,
+    this.client,
+  ) : super(MachOConstants.LC_SUB_CLIENT, cmdsize);
+
+  @override
+  MachOSubClientCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    client.writeContentsSync(stream);
+  }
+}
+
+/*
+ * A dynamically linked shared library may be a sub_umbrella of an umbrella
+ * framework.  If so it will be linked with "-sub_umbrella umbrella_name" where
+ * Where "umbrella_name" is the name of the sub_umbrella framework.  When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace
+ * umbrella framework will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks to be implicited linked in.  Any other
+ * dependent dynamic libraries will not be linked it when -twolevel_namespace
+ * is in effect.  The primary library recorded by the static linker when
+ * resolving a symbol in these libraries will be the umbrella framework.
+ * Zero or more sub_umbrella frameworks may be use by an umbrella framework.
+ * The name of a sub_umbrella framework is recorded in the following structure.
+ */
+class MachOSubUmbrellaCommand
+    extends IMachOLoadCommand<MachOSubUmbrellaCommand> {
+  final MachOStr sub_umbrella; /* the sub_umbrella framework name */
+
+  MachOSubUmbrellaCommand(
+    Uint32 cmdsize,
+    this.sub_umbrella,
+  ) : super(
+          MachOConstants.LC_SUB_UMBRELLA,
+          cmdsize,
+        );
+
+  @override
+  MachOSubUmbrellaCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    sub_umbrella.writeContentsSync(stream);
+  }
+}
+
+/*
+ * A dynamically linked shared library may be a sub_library of another shared
+ * library.  If so it will be linked with "-sub_library library_name" where
+ * Where "library_name" is the name of the sub_library shared library.  When
+ * staticly linking when -twolevel_namespace is in effect a twolevel namespace
+ * shared library will only cause its subframeworks and those frameworks
+ * listed as sub_umbrella frameworks and libraries listed as sub_libraries to
+ * be implicited linked in.  Any other dependent dynamic libraries will not be
+ * linked it when -twolevel_namespace is in effect.  The primary library
+ * recorded by the static linker when resolving a symbol in these libraries
+ * will be the umbrella framework (or dynamic library). Zero or more sub_library
+ * shared libraries may be use by an umbrella framework or (or dynamic library).
+ * The name of a sub_library framework is recorded in the following structure.
+ * For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc".
+ */
+class MachOSubLibraryCommand extends IMachOLoadCommand<MachOSubLibraryCommand> {
+  final MachOStr sub_library; /* the sub_library name */
+
+  MachOSubLibraryCommand(
+    Uint32 cmdsize,
+    this.sub_library,
+  ) : super(
+          MachOConstants.LC_SUB_LIBRARY,
+          cmdsize,
+        );
+
+  @override
+  MachOSubLibraryCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    sub_library.writeContentsSync(stream);
+  }
+}
+
+/*
+ * A program (filetype == MH_EXECUTE) that is
+ * prebound to its dynamic libraries has one of these for each library that
+ * the static linker used in prebinding.  It contains a bit vector for the
+ * modules in the library.  The bits indicate which modules are bound (1) and
+ * which are not (0) from the library.  The bit for module 0 is the low bit
+ * of the first byte.  So the bit for the Nth module is:
+ * (linked_modules[N/8] >> N%8) & 1
+ */
+class MachOPreboundDylibCommand
+    extends IMachOLoadCommand<MachOPreboundDylibCommand> {
+  final MachOStr name; /* library's path name */
+  final Uint32 nmodules; /* number of modules in library (uint32_t) */
+  final MachOStr linked_modules; /* bit vector of linked modules */
+
+  MachOPreboundDylibCommand(
+    Uint32 cmdsize,
+    this.name,
+    this.nmodules,
+    this.linked_modules,
+  ) : super(
+          MachOConstants.LC_PREBOUND_DYLIB,
+          cmdsize,
+        );
+
+  @override
+  MachOPreboundDylibCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    name.writeContentsSync(stream);
+    stream.writeUint32(nmodules);
+    linked_modules.writeContentsSync(stream);
+  }
+}
+
+/*
+ * A program that uses a dynamic linker contains a dylinker_command to identify
+ * the name of the dynamic linker (LC_LOAD_DYLINKER).  And a dynamic linker
+ * contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER).
+ * A file can have at most one of these.
+ * This struct is also used for the LC_DYLD_ENVIRONMENT load command and
+ * contains string for dyld to treat like environment variable.
+ */
+class MachODylinkerCommand extends IMachOLoadCommand<MachODylinkerCommand> {
+  final MachOStr name; /* dynamic linker's path name */
+
+  MachODylinkerCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    this.name,
+  ) : super(
+          cmd,
+          cmdsize,
+        ) {
+    if (this.cmd != MachOConstants.LC_ID_DYLINKER &&
+        this.cmd != MachOConstants.LC_LOAD_DYLINKER &&
+        this.cmd != MachOConstants.LC_DYLD_ENVIRONMENT) {
+      throw ArgumentError(
+          "cmd was not one of LC_ID_DYLINKER (${MachOConstants.LC_ID_DYLINKER}), "
+          "LC_LOAD_DYLINKER (${MachOConstants.LC_LOAD_DYLINKER}), "
+          "LC_DYLD_ENVIRONMENT (${MachOConstants.LC_DYLD_ENVIRONMENT}): $cmd");
+    }
+  }
+
+  @override
+  MachODylinkerCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    name.writeContentsSync(stream);
+  }
+}
+
+/*
+ * Thread commands contain machine-specific data structures suitable for
+ * use in the thread state primitives.  The machine specific data structures
+ * follow the struct thread_command as follows.
+ * Each flavor of machine specific data structure is preceded by an uint32_t
+ * constant for the flavor of that data structure, an uint32_t that is the
+ * count of uint32_t's of the size of the state data structure and then
+ * the state data structure follows.  This triple may be repeated for many
+ * flavors.  The constants for the flavors, counts and state data structure
+ * definitions are expected to be in the header file <machine/thread_status.h>.
+ * These machine specific data structures sizes must be multiples of
+ * 4 bytes.  The cmdsize reflects the total size of the thread_command
+ * and all of the sizes of the constants for the flavors, counts and state
+ * data structures.
+ *
+ * For executable objects that are unix processes there will be one
+ * thread_command (cmd == LC_UNIXTHREAD) created for it by the link-editor.
+ * This is the same as a LC_THREAD, except that a stack is automatically
+ * created (based on the shell's limit for the stack size).  Command arguments
+ * and environment variables are copied onto that stack.
+ */
+class MachOThreadCommand extends IMachOLoadCommand<MachOThreadCommand> {
+  /* final int flavor		   flavor of thread state (uint32_t) */
+  /* final int count		   count of longs in thread state (uint32_t) */
+  /* struct XXX_thread_state state   thread state for this flavor */
+  /* ... */
+
+  MachOThreadCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    /* final int flavor		   flavor of thread state (uint32_t) */
+    /* final int count		   count of longs in thread state (uint32_t) */
+    /* struct XXX_thread_state state   thread state for this flavor */
+    /* ... */
+  ) : super(
+          cmd,
+          cmdsize,
+        ) {
+    if (this.cmd != MachOConstants.LC_THREAD &&
+        this.cmd != MachOConstants.LC_UNIXTHREAD) {
+      throw ArgumentError(
+          "cmd was not one of LC_THREAD (${MachOConstants.LC_THREAD}), "
+          "LC_UNIXTHREAD (${MachOConstants.LC_UNIXTHREAD}): $cmd");
+    }
+  }
+
+  @override
+  MachOThreadCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {}
+}
+
+/*
+ * The routines command contains the address of the dynamic shared library
+ * initialization routine and an index into the module table for the module
+ * that defines the routine.  Before any modules are used from the library the
+ * dynamic linker fully binds the module that defines the initialization routine
+ * and then calls it.  This gets called before any module initialization
+ * routines (used for C++ static constructors) in the library.
+ */
+class MachORoutinesCommand extends IMachOLoadCommand<MachORoutinesCommand> {
+  final Uint32 init_address; /* address of initialization routine (uint32_t) */
+  final Uint32 init_module; /* index into the module table that (uint32_t) */
+  /*  the init routine is defined in */
+  final Uint32 reserved1; /* (uint32_t) */
+  final Uint32 reserved2; /* (uint32_t) */
+  final Uint32 reserved3; /* (uint32_t) */
+  final Uint32 reserved4; /* (uint32_t) */
+  final Uint32 reserved5; /* (uint32_t) */
+  final Uint32 reserved6; /* (uint32_t) */
+
+  MachORoutinesCommand(
+    Uint32 cmdsize,
+    this.init_address,
+    this.init_module,
+    this.reserved1,
+    this.reserved2,
+    this.reserved3,
+    this.reserved4,
+    this.reserved5,
+    this.reserved6,
+  ) : super(
+          MachOConstants.LC_ROUTINES,
+          cmdsize,
+        );
+
+  @override
+  MachORoutinesCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(init_address);
+    stream.writeUint32(init_module);
+    stream.writeUint32(reserved1);
+    stream.writeUint32(reserved2);
+    stream.writeUint32(reserved3);
+    stream.writeUint32(reserved4);
+    stream.writeUint32(reserved5);
+    stream.writeUint32(reserved6);
+  }
+}
+
+/*
+ * The 64-bit routines command.  Same use as above.
+ */
+class MachORoutinesCommand64 extends IMachOLoadCommand<MachORoutinesCommand64> {
+  final Uint64 init_address; /* address of initialization routine (uint64_t) */
+  final Uint64 init_module; /* index into the module table that (uint64_t) */
+  /*  the init routine is defined in */
+  final Uint64 reserved1; /* (uint64_t) */
+  final Uint64 reserved2; /* (uint64_t) */
+  final Uint64 reserved3; /* (uint64_t) */
+  final Uint64 reserved4; /* (uint64_t) */
+  final Uint64 reserved5; /* (uint64_t) */
+  final Uint64 reserved6; /* (uint64_t) */
+
+  MachORoutinesCommand64(
+    Uint32 cmdsize,
+    this.init_address,
+    this.init_module,
+    this.reserved1,
+    this.reserved2,
+    this.reserved3,
+    this.reserved4,
+    this.reserved5,
+    this.reserved6,
+  ) : super(
+          MachOConstants.LC_ROUTINES_64,
+          cmdsize,
+        );
+
+  @override
+  MachORoutinesCommand64 asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint64(init_address);
+    stream.writeUint64(init_module);
+    stream.writeUint64(reserved1);
+    stream.writeUint64(reserved2);
+    stream.writeUint64(reserved3);
+    stream.writeUint64(reserved4);
+    stream.writeUint64(reserved5);
+    stream.writeUint64(reserved6);
+  }
+}
+
+/*
+ * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
+ * "stab" style symbol table information as described in the header files
+ * <nlist.h> and <stab.h>.
+ */
+class MachOSymtabCommand extends IMachOLoadCommand<MachOSymtabCommand> {
+  final Uint32 symoff; /* symbol table offset (uint32_t) */
+  final Uint32 nsyms; /* number of symbol table entries (uint32_t) */
+  final Uint32 stroff; /* string table offset (uint32_t) */
+  final Uint32 strsize; /* string table size in bytes (uint32_t) */
+
+  MachOSymtabCommand(
+    Uint32 cmdsize,
+    this.symoff,
+    this.nsyms,
+    this.stroff,
+    this.strsize,
+  ) : super(
+          MachOConstants.LC_SYMTAB,
+          cmdsize,
+        );
+
+  @override
+  MachOSymtabCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(symoff);
+    stream.writeUint32(nsyms);
+    stream.writeUint32(stroff);
+    stream.writeUint32(strsize);
+  }
+}
+
+/*
+ * This is the second set of the symbolic information which is used to support
+ * the data structures for the dynamically link editor.
+ *
+ * The original set of symbolic information in the symtab_command which contains
+ * the symbol and string tables must also be present when this load command is
+ * present.  When this load command is present the symbol table is organized
+ * into three groups of symbols:
+ *	local symbols (static and debugging symbols) - grouped by module
+ *	defined external symbols - grouped by module (sorted by name if not lib)
+ *	undefined external symbols (sorted by name if MH_BINDATLOAD is not set,
+ *	     			    and in order the were seen by the static
+ *				    linker if MH_BINDATLOAD is set)
+ * In this load command there are offsets and counts to each of the three groups
+ * of symbols.
+ *
+ * This load command contains a the offsets and sizes of the following new
+ * symbolic information tables:
+ *	table of contents
+ *	module table
+ *	reference symbol table
+ *	indirect symbol table
+ * The first three tables above (the table of contents, module table and
+ * reference symbol table) are only present if the file is a dynamically linked
+ * shared library.  For executable and object modules, which are files
+ * containing only one module, the information that would be in these three
+ * tables is determined as follows:
+ * 	table of contents - the defined external symbols are sorted by name
+ *	module table - the file contains only one module so everything in the
+ *		       file is part of the module.
+ *	reference symbol table - is the defined and undefined external symbols
+ *
+ * For dynamically linked shared library files this load command also contains
+ * offsets and sizes to the pool of relocation entries for all sections
+ * separated into two groups:
+ *	external relocation entries
+ *	local relocation entries
+ * For executable and object modules the relocation entries continue to hang
+ * off the section structures.
+ */
+class MachODysymtabCommand extends IMachOLoadCommand<MachODysymtabCommand> {
+  /*
+     * The symbols indicated by symoff and nsyms of the LC_SYMTAB load command
+     * are grouped into the following three groups:
+     *    local symbols (further grouped by the module they are from)
+     *    defined external symbols (further grouped by the module they are from)
+     *    undefined symbols
+     *
+     * The local symbols are used only for debugging.  The dynamic binding
+     * process may have to use them to indicate to the debugger the local
+     * symbols for a module that is being bound.
+     *
+     * The last two groups are used by the dynamic binding process to do the
+     * binding (indirectly through the module table and the reference symbol
+     * table when this is a dynamically linked shared library file).
+     */
+  final Uint32 ilocalsym; /* index to local symbols (uint32_t) */
+  final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+  final Uint32 iextdefsym; /* index to externally defined symbols (uint32_t) */
+  final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+
+  final Uint32 iundefsym; /* index to undefined symbols (uint32_t) */
+  final Uint32 nundefsym; /* number of undefined symbols (uint32_t) */
+
+  /*
+     * For the for the dynamic binding process to find which module a symbol
+     * is defined in the table of contents is used (analogous to the ranlib
+     * structure in an archive) which maps defined external symbols to modules
+     * they are defined in.  This exists only in a dynamically linked shared
+     * library file.  For executable and object modules the defined external
+     * symbols are sorted by name and is use as the table of contents.
+     */
+  final Uint32 tocoff; /* file offset to table of contents (uint32_t) */
+  final Uint32 ntoc; /* number of entries in table of contents (uint32_t) */
+
+  /*
+     * To support dynamic binding of "modules" (whole object files) the symbol
+     * table must reflect the modules that the file was created from.  This is
+     * done by having a module table that has indexes and counts into the merged
+     * tables for each module.  The module structure that these two entries
+     * refer to is described below.  This exists only in a dynamically linked
+     * shared library file.  For executable and object modules the file only
+     * contains one module so everything in the file belongs to the module.
+     */
+  final Uint32 modtaboff; /* file offset to module table (uint32_t) */
+  final Uint32 nmodtab; /* number of module table entries (uint32_t) */
+
+  /*
+     * To support dynamic module binding the module structure for each module
+     * indicates the external references (defined and undefined) each module
+     * makes.  For each module there is an offset and a count into the
+     * reference symbol table for the symbols that the module references.
+     * This exists only in a dynamically linked shared library file.  For
+     * executable and object modules the defined external symbols and the
+     * undefined external symbols indicates the external references.
+     */
+  final Uint32 extrefsymoff; /* offset to referenced symbol table (uint32_t) */
+  final Uint32
+      nextrefsyms; /* number of referenced symbol table entries (uint32_t) */
+
+  /*
+     * The sections that contain "symbol pointers" and "routine stubs" have
+     * indexes and (implied counts based on the size of the section and fixed
+     * size of the entry) into the "indirect symbol" table for each pointer
+     * and stub.  For every section of these two types the index into the
+     * indirect symbol table is stored in the section header in the field
+     * reserved1.  An indirect symbol table entry is simply a 32bit index into
+     * the symbol table to the symbol that the pointer or stub is referring to.
+     * The indirect symbol table is ordered to match the entries in the section.
+     */
+  final Uint32
+      indirectsymoff; /* file offset to the indirect symbol table (uint32_t) */
+  final Uint32
+      nindirectsyms; /* number of indirect symbol table entries (uint32_t) */
+
+  /*
+     * To support relocating an individual module in a library file quickly the
+     * external relocation entries for each module in the library need to be
+     * accessed efficiently.  Since the relocation entries can't be accessed
+     * through the section headers for a library file they are separated into
+     * groups of local and external entries further grouped by module.  In this
+     * case the presents of this load command who's extreloff, nextrel,
+     * locreloff and nlocrel fields are non-zero indicates that the relocation
+     * entries of non-merged sections are not referenced through the section
+     * structures (and the reloff and nreloc fields in the section headers are
+     * set to zero).
+     *
+     * Since the relocation entries are not accessed through the section headers
+     * this requires the r_address field to be something other than a section
+     * offset to identify the item to be relocated.  In this case r_address is
+     * set to the offset from the vmaddr of the first LC_SEGMENT command.
+     * For MH_SPLIT_SEGS images r_address is set to the the offset from the
+     * vmaddr of the first read-write LC_SEGMENT command.
+     *
+     * The relocation entries are grouped by module and the module table
+     * entries have indexes and counts into them for the group of external
+     * relocation entries for that the module.
+     *
+     * For sections that are merged across modules there must not be any
+     * remaining external relocation entries for them (for merged sections
+     * remaining relocation entries must be local).
+     */
+  final Uint32 extreloff; /* offset to external relocation entries (uint32_t) */
+  final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+  /*
+     * All the local relocation entries are grouped together (they are not
+     * grouped by their module since they are only used if the object is moved
+     * from it staticly link edited address).
+     */
+  final Uint32 locreloff; /* offset to local relocation entries (uint32_t) */
+  final Uint32 nlocrel; /* number of local relocation entries (uint32_t) */
+
+  MachODysymtabCommand(
+    Uint32 cmdsize,
+    this.ilocalsym,
+    this.nlocalsym,
+    this.iextdefsym,
+    this.nextdefsym,
+    this.iundefsym,
+    this.nundefsym,
+    this.tocoff,
+    this.ntoc,
+    this.modtaboff,
+    this.nmodtab,
+    this.extrefsymoff,
+    this.nextrefsyms,
+    this.indirectsymoff,
+    this.nindirectsyms,
+    this.extreloff,
+    this.nextrel,
+    this.locreloff,
+    this.nlocrel,
+  ) : super(
+          MachOConstants.LC_DYSYMTAB,
+          cmdsize,
+        );
+
+  @override
+  MachODysymtabCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(ilocalsym);
+    stream.writeUint32(nlocalsym);
+    stream.writeUint32(iextdefsym);
+    stream.writeUint32(nextdefsym);
+    stream.writeUint32(iundefsym);
+    stream.writeUint32(nundefsym);
+    stream.writeUint32(tocoff);
+    stream.writeUint32(ntoc);
+    stream.writeUint32(modtaboff);
+    stream.writeUint32(nmodtab);
+    stream.writeUint32(extrefsymoff);
+    stream.writeUint32(nextrefsyms);
+    stream.writeUint32(indirectsymoff);
+    stream.writeUint32(nindirectsyms);
+    stream.writeUint32(extreloff);
+    stream.writeUint32(nextrel);
+    stream.writeUint32(locreloff);
+    stream.writeUint32(nlocrel);
+  }
+}
+
+/* a table of contents entry */
+class MachODylibTableOfContents {
+  final Uint32
+      symbol_index; /* the defined external symb(uint32_t) ol
+				   (index into the symbol table) */
+  final Uint32
+      module_index; /* index into the module table this symb(uint32_t) ol
+				   is defined in */
+
+  MachODylibTableOfContents(
+    this.symbol_index,
+    this.module_index,
+  );
+}
+
+/* a module table entry */
+class MachODylibModule {
+  final Uint32
+      module_name; /* the module name (index into string table) (uint32_t) */
+
+  final Uint32
+      iextdefsym; /* index into externally defined symbols (uint32_t) */
+  final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+  final Uint32 irefsym; /* index into reference symbol table (uint32_t) */
+  final Uint32
+      nrefsym; /* number of reference symbol table entries (uint32_t) */
+  final Uint32 ilocalsym; /* index into symbols for local symbols (uint32_t) */
+  final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+  final Uint32 iextrel; /* index into external relocation entries (uint32_t) */
+  final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+  final Uint32
+      iinit_iterm; /* low 16 bits are the index into the in(uint32_t) it
+				   section, high 16 bits are the index into
+			           the term section */
+  final Uint32
+      ninit_nterm; /* low 16 bits are the number of init secti(uint32_t) on
+				   entries, high 16 bits are the number of
+				   term section entries */
+
+  final Uint32 /* for this module address of the start of (uint32_t) */
+      objc_module_info_addr; /*  the (__OBJC,__module_info) section */
+  final Uint32 /* for this module size of (uint32_t) */
+      objc_module_info_size; /*  the (__OBJC,__module_info) section */
+
+  MachODylibModule(
+    this.module_name,
+    this.iextdefsym,
+    this.nextdefsym,
+    this.irefsym,
+    this.nrefsym,
+    this.ilocalsym,
+    this.nlocalsym,
+    this.iextrel,
+    this.nextrel,
+    this.iinit_iterm,
+    this.ninit_nterm,
+    this.objc_module_info_addr,
+    this.objc_module_info_size,
+  );
+}
+
+/* a 64-bit module table entry */
+class MachODylibModule64 {
+  final Uint32
+      module_name; /* the module name (index into string table) (uint32_t) */
+
+  final Uint32
+      iextdefsym; /* index into externally defined symbols (uint32_t) */
+  final Uint32 nextdefsym; /* number of externally defined symbols (uint32_t) */
+  final Uint32 irefsym; /* index into reference symbol table (uint32_t) */
+  final Uint32
+      nrefsym; /* number of reference symbol table entries (uint32_t) */
+  final Uint32 ilocalsym; /* index into symbols for local symbols (uint32_t) */
+  final Uint32 nlocalsym; /* number of local symbols (uint32_t) */
+
+  final Uint32 iextrel; /* index into external relocation entries (uint32_t) */
+  final Uint32 nextrel; /* number of external relocation entries (uint32_t) */
+
+  final Uint32
+      iinit_iterm; /* low 16 bits are the index into the in(uint32_t) it
+				   section, high 16 bits are the index into
+				   the term section */
+  final Uint32
+      ninit_nterm; /* low 16 bits are the number of init secti(uint32_t) on
+				  entries, high 16 bits are the number of
+				  term section entries */
+
+  final Uint32 /* for this module size of (uint32_t) */
+      objc_module_info_size; /*  the (__OBJC,__module_info) section */
+  final Uint64 /* for this module address of the start of (uint64_t) */
+      objc_module_info_addr; /*  the (__OBJC,__module_info) section */
+
+  MachODylibModule64(
+    this.module_name,
+    this.iextdefsym,
+    this.nextdefsym,
+    this.irefsym,
+    this.nrefsym,
+    this.ilocalsym,
+    this.nlocalsym,
+    this.iextrel,
+    this.nextrel,
+    this.iinit_iterm,
+    this.ninit_nterm,
+    this.objc_module_info_size,
+    this.objc_module_info_addr,
+  );
+}
+
+/*
+ * The entries in the reference symbol table are used when loading the module
+ * (both by the static and dynamic link editors) and if the module is unloaded
+ * or replaced.  Therefore all external symbols (defined and undefined) are
+ * listed in the module's reference table.  The flags describe the type of
+ * reference that is being made.  The constants for the flags are defined in
+ * <mach-o/nlist.h> as they are also used for symbol table entries.
+ */
+class MachODylibReference {
+  final Uint32 isym; //:24,		/* index into the symbol table (uint32_t) */
+  final Uint32 flags; //:8;	/* flags to indicate the type of reference */
+
+  MachODylibReference(Uint32 value)
+      : isym = value & Uint32(0xffffff),
+        flags = value >> Uint32(24);
+}
+
+/*
+ * The twolevel_hints_command contains the offset and number of hints in the
+ * two-level namespace lookup hints table.
+ */
+class MachOTwolevelHintsCommand
+    extends IMachOLoadCommand<MachOTwolevelHintsCommand> {
+  final Uint32 offset; /* offset to the hint table (uint32_t) */
+  final Uint32 nhints; /* number of hints in the hint table (uint32_t) */
+
+  MachOTwolevelHintsCommand(
+    Uint32 cmdsize,
+    this.offset,
+    this.nhints,
+  ) : super(
+          MachOConstants.LC_TWOLEVEL_HINTS,
+          cmdsize,
+        );
+
+  @override
+  MachOTwolevelHintsCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(offset);
+    stream.writeUint32(nhints);
+  }
+}
+
+/*
+ * The entries in the two-level namespace lookup hints table are twolevel_hint
+ * structs.  These provide hints to the dynamic link editor where to start
+ * looking for an undefined symbol in a two-level namespace image.  The
+ * isub_image field is an index into the sub-images (sub-frameworks and
+ * sub-umbrellas list) that made up the two-level image that the undefined
+ * symbol was found in when it was built by the static link editor.  If
+ * isub-image is 0 the the symbol is expected to be defined in library and not
+ * in the sub-images.  If isub-image is non-zero it is an index into the array
+ * of sub-images for the umbrella with the first index in the sub-images being
+ * 1. The array of sub-images is the ordered list of sub-images of the umbrella
+ * that would be searched for a symbol that has the umbrella recorded as its
+ * primary library.  The table of contents index is an index into the
+ * library's table of contents.  This is used as the starting point of the
+ * binary search or a directed linear search.
+ */
+class MachOTwolevelHint {
+  final int isub_image; //:8,	/* index into the sub images */
+  final int itoc; //:24;	/* index into the table of contents */
+
+  MachOTwolevelHint(int value)
+      : isub_image = value & 0xff,
+        itoc = value >> 8;
+}
+
+/*
+ * The prebind_cksum_command contains the value of the original check sum for
+ * prebound files or zero.  When a prebound file is first created or modified
+ * for other than updating its prebinding information the value of the check sum
+ * is set to zero.  When the file has it prebinding re-done and if the value of
+ * the check sum is zero the original check sum is calculated and stored in
+ * cksum field of this load command in the output file.  If when the prebinding
+ * is re-done and the cksum field is non-zero it is left unchanged from the
+ * input file.
+ */
+class MachOPrebindCksumCommand
+    extends IMachOLoadCommand<MachOPrebindCksumCommand> {
+  final Uint32 cksum; /* the check sum or zero (uint32_t) */
+
+  MachOPrebindCksumCommand(
+    Uint32 cmdsize,
+    this.cksum,
+  ) : super(
+          MachOConstants.LC_PREBIND_CKSUM,
+          cmdsize,
+        );
+
+  @override
+  MachOPrebindCksumCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(cksum);
+  }
+}
+
+/*
+ * The uuid load command contains a single 128-bit unique random number that
+ * identifies an object produced by the static link editor.
+ */
+class MachOUuidCommand extends IMachOLoadCommand<MachOUuidCommand> {
+  final Uint8List uuid; //[16];	/* the 128-bit uuid */
+
+  MachOUuidCommand(
+    Uint32 cmdsize,
+    this.uuid,
+  ) : super(
+          MachOConstants.LC_UUID,
+          cmdsize,
+        );
+
+  @override
+  MachOUuidCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeFromSync(uuid);
+  }
+}
+
+/*
+ * The rpath_command contains a path which at runtime should be added to
+ * the current run path used to find @rpath prefixed dylibs.
+ */
+class MachORpathCommand extends IMachOLoadCommand<MachORpathCommand> {
+  final MachOStr path; /* path to add to run path */
+
+  MachORpathCommand(
+    Uint32 cmdsize,
+    this.path,
+  ) : super(
+          MachOConstants.LC_RPATH,
+          cmdsize,
+        );
+
+  @override
+  MachORpathCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    path.writeContentsSync(stream);
+  }
+}
+
+/*
+ * The linkedit_data_command contains the offsets and sizes of a blob
+ * of data in the __LINKEDIT segment.
+ */
+class MachOLinkeditDataCommand
+    extends IMachOLoadCommand<MachOLinkeditDataCommand> {
+  final Uint32
+      dataoff; /* file offset of data in __LINKEDIT segment (uint32_t) */
+  final Uint32
+      datasize; /* file size of data in __LINKEDIT segment  (uint32_t) */
+
+  MachOLinkeditDataCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    this.dataoff,
+    this.datasize,
+  ) : super(cmd, cmdsize) {
+    if (this.cmd != MachOConstants.LC_CODE_SIGNATURE &&
+        this.cmd != MachOConstants.LC_SEGMENT_SPLIT_INFO &&
+        this.cmd != MachOConstants.LC_FUNCTION_STARTS &&
+        this.cmd != MachOConstants.LC_DATA_IN_CODE &&
+        this.cmd != MachOConstants.LC_DYLIB_CODE_SIGN_DRS) {
+      throw ArgumentError("cmd was not one of LC_CODE_SIGNATURE "
+          "(${MachOConstants.LC_CODE_SIGNATURE}), LC_SEGMENT_SPLIT_INFO "
+          "(${MachOConstants.LC_SEGMENT_SPLIT_INFO}), LC_FUNCTION_STARTS "
+          "(${MachOConstants.LC_FUNCTION_STARTS}), LC_DATA_IN_CODE "
+          "(${MachOConstants.LC_DATA_IN_CODE}), LC_DYLIB_CODE_SIGN_DRS "
+          "(${MachOConstants.LC_DYLIB_CODE_SIGN_DRS}): $cmd");
+    }
+  }
+
+  @override
+  MachOLinkeditDataCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(dataoff);
+    stream.writeUint32(datasize);
+  }
+}
+
+/*
+ * The encryption_info_command contains the file offset and size of an
+ * of an encrypted segment.
+ */
+class MachOEncryptionInfoCommand
+    extends IMachOLoadCommand<MachOEncryptionInfoCommand> {
+  final Uint32 cryptoff; /* file offset of encrypted range (uint32_t) */
+  final Uint32 cryptsize; /* file size of encrypted range (uint32_t) */
+  final Uint32
+      cryptid; /* which enryption syste(uint32_t) m,
+				   0 means not-encrypted yet */
+
+  MachOEncryptionInfoCommand(
+    Uint32 cmdsize,
+    this.cryptoff,
+    this.cryptsize,
+    this.cryptid,
+  ) : super(
+          MachOConstants.LC_ENCRYPTION_INFO,
+          cmdsize,
+        );
+
+  @override
+  MachOEncryptionInfoCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(cryptoff);
+    stream.writeUint32(cryptsize);
+    stream.writeUint32(cryptid);
+  }
+}
+
+/*
+ * The version_min_command contains the min OS version on which this
+ * binary was built to run.
+ */
+class MachOVersionMinCommand extends IMachOLoadCommand<MachOVersionMinCommand> {
+  final Uint32 version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz (uint32_t) */
+  final Uint32 sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz (uint32_t) */
+
+  MachOVersionMinCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    this.version,
+    this.sdk,
+  ) : super(cmd, cmdsize) {
+    if (this.cmd != MachOConstants.LC_VERSION_MIN_MACOSX &&
+        this.cmd != MachOConstants.LC_VERSION_MIN_IPHONEOS) {
+      throw ArgumentError("cmd was not one of: LC_VERSION_MIN_MACOSX "
+          "(${MachOConstants.LC_VERSION_MIN_MACOSX}), LC_VERSION_MIN_IPHONEOS "
+          "(${MachOConstants.LC_VERSION_MIN_IPHONEOS}): $cmd");
+    }
+  }
+
+  @override
+  MachOVersionMinCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(version);
+    stream.writeUint32(sdk);
+  }
+}
+
+/*
+ * The dyld_info_command contains the file offsets and sizes of
+ * the new compressed form of the information dyld needs to
+ * load the image.  This information is used by dyld on Mac OS X
+ * 10.6 and later.  All information pointed to by this command
+ * is encoded using byte streams, so no endian swapping is needed
+ * to interpret it.
+ */
+class MachODyldInfoCommand extends IMachOLoadCommand<MachODyldInfoCommand> {
+  /*
+     * Dyld rebases an image whenever dyld loads it at an address different
+     * from its preferred address.  The rebase information is a stream
+     * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_.
+     * Conceptually the rebase information is a table of tuples:
+     *    <seg-index, seg-offset, type>
+     * The opcodes are a compressed way to encode the table by only
+     * encoding when a column changes.  In addition simple patterns
+     * like "every n'th offset for m times" can be encoded in a few
+     * bytes.
+     */
+  final Uint32 rebase_off; /* file offset to rebase info  (uint32_t) */
+  final Uint32 rebase_size; /* size of rebase info   (uint32_t) */
+
+  /*
+  * Dyld binds an image during the loading process, if the image
+  * requires any pointers to be initialized to symbols in other images.
+  * The bind information is a stream of byte sized
+  * opcodes whose symbolic names start with BIND_OPCODE_.
+  * Conceptually the bind information is a table of tuples:
+  *   <seg-index, seg-offset, type, symbol-library-ordinal, symbol-name, addend>
+  * The opcodes are a compressed way to encode the table by only
+  * encoding when a column changes.  In addition simple patterns
+  * like for runs of pointers initialzed to the same value can be
+  * encoded in a few bytes.
+  */
+  final Uint32 bind_off; /* file offset to binding info   (uint32_t) */
+  final Uint32 bind_size; /* size of binding info  (uint32_t) */
+
+  /*
+     * Some C++ programs require dyld to unique symbols so that all
+     * images in the process use the same copy of some code/data.
+     * This step is done after binding. The content of the weak_bind
+     * info is an opcode stream like the bind_info.  But it is sorted
+     * alphabetically by symbol name.  This enable dyld to walk
+     * all images with weak binding information in order and look
+     * for collisions.  If there are no collisions, dyld does
+     * no updating.  That means that some fixups are also encoded
+     * in the bind_info.  For instance, all calls to "operator new"
+     * are first bound to libstdc++.dylib using the information
+     * in bind_info.  Then if some image overrides operator new
+     * that is detected when the weak_bind information is processed
+     * and the call to operator new is then rebound.
+     */
+  final Uint32
+      weak_bind_off; /* file offset to weak binding info   (uint32_t) */
+  final Uint32 weak_bind_size; /* size of weak binding info  (uint32_t) */
+
+  /*
+     * Some uses of external symbols do not need to be bound immediately.
+     * Instead they can be lazily bound on first use.  The lazy_bind
+     * are contains a stream of BIND opcodes to bind all lazy symbols.
+     * Normal use is that dyld ignores the lazy_bind section when
+     * loading an image.  Instead the static linker arranged for the
+     * lazy pointer to initially point to a helper function which
+     * pushes the offset into the lazy_bind area for the symbol
+     * needing to be bound, then jumps to dyld which simply adds
+     * the offset to lazy_bind_off to get the information on what
+     * to bind.
+     */
+  final Uint32 lazy_bind_off; /* file offset to lazy binding info (uint32_t) */
+  final Uint32 lazy_bind_size; /* size of lazy binding infs (uint32_t) */
+
+  /*
+     * The symbols exported by a dylib are encoded in a trie.  This
+     * is a compact representation that factors out common prefixes.
+     * It also reduces LINKEDIT pages in RAM because it encodes all
+     * information (name, address, flags) in one small, contiguous range.
+     * The export area is a stream of nodes.  The first node sequentially
+     * is the start node for the trie.
+     *
+     * Nodes for a symbol start with a uleb128 that is the length of
+     * the exported symbol information for the string so far.
+     * If there is no exported symbol, the node starts with a zero byte.
+     * If there is exported info, it follows the length.
+	 *
+	 * First is a uleb128 containing flags. Normally, it is followed by
+     * a uleb128 encoded offset which is location of the content named
+     * by the symbol from the mach_header for the image.  If the flags
+     * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is
+     * a uleb128 encoded library ordinal, then a zero terminated
+     * UTF8 string.  If the string is zero length, then the symbol
+     * is re-export from the specified dylib with the same name.
+	 * If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following
+	 * the flags is two uleb128s: the stub offset and the resolver offset.
+	 * The stub is used by non-lazy pointers.  The resolver is used
+	 * by lazy pointers and must be called to get the actual address to use.
+     *
+     * After the optional exported symbol information is a byte of
+     * how many edges (0-255) that this node has leaving it,
+     * followed by each edge.
+     * Each edge is a zero terminated UTF8 of the addition chars
+     * in the symbol, followed by a uleb128 offset for the node that
+     * edge points to.
+     *
+     */
+  final Uint32 export_off; /* file offset to lazy binding info (uint32_t) */
+  final Uint32 export_size; /* size of lazy binding infs (uint32_t) */
+
+  MachODyldInfoCommand(
+    Uint32 cmd,
+    Uint32 cmdsize,
+    this.rebase_off,
+    this.rebase_size,
+    this.bind_off,
+    this.bind_size,
+    this.weak_bind_off,
+    this.weak_bind_size,
+    this.lazy_bind_off,
+    this.lazy_bind_size,
+    this.export_off,
+    this.export_size,
+  ) : super(
+          cmd,
+          cmdsize,
+        ) {
+    if (this.cmd != MachOConstants.LC_DYLD_INFO &&
+        this.cmd != MachOConstants.LC_DYLD_INFO_ONLY) {
+      throw ArgumentError(
+          "cmd was not one of LC_DYLD_INFO (${MachOConstants.LC_DYLD_INFO}), "
+          "LC_DYLD_INFO_ONLY (${MachOConstants.LC_DYLD_INFO_ONLY}): $cmd");
+    }
+  }
+
+  @override
+  MachODyldInfoCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(rebase_off);
+    stream.writeUint32(rebase_size);
+    stream.writeUint32(bind_off);
+    stream.writeUint32(bind_size);
+    stream.writeUint32(weak_bind_off);
+    stream.writeUint32(weak_bind_size);
+    stream.writeUint32(lazy_bind_off);
+    stream.writeUint32(lazy_bind_size);
+    stream.writeUint32(export_off);
+    stream.writeUint32(export_size);
+  }
+}
+
+/*
+ * The symseg_command contains the offset and size of the GNU style
+ * symbol table information as described in the header file <symseg.h>.
+ * The symbol roots of the symbol segments must also be aligned properly
+ * in the file.  So the requirement of keeping the offsets aligned to a
+ * multiple of a 4 bytes translates to the length field of the symbol
+ * roots also being a multiple of a long.  Also the padding must again be
+ * zeroed. (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOSymsegCommand extends IMachOLoadCommand<MachOSymsegCommand> {
+  final Uint32 offset; /* symbol segment offset (uint32_t) */
+  final Uint32 size; /* symbol segment size in bytes (uint32_t) */
+
+  MachOSymsegCommand(
+    Uint32 cmdsize,
+    this.offset,
+    this.size,
+  ) : super(
+          MachOConstants.LC_SYMSEG,
+          cmdsize,
+        );
+
+  @override
+  MachOSymsegCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint32(offset);
+    stream.writeUint32(size);
+  }
+}
+
+/*
+ * The ident_command contains a free format string table following the
+ * ident_command structure.  The strings are null terminated and the size of
+ * the command is padded out with zero bytes to a multiple of 4 bytes/
+ * (THIS IS OBSOLETE and no longer supported).
+ */
+class MachOIdentCommand extends IMachOLoadCommand<MachOIdentCommand> {
+  MachOIdentCommand(
+    Uint32 cmdsize,
+  ) : super(
+          MachOConstants.LC_IDENT,
+          cmdsize,
+        );
+
+  @override
+  MachOIdentCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {}
+}
+
+/*
+ * The fvmfile_command contains a reference to a file to be loaded at the
+ * specified virtual address.  (Presently, this command is reserved for
+ * internal use.  The kernel ignores this command when loading a program into
+ * memory).
+ */
+class MachOFvmfileCommand extends IMachOLoadCommand<MachOFvmfileCommand> {
+  final MachOStr name; /* files pathname */
+  final Uint32 header_addr; /* files virtual address (uint32_t) */
+
+  MachOFvmfileCommand(
+    Uint32 cmdsize,
+    this.name,
+    this.header_addr,
+  ) : super(
+          MachOConstants.LC_FVMFILE,
+          cmdsize,
+        );
+
+  @override
+  MachOFvmfileCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    name.writeContentsSync(stream);
+    stream.writeUint32(header_addr);
+  }
+}
+
+/*
+ * The entry_point_command is a replacement for thread_command.
+ * It is used for main executables to specify the location (file offset)
+ * of main().  If -stack_size was used at link time, the stacksize
+ * field will contain the stack size need for the main thread.
+ */
+class MachOEntryPointCommand extends IMachOLoadCommand<MachOEntryPointCommand> {
+  final Uint64 entryoff; /* file (__TEXT) offset of main (uint64_t)() */
+  final Uint64 stacksize; /* if not zero, initial stack size (uint64_t) */
+
+  MachOEntryPointCommand(
+    Uint32 cmdsize,
+    this.entryoff,
+    this.stacksize,
+  ) : super(
+          MachOConstants.LC_MAIN,
+          cmdsize,
+        );
+
+  @override
+  MachOEntryPointCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint64(entryoff);
+    stream.writeUint64(stacksize);
+  }
+}
+
+/*
+ * The source_version_command is an optional load command containing
+ * the version of the sources used to build the binary.
+ */
+class MachOSourceVersionCommand
+    extends IMachOLoadCommand<MachOSourceVersionCommand> {
+  final Uint64 version; /* A.B.C.D.E packed as a24.b10.c10.d10.e10 (uint64_t) */
+
+  MachOSourceVersionCommand(
+    Uint32 cmdsize,
+    this.version,
+  ) : super(
+          MachOConstants.LC_SOURCE_VERSION,
+          cmdsize,
+        );
+
+  @override
+  MachOSourceVersionCommand asType() => this;
+
+  @override
+  void writeContentsSync(RandomAccessFile stream) {
+    stream.writeUint64(version);
+  }
+}
+
+/*
+ * The LC_DATA_IN_CODE load commands uses a linkedit_data_command
+ * to point to an array of data_in_code_entry entries. Each entry
+ * describes a range of data in a code section.  This load command
+ * is only used in final linked images.
+ */
+class MachODataInCodeEntry {
+  final Uint32 offset; /* from mach_header to start of data range(uint32_t) */
+  final Uint16 length; /* number of bytes in data range (uint16_t) */
+  final Uint16 kind; /* a DICE_KIND_* value  (uint16_t) */
+
+  MachODataInCodeEntry(
+    this.offset,
+    this.length,
+    this.kind,
+  );
+}
+
+/*
+ * Sections of type S_THREAD_LOCAL_VARIABLES contain an array
+ * of tlv_descriptor structures.
+ */
+// class MachOTlvDescriptor {
+// 	void*		(*thunk)(struct tlv_descriptor*);
+// 	unsigned long	key;
+// 	unsigned long	offset;
+
+// 	MachOTlvDescriptor(
+//     void*		(*thunk)(struct tlv_descriptor*);
+// 	unsigned long	key;
+// 	unsigned long	offset;
+// );
+
+// }
+
+class MachOConstants {
+  /* Constant for the magic field of the mach_header (32-bit architectures) */
+  static const Uint32 MH_MAGIC = Uint32(0xfeedface); /* the mach magic number */
+  static const Uint32 MH_CIGAM = Uint32(0xcefaedfe); /* NXSwapInt(MH_MAGIC) */
+
+  /* Constant for the magic field of the mach_header_64 (64-bit architectures) */
+  static const Uint32 MH_MAGIC_64 =
+      Uint32(0xfeedfacf); /* the 64-bit mach magic number */
+  static const Uint32 MH_CIGAM_64 =
+      Uint32(0xcffaedfe); /* NXSwapInt(MH_MAGIC_64) */
+
+  /*
+  * After MacOS X 10.1 when a new load command is added that is required to be
+  * understood by the dynamic linker for the image to execute properly the
+  * LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic
+  * linker sees such a load command it it does not understand will issue a
+  * "unknown load command required for execution" error and refuse to use the
+  * image.  Other load commands without this bit that are not understood will
+  * simply be ignored.
+  */
+  static const Uint32 LC_REQ_DYLD = Uint32(0x80000000);
+  // This one is a convenience so we can define other constants in this class as
+  // actual const.
+  static const int _LC_REQ_DYLD = 0x80000000;
+
+  /*; Constants for the cmd field of all load commands, the type */
+  static const Uint32 LC_SEGMENT =
+      Uint32(0x1); /* segment of this file to be mapped */
+  static const Uint32 LC_SYMTAB =
+      Uint32(0x2); /* link-edit stab symbol table info */
+  static const Uint32 LC_SYMSEG =
+      Uint32(0x3); /* link-edit gdb symbol table info (obsolete) */
+  static const Uint32 LC_THREAD = Uint32(0x4); /* thread */
+  static const Uint32 LC_UNIXTHREAD =
+      Uint32(0x5); /* unix thread (includes a stack) */
+  static const Uint32 LC_LOADFVMLIB =
+      Uint32(0x6); /* load a specified fixed VM shared library */
+  static const Uint32 LC_IDFVMLIB =
+      Uint32(0x7); /* fixed VM shared library identification */
+  static const Uint32 LC_IDENT =
+      Uint32(0x8); /* object identification info (obsolete) */
+  static const Uint32 LC_FVMFILE =
+      Uint32(0x9); /* fixed VM file inclusion (internal use) */
+  static const Uint32 LC_PREPAGE =
+      Uint32(0xa); /* prepage command (internal use) */
+  static const Uint32 LC_DYSYMTAB =
+      Uint32(0xb); /* dynamic link-edit symbol table info */
+  static const Uint32 LC_LOAD_DYLIB =
+      Uint32(0xc); /* load a dynamically linked shared library */
+  static const Uint32 LC_ID_DYLIB =
+      Uint32(0xd); /* dynamically linked shared lib ident */
+  static const Uint32 LC_LOAD_DYLINKER =
+      Uint32(0xe); /* load a dynamic linker */
+  static const Uint32 LC_ID_DYLINKER =
+      Uint32(0xf); /* dynamic linker identification */
+  static const Uint32 LC_PREBOUND_DYLIB =
+      Uint32(0x10); /* modules prebound for a dynamically */
+  /*  linked shared library */
+  static const Uint32 LC_ROUTINES = Uint32(0x11); /* image routines */
+  static const Uint32 LC_SUB_FRAMEWORK = Uint32(0x12); /* sub framework */
+  static const Uint32 LC_SUB_UMBRELLA = Uint32(0x13); /* sub umbrella */
+  static const Uint32 LC_SUB_CLIENT = Uint32(0x14); /* sub client */
+  static const Uint32 LC_SUB_LIBRARY = Uint32(0x15); /* sub library */
+  static const Uint32 LC_TWOLEVEL_HINTS =
+      Uint32(0x16); /* two-level namespace lookup hints */
+  static const Uint32 LC_PREBIND_CKSUM = Uint32(0x17); /* prebind checksum */
+
+  /*
+  * load a dynamically linked shared library that is allowed to be missing
+  * (all symbols are weak imported).
+  */
+  static const Uint32 LC_LOAD_WEAK_DYLIB = Uint32(0x18 | _LC_REQ_DYLD);
+
+  static const Uint32 LC_SEGMENT_64 = Uint32(0x19);
+  /* 64-bit segment of this file to be
+	mapped */
+  static const Uint32 LC_ROUTINES_64 = Uint32(0x1a); /* 64-bit image routines */
+  static const Uint32 LC_UUID = Uint32(0x1b); /* the uuid */
+  static const Uint32 LC_RPATH =
+      Uint32(0x1c | _LC_REQ_DYLD); /* runpath additions */
+  static const Uint32 LC_CODE_SIGNATURE =
+      Uint32(0x1d); /* local of code signature */
+  static const Uint32 LC_SEGMENT_SPLIT_INFO =
+      Uint32(0x1e); /* local of info to split segments */
+  static const Uint32 LC_REEXPORT_DYLIB =
+      Uint32(0x1f | _LC_REQ_DYLD); /* load and re-export dylib */
+  static const Uint32 LC_LAZY_LOAD_DYLIB =
+      Uint32(0x20); /* delay load of dylib until first use */
+  static const Uint32 LC_ENCRYPTION_INFO =
+      Uint32(0x21); /* encrypted segment information */
+  static const Uint32 LC_DYLD_INFO =
+      Uint32(0x22); /* compressed dyld information */
+  static const Uint32 LC_DYLD_INFO_ONLY =
+      Uint32(0x22 | _LC_REQ_DYLD); /* compressed dyld information only */
+  static const Uint32 LC_LOAD_UPWARD_DYLIB =
+      Uint32(0x23 | _LC_REQ_DYLD); /* load upward dylib */
+  static const Uint32 LC_VERSION_MIN_MACOSX =
+      Uint32(0x24); /* build for MacOSX min OS version */
+  static const Uint32 LC_VERSION_MIN_IPHONEOS =
+      Uint32(0x25); /* build for iPhoneOS min OS version */
+  static const Uint32 LC_FUNCTION_STARTS =
+      Uint32(0x26); /* compressed table of function start addresses */
+  static const Uint32 LC_DYLD_ENVIRONMENT = Uint32(0x27);
+  /* string for dyld to treat
+	like environment variable */
+  static const Uint32 LC_MAIN =
+      Uint32(0x28 | _LC_REQ_DYLD); /* replacement for LC_UNIXTHREAD */
+  static const Uint32 LC_DATA_IN_CODE =
+      Uint32(0x29); /* table of non-instructions in __text */
+  static const Uint32 LC_SOURCE_VERSION =
+      Uint32(0x2A); /* source version used to build binary */
+  static const Uint32 LC_DYLIB_CODE_SIGN_DRS =
+      Uint32(0x2B); /* Code signing DRs copied from linked dylibs */
+  static const Uint32 LC_BUILD_VERSION =
+      Uint32(0x32); /* Platform min OS version */
+
+  /* Constants for the flags field of the segment_command */
+
+  /* the file contents for this segment is for the high part of the VM space,
+	the low part is zero filled (for stacks in core files) */
+  static const Uint32 SG_HIGHVM = Uint32(0x1);
+  /* this segment is the VM that is allocated by a fixed VM library, for overlap
+	checking in the link editor */
+  static const Uint32 SG_FVMLIB = Uint32(0x2);
+  /* this segment has nothing that was relocated in it and nothing relocated to
+	it, that is it maybe safely replaced without relocation */
+  static const Uint32 SG_NORELOC = Uint32(0x4);
+  /* This segment is protected.  If the segment starts at file offset 0, the
+	first page of the segment is not protected.  All other pages of the segment
+	are protected. */
+  static const Uint32 SG_PROTECTED_VERSION_1 = Uint32(0x8);
+
+/*
+ * The flags field of a section structure is separated into two parts a section
+ * type and section attributes.  The section types are mutually exclusive (it
+ * can only have one type) but the section attributes are not (it may have more
+ * than one attribute).
+ */
+  static const Uint32 SECTION_TYPE = Uint32(0x000000ff); /* 256 section types */
+  static const Uint32 SECTION_ATTRIBUTES =
+      Uint32(0xffffff00); /*  24 section attributes */
+
+/* Constants for the type of a section */
+  static const Uint32 S_REGULAR = Uint32(0x0); /* regular section */
+  static const Uint32 S_ZEROFILL =
+      Uint32(0x1); /* zero fill on demand section */
+  static const Uint32 S_CSTRING_LITERALS =
+      Uint32(0x2); /* section with only literal C strings*/
+  static const Uint32 S_4BYTE_LITERALS =
+      Uint32(0x3); /* section with only 4 byte literals */
+  static const Uint32 S_8BYTE_LITERALS =
+      Uint32(0x4); /* section with only 8 byte literals */
+  static const Uint32 S_LITERAL_POINTERS =
+      Uint32(0x5); /* section with only pointers to */
+  /*  literals */
+/*
+ * For the two types of symbol pointers sections and the symbol stubs section
+ * they have indirect symbol table entries.  For each of the entries in the
+ * section the indirect symbol table entries, in corresponding order in the
+ * indirect symbol table, start at the index stored in the reserved1 field
+ * of the section structure.  Since the indirect symbol table entries
+ * correspond to the entries in the section the number of indirect symbol table
+ * entries is inferred from the size of the section divided by the size of the
+ * entries in the section.  For symbol pointers sections the size of the entries
+ * in the section is 4 bytes and for symbol stubs sections the byte size of the
+ * stubs is stored in the reserved2 field of the section structure.
+ */
+  static const Uint32 S_NON_LAZY_SYMBOL_POINTERS =
+      Uint32(0x6); /* section with only non-lazy
+						   symbol pointers */
+  static const Uint32 S_LAZY_SYMBOL_POINTERS =
+      Uint32(0x7); /* section with only lazy symbol
+ pointers */
+  static const Uint32 S_SYMBOL_STUBS = Uint32(
+      0x8); /* section with only symbol
+ stubs, byte size of stub in
+ the reserved2 field */
+  static const Uint32 S_MOD_INIT_FUNC_POINTERS =
+      Uint32(0x9); /* section with only function
+ pointers for initialization*/
+  static const Uint32 S_MOD_TERM_FUNC_POINTERS =
+      Uint32(0xa); /* section with only function
+ pointers for termination */
+  static const Uint32 S_COALESCED =
+      Uint32(0xb); /* section contains symbols that
+ are to be coalesced */
+  static const Uint32 S_GB_ZEROFILL = Uint32(
+      0xc); /* zero fill on demand section
+ (that can be larger than 4
+ gigabytes) */
+  static const Uint32 S_INTERPOSING = Uint32(
+      0xd); /* section with only pairs of
+ function pointers for
+ interposing */
+  static const Uint32 S_16BYTE_LITERALS =
+      Uint32(0xe); /* section with only 16 byte
+ literals */
+  static const Uint32 S_DTRACE_DOF =
+      Uint32(0xf); /* section contains
+ DTrace Object Format */
+  static const Uint32 S_LAZY_DYLIB_SYMBOL_POINTERS = Uint32(
+      0x10); /* section with only lazy
+ symbol pointers to lazy
+ loaded dylibs */
+  /*
+ * Section types to support thread local variables
+ */
+  static const Uint32 S_THREAD_LOCAL_REGULAR =
+      Uint32(0x11); /* template of initial
+ values for TLVs */
+  static const Uint32 S_THREAD_LOCAL_ZEROFILL =
+      Uint32(0x12); /* template of initial
+ values for TLVs */
+  static const Uint32 S_THREAD_LOCAL_VARIABLES =
+      Uint32(0x13); /* TLV descriptors */
+  static const Uint32 S_THREAD_LOCAL_VARIABLE_POINTERS =
+      Uint32(0x14); /* pointers to TLV
+ descriptors */
+  static const Uint32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS =
+      Uint32(0x15); /* functions to call
+ to initialize TLV
+ values */
+
+  /*
+ * Constants for the section attributes part of the flags field of a section
+ * structure.
+ */
+  static const Uint32 SECTION_ATTRIBUTES_USR =
+      Uint32(0xff000000); /* User setable attributes */
+  static const Uint32 S_ATTR_PURE_INSTRUCTIONS =
+      Uint32(0x80000000); /* section contains only true
+ machine instructions */
+  static const Uint32 S_ATTR_NO_TOC = Uint32(
+      0x40000000); /* section contains coalesced
+ symbols that are not to be
+ in a ranlib table of
+ contents */
+  static const Uint32 S_ATTR_STRIP_STATIC_SYMS = Uint32(
+      0x20000000); /* ok to strip static symbols
+ in this section in files
+ with the MH_DYLDLINK flag */
+  static const Uint32 S_ATTR_NO_DEAD_STRIP =
+      Uint32(0x10000000); /* no dead stripping */
+  static const Uint32 S_ATTR_LIVE_SUPPORT =
+      Uint32(0x08000000); /* blocks are live if they
+ reference live blocks */
+  static const Uint32 S_ATTR_SELF_MODIFYING_CODE =
+      Uint32(0x04000000); /* Used with i386 code stubs
+ written on by dyld */
+  /*
+ * If a segment contains any sections marked with S_ATTR_DEBUG then all
+ * sections in that segment must have this attribute.  No section other than
+ * a section marked with this attribute may reference the contents of this
+ * section.  A section with this attribute may contain no symbols and must have
+ * a section type S_REGULAR.  The static linker will not copy section contents
+ * from sections with this attribute into its output file.  These sections
+ * generally contain DWARF debugging info.
+ */
+  static const Uint32 S_ATTR_DEBUG = Uint32(0x02000000); /* a debug section */
+  static const Uint32 SECTION_ATTRIBUTES_SYS =
+      Uint32(0x00ffff00); /* system setable attributes */
+  static const Uint32 S_ATTR_SOME_INSTRUCTIONS =
+      Uint32(0x00000400); /* section contains some
+ machine instructions */
+  static const Uint32 S_ATTR_EXT_RELOC =
+      Uint32(0x00000200); /* section has external
+ relocation entries */
+  static const Uint32 S_ATTR_LOC_RELOC =
+      Uint32(0x00000100); /* section has local
+ relocation entries */
+
+  /*
+ * The names of segments and sections in them are mostly meaningless to the
+ * link-editor.  But there are few things to support traditional UNIX
+ * executables that require the link-editor and assembler to use some names
+ * agreed upon by convention.
+ *
+ * The initial protection of the "__TEXT" segment has write protection turned
+ * off (not writeable).
+ *
+ * The link-editor will allocate common symbols at the end of the "__common"
+ * section in the "__DATA" segment.  It will create the section and segment
+ * if needed.
+ */
+
+/* The currently known segment names and the section names in those segments */
+
+  static final String SEG_PAGEZERO =
+      "__PAGEZERO"; /* the pagezero segment which has no */
+  /* protections and catches NULL */
+  /* references for MH_EXECUTE files */
+
+  static final String SEG_TEXT = "__TEXT"; /* the tradition UNIX text segment */
+  static final String SECT_TEXT = "__text"; /* the real text part of the text */
+  /* section no headers, and no padding */
+  static final String SECT_FVMLIB_INIT0 =
+      "__fvmlib_init0"; /* the fvmlib initialization */
+  /*  section */
+  static final String SECT_FVMLIB_INIT1 =
+      "__fvmlib_init1"; /* the section following the */
+  /*  fvmlib initialization */
+  /*  section */
+
+  static final String SEG_DATA = "__DATA"; /* the tradition UNIX data segment */
+  static final String SECT_DATA =
+      "__data"; /* the real initialized data section */
+  /* no padding, no bss overlap */
+  static final String SECT_BSS =
+      "__bss"; /* the real uninitialized data section*/
+  /* no padding */
+  static final String SECT_COMMON =
+      "__common"; /* the section common symbols are */
+  /* allocated in by the link editor */
+
+  static final String SEG_OBJC = "__OBJC"; /* objective-C runtime segment */
+  static final String SECT_OBJC_SYMBOLS = "__symbol_table"; /* symbol table */
+  static final String SECT_OBJC_MODULES =
+      "__module_info"; /* module information */
+  static final String SECT_OBJC_STRINGS = "__selector_strs"; /* string table */
+  static final String SECT_OBJC_REFS = "__selector_refs"; /* string table */
+
+  static final String SEG_ICON = "__ICON"; /* the icon segment */
+  static final String SECT_ICON_HEADER = "__header"; /* the icon headers */
+  static final String SECT_ICON_TIFF = "__tiff"; /* the icons in tiff format */
+
+  static final String SEG_LINKEDIT =
+      "__LINKEDIT"; /* the segment containing all structs */
+  /* created and maintained by the link */
+  /* editor.  Created with -seglinkedit */
+  /* option to ld(1) for MH_EXECUTE and */
+  /* FVMLIB file types only */
+
+  static final String SEG_UNIXSTACK =
+      "__UNIXSTACK"; /* the unix stack segment */
+
+  static final String SEG_IMPORT =
+      "__IMPORT"; /* the segment for the self (dyld) */
+  /* modifing code stubs that has read, */
+  /* write and execute permissions */
+
+  /*
+ * An indirect symbol table entry is simply a 32bit index into the symbol table
+ * to the symbol that the pointer or stub is refering to.  Unless it is for a
+ * non-lazy symbol pointer section for a defined symbol which strip(1) as
+ * removed.  In which case it has the value INDIRECT_SYMBOL_LOCAL.  If the
+ * symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that.
+ */
+  static const Uint32 INDIRECT_SYMBOL_LOCAL = Uint32(0x80000000);
+  static const Uint32 INDIRECT_SYMBOL_ABS = Uint32(0x40000000);
+
+  /*
+ * The following are used to encode rebasing information
+ */
+  static const Uint32 REBASE_TYPE_POINTER = Uint32(1);
+  static const Uint32 REBASE_TYPE_TEXT_ABSOLUTE32 = Uint32(2);
+  static const Uint32 REBASE_TYPE_TEXT_PCREL32 = Uint32(3);
+
+  static const Uint32 REBASE_OPCODE_MASK = Uint32(0xF0);
+  static const Uint32 REBASE_IMMEDIATE_MASK = Uint32(0x0F);
+  static const Uint32 REBASE_OPCODE_DONE = Uint32(0x00);
+  static const Uint32 REBASE_OPCODE_SET_TYPE_IMM = Uint32(0x10);
+  static const Uint32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = Uint32(0x20);
+  static const Uint32 REBASE_OPCODE_ADD_ADDR_ULEB = Uint32(0x30);
+  static const Uint32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = Uint32(0x40);
+  static const Uint32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = Uint32(0x50);
+  static const Uint32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = Uint32(0x60);
+  static const Uint32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = Uint32(0x70);
+  static const Uint32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB =
+      Uint32(0x80);
+
+/*
+ * The following are used to encode binding information
+ */
+  static const Uint32 BIND_TYPE_POINTER = Uint32(1);
+  static const Uint32 BIND_TYPE_TEXT_ABSOLUTE32 = Uint32(2);
+  static const Uint32 BIND_TYPE_TEXT_PCREL32 = Uint32(3);
+
+  static const Uint32 BIND_SPECIAL_DYLIB_SELF = Uint32(0);
+  static const Uint32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = Uint32(-1);
+  static const Uint32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = Uint32(-2);
+
+  static const Uint32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = Uint32(0x1);
+  static const Uint32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = Uint32(0x8);
+
+  static const Uint32 BIND_OPCODE_MASK = Uint32(0xF0);
+  static const Uint32 BIND_IMMEDIATE_MASK = Uint32(0x0F);
+  static const Uint32 BIND_OPCODE_DONE = Uint32(0x00);
+  static const Uint32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = Uint32(0x10);
+  static const Uint32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = Uint32(0x20);
+  static const Uint32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = Uint32(0x30);
+  static const Uint32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = Uint32(0x40);
+  static const Uint32 BIND_OPCODE_SET_TYPE_IMM = Uint32(0x50);
+  static const Uint32 BIND_OPCODE_SET_ADDEND_SLEB = Uint32(0x60);
+  static const Uint32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = Uint32(0x70);
+  static const Uint32 BIND_OPCODE_ADD_ADDR_ULEB = Uint32(0x80);
+  static const Uint32 BIND_OPCODE_DO_BIND = Uint32(0x90);
+  static const Uint32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = Uint32(0xA0);
+  static const Uint32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = Uint32(0xB0);
+  static const Uint32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB =
+      Uint32(0xC0);
+
+/*
+ * The following are used on the flags byte of a terminal node
+ * in the export information.
+ */
+  static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_MASK = Uint32(0x03);
+  static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = Uint32(0x00);
+  static const Uint32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = Uint32(0x01);
+  static const Uint32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = Uint32(0x04);
+  static const Uint32 EXPORT_SYMBOL_FLAGS_REEXPORT = Uint32(0x08);
+  static const Uint32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = Uint32(0x10);
+
+  static const Uint32 DICE_KIND_DATA =
+      Uint32(0x0001); /* L$start$data$...  label */
+  static const Uint32 DICE_KIND_JUMP_TABLE8 =
+      Uint32(0x0002); /* L$start$jt8$...   label */
+  static const Uint32 DICE_KIND_JUMP_TABLE16 =
+      Uint32(0x0003); /* L$start$jt16$...  label */
+  static const Uint32 DICE_KIND_JUMP_TABLE32 =
+      Uint32(0x0004); /* L$start$jt32$...  label */
+  static const Uint32 DICE_KIND_ABS_JUMP_TABLE32 =
+      Uint32(0x0005); /* L$start$jta32$... label */
+
+/*
+ *	Protection values, defined as bits within the vm_prot_t type
+ */
+
+  static const Int32 VM_PROT_NONE = Int32(0x00);
+  static const Int32 VM_PROT_READ = Int32(0x01); /* read permission */
+  static const Int32 VM_PROT_WRITE = Int32(0x02); /* write permission */
+  static const Int32 VM_PROT_EXECUTE = Int32(0x04); /* execute permission */
+
+/*
+ *	The default protection for newly-created virtual memory
+ */
+
+  static final Int32 VM_PROT_DEFAULT = VM_PROT_READ | VM_PROT_WRITE;
+
+/*
+ *	The maximum privileges possible, for parameter checking.
+ */
+
+  static final Int32 VM_PROT_ALL =
+      VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
+
+/*
+ *	An invalid protection value.
+ *	Used only by memory_object_lock_request to indicate no change
+ *	to page locks.  Using -1 here is a bad idea because it
+ *	looks like VM_PROT_ALL and then some.
+ */
+
+  static const Int32 VM_PROT_NO_CHANGE = Int32(0x08);
+
+/*
+ *      When a caller finds that he cannot obtain write permission on a
+ *      mapped entry, the following flag can be used.  The entry will
+ *      be made "needs copy" effectively copying the object (using COW),
+ *      and write permission will be added to the maximum protections
+ *      for the associated entry.
+ */
+
+  static const Int32 VM_PROT_COPY = Int32(0x10);
+
+/*
+ *	Another invalid protection value.
+ *	Used only by memory_object_data_request upon an object
+ *	which has specified a copy_call copy strategy. It is used
+ *	when the kernel wants a page belonging to a copy of the
+ *	object, and is only asking the object as a result of
+ *	following a shadow chain. This solves the race between pages
+ *	being pushed up by the memory manager and the kernel
+ *	walking down the shadow chain.
+ */
+
+  static const Int32 VM_PROT_WANTS_COPY = Int32(0x10);
+}
diff --git a/pkg/dart2native/lib/macho_parser.dart b/pkg/dart2native/lib/macho_parser.dart
new file mode 100644
index 0000000..20deb1d
--- /dev/null
+++ b/pkg/dart2native/lib/macho_parser.dart
@@ -0,0 +1,376 @@
+// 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:math';
+import 'dart:typed_data';
+
+import './macho.dart';
+
+extension ByteReader on RandomAccessFile {
+  Uint32 readUint32() {
+    Uint8List rawBytes = readSync(4);
+    var byteView = ByteData.view(rawBytes.buffer);
+    return Uint32(byteView.getUint32(0, Endian.little));
+  }
+
+  Uint64 readUint64() {
+    Uint8List rawBytes = readSync(8);
+    var byteView = ByteData.view(rawBytes.buffer);
+    return Uint64(byteView.getUint64(0, Endian.little));
+  }
+
+  Int32 readInt32() {
+    Uint8List rawBytes = readSync(4);
+    var byteView = ByteData.view(rawBytes.buffer);
+    return Int32(byteView.getInt32(0, Endian.little));
+  }
+}
+
+class MachOFile {
+  IMachOHeader? header;
+  // The headerMaxOffset is set during parsing based on the maximum offset for
+  // segment offsets. Assuming the header start at byte 0 (that seems to always
+  // be the case), this number represents the total size of the header, which
+  // often includes a significant amount of zero-padding.
+  int headerMaxOffset = 0;
+  // We keep track on whether a code signature was seen so we can recreate it
+  // in the case that the binary has a CD hash that nededs updating.
+  bool hasCodeSignature = false;
+
+  // This wil contain all of the "load commands" in this MachO file. A load
+  // command is really a typed schema that indicates various parts of the MachO
+  // file (e.g. where to find the TEXT and DATA sections).
+  List<IMachOLoadCommand> commands =
+      List<IMachOLoadCommand>.empty(growable: true);
+
+  MachOFile();
+
+  // Returns the number of bytes read from the file.
+  Future<int> loadFromFile(File file) async {
+    // Ensure the file is long enough to contain the magic bytes.
+    final int fileLength = await file.length();
+    if (fileLength < 4) {
+      throw FormatException(
+          "File was not formatted properly. Length was too short: $fileLength");
+    }
+
+    // Read the first 4 bytes to see what type of MachO file this is.
+    var stream = await file.open();
+    var magic = stream.readUint32();
+
+    bool is64Bit = magic == MachOConstants.MH_MAGIC_64 ||
+        magic == MachOConstants.MH_CIGAM_64;
+
+    await stream.setPosition(0);
+
+    // Set the max header offset to the maximum file size so that when we read
+    // in the header we can correctly set the total header size.
+    headerMaxOffset = (1 << 63) - 1;
+
+    header = await _headerFromStream(stream, is64Bit);
+    if (header == null) {
+      throw FormatException(
+          "Could not parse a MachO header from the file: ${file.path}");
+    } else {
+      commands = await _commandsFromStream(stream, header!);
+    }
+
+    return stream.positionSync();
+  }
+
+  Future<MachOSymtabCommand> parseSymtabFromStream(
+      final Uint32 cmdsize, RandomAccessFile stream) async {
+    final symoff = stream.readUint32();
+    final nsyms = stream.readUint32();
+    final stroff = stream.readUint32();
+    final strsize = stream.readUint32();
+
+    return MachOSymtabCommand(cmdsize, symoff, nsyms, stroff, strsize);
+  }
+
+  Future<MachODysymtabCommand> parseDysymtabFromStream(
+      final Uint32 cmdsize, RandomAccessFile stream) async {
+    final ilocalsym = stream.readUint32();
+    final nlocalsym = stream.readUint32();
+    final iextdefsym = stream.readUint32();
+    final nextdefsym = stream.readUint32();
+    final iundefsym = stream.readUint32();
+    final nundefsym = stream.readUint32();
+    final tocoff = stream.readUint32();
+    final ntoc = stream.readUint32();
+    final modtaboff = stream.readUint32();
+    final nmodtab = stream.readUint32();
+    final extrefsymoff = stream.readUint32();
+    final nextrefsyms = stream.readUint32();
+    final indirectsymoff = stream.readUint32();
+    final nindirectsyms = stream.readUint32();
+    final extreloff = stream.readUint32();
+    final nextrel = stream.readUint32();
+    final locreloff = stream.readUint32();
+    final nlocrel = stream.readUint32();
+
+    return MachODysymtabCommand(
+        cmdsize,
+        ilocalsym,
+        nlocalsym,
+        iextdefsym,
+        nextdefsym,
+        iundefsym,
+        nundefsym,
+        tocoff,
+        ntoc,
+        modtaboff,
+        nmodtab,
+        extrefsymoff,
+        nextrefsyms,
+        indirectsymoff,
+        nindirectsyms,
+        extreloff,
+        nextrel,
+        locreloff,
+        nlocrel);
+  }
+
+  Future<MachOLinkeditDataCommand> parseLinkeditDataCommand(
+      final Uint32 cmd, final Uint32 cmdsize, RandomAccessFile stream) async {
+    final dataoff = stream.readUint32();
+    final datasize = stream.readUint32();
+
+    return MachOLinkeditDataCommand(
+      cmd,
+      cmdsize,
+      dataoff,
+      datasize,
+    );
+  }
+
+  Future<MachODyldInfoCommand> parseDyldInfoFromStream(
+      final Uint32 cmd, final Uint32 cmdsize, RandomAccessFile stream) async {
+    // Note that we're relying on the fact that the mirror returns the list of
+    // fields in the same order they're defined ni the class definition.
+
+    final rebaseOff = stream.readUint32();
+    final rebaseSize = stream.readUint32();
+    final bindOff = stream.readUint32();
+    final bindSize = stream.readUint32();
+    final weakBindOff = stream.readUint32();
+    final weakBindSize = stream.readUint32();
+    final lazyBindOff = stream.readUint32();
+    final lazyBindSize = stream.readUint32();
+    final exportOff = stream.readUint32();
+    final exportSize = stream.readUint32();
+
+    return MachODyldInfoCommand(
+        cmd,
+        cmdsize,
+        rebaseOff,
+        rebaseSize,
+        bindOff,
+        bindSize,
+        weakBindOff,
+        weakBindSize,
+        lazyBindOff,
+        lazyBindSize,
+        exportOff,
+        exportSize);
+  }
+
+  Future<MachOSegmentCommand64> parseSegmentCommand64FromStream(
+      final Uint32 cmdsize, RandomAccessFile stream) async {
+    final Uint8List segname = await stream.read(16);
+    final vmaddr = stream.readUint64();
+    final vmsize = stream.readUint64();
+    final fileoff = stream.readUint64();
+    final filesize = stream.readUint64();
+    final maxprot = stream.readInt32();
+    final initprot = stream.readInt32();
+    final nsects = stream.readUint32();
+    final flags = stream.readUint32();
+
+    if (nsects.asInt() == 0 && filesize.asInt() != 0) {
+      headerMaxOffset = min(headerMaxOffset, fileoff.asInt());
+    }
+
+    final sections = List.filled(nsects.asInt(), 0).map((_) {
+      final Uint8List sectname = stream.readSync(16);
+      final Uint8List segname = stream.readSync(16);
+      final addr = stream.readUint64();
+      final size = stream.readUint64();
+      final offset = stream.readUint32();
+      final align = stream.readUint32();
+      final reloff = stream.readUint32();
+      final nreloc = stream.readUint32();
+      final flags = stream.readUint32();
+      final reserved1 = stream.readUint32();
+      final reserved2 = stream.readUint32();
+      final reserved3 = stream.readUint32();
+
+      final notZerofill =
+          (flags & MachOConstants.S_ZEROFILL) != MachOConstants.S_ZEROFILL;
+      if (offset > 0 && size > 0 && notZerofill) {
+        headerMaxOffset = min(headerMaxOffset, offset.asInt());
+      }
+
+      return MachOSection64(sectname, segname, addr, size, offset, align,
+          reloff, nreloc, flags, reserved1, reserved2, reserved3);
+    }).toList();
+
+    return MachOSegmentCommand64(cmdsize, segname, vmaddr, vmsize, fileoff,
+        filesize, maxprot, initprot, nsects, flags, sections);
+  }
+
+  Future<IMachOHeader> _headerFromStream(
+      RandomAccessFile stream, bool is64Bit) async {
+    final magic = stream.readUint32();
+    final cputype = stream.readUint32();
+    final cpusubtype = stream.readUint32();
+    final filetype = stream.readUint32();
+    final ncmds = stream.readUint32();
+    final sizeofcmds = stream.readUint32();
+    final flags = stream.readUint32();
+
+    if (is64Bit) {
+      final reserved = stream.readUint32();
+      return MachOHeader(magic, cputype, cpusubtype, filetype, ncmds,
+          sizeofcmds, flags, reserved);
+    } else {
+      return MachOHeader32(
+          magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags);
+    }
+  }
+
+  void writeLoadCommandToStream(
+      IMachOLoadCommand command, RandomAccessFile stream) {
+    command.writeSync(stream);
+  }
+
+  void writeSync(RandomAccessFile stream) {
+    // Write the header.
+    stream.writeUint32(header!.magic);
+    stream.writeUint32(header!.cputype);
+    stream.writeUint32(header!.cpusubtype);
+    stream.writeUint32(header!.filetype);
+    stream.writeUint32(header!.ncmds);
+    stream.writeUint32(header!.sizeofcmds);
+    stream.writeUint32(header!.flags);
+
+    if (header is MachOHeader) {
+      stream.writeUint32(header!.reserved);
+    }
+
+    // Write all of the commands.
+    commands.forEach((command) {
+      writeLoadCommandToStream(command, stream);
+    });
+
+    // Pad the header according to the offset.
+    final int paddingAmount = headerMaxOffset - stream.positionSync();
+    if (paddingAmount > 0) {
+      stream.writeFromSync(List.filled(paddingAmount, 0));
+    }
+  }
+
+  Future<List<IMachOLoadCommand>> _commandsFromStream(
+      RandomAccessFile stream, IMachOHeader header) async {
+    final loadCommands = List<MachOLoadCommand>.empty(growable: true);
+    for (int i = 0; i < header.ncmds.asInt(); i++) {
+      final cmd = stream.readUint32();
+      final cmdsize = stream.readUint32();
+
+      // We need to read cmdsize bytes to get to the next command definition,
+      // but the cmdsize does includes the 2 bytes we just read (cmd +
+      // cmdsize) so we need to subtract those.
+      await stream
+          .setPosition((await stream.position()) + cmdsize.asInt() - 2 * 4);
+
+      loadCommands.add(MachOLoadCommand(cmd, cmdsize));
+    }
+
+    // Un-read all the bytes we just read.
+    var loadCommandsOffset = loadCommands
+        .map((command) => command.cmdsize)
+        .reduce((value, element) => value + element);
+    await stream
+        .setPosition((await stream.position()) - loadCommandsOffset.asInt());
+
+    final commands = List<IMachOLoadCommand>.empty(growable: true);
+    for (int i = 0; i < header.ncmds.asInt(); i++) {
+      final cmd = stream.readUint32();
+      final cmdsize = stream.readUint32();
+
+      // TODO(sarietta): Handle all MachO load command types. For now, since
+      // this implementation is exclusively being used to handle generating
+      // MacOS-compatible MachO executables for compiled dart scripts, only the
+      // load commands that are currently implemented are strictly necessary. It
+      // may be useful to handle all cases and pull this functionality out to a
+      // separate MachO library.
+      if (cmd == MachOConstants.LC_SEGMENT_64) {
+        commands.add(await parseSegmentCommand64FromStream(cmdsize, stream));
+      } else if (cmd == MachOConstants.LC_DYLD_INFO_ONLY ||
+          cmd == MachOConstants.LC_DYLD_INFO) {
+        commands.add(await parseDyldInfoFromStream(cmd, cmdsize, stream));
+      } else if (cmd == MachOConstants.LC_SYMTAB) {
+        commands.add(await parseSymtabFromStream(cmdsize, stream));
+      } else if (cmd == MachOConstants.LC_DYSYMTAB) {
+        commands.add(await parseDysymtabFromStream(cmdsize, stream));
+      } else if (cmd == MachOConstants.LC_CODE_SIGNATURE ||
+          cmd == MachOConstants.LC_SEGMENT_SPLIT_INFO ||
+          cmd == MachOConstants.LC_FUNCTION_STARTS ||
+          cmd == MachOConstants.LC_DATA_IN_CODE ||
+          cmd == MachOConstants.LC_DYLIB_CODE_SIGN_DRS) {
+        if (cmd == MachOConstants.LC_CODE_SIGNATURE) {
+          hasCodeSignature = true;
+        }
+        commands.add(await parseLinkeditDataCommand(cmd, cmdsize, stream));
+      } else if (cmd == MachOConstants.LC_SEGMENT ||
+          cmd == MachOConstants.LC_SYMSEG ||
+          cmd == MachOConstants.LC_THREAD ||
+          cmd == MachOConstants.LC_UNIXTHREAD ||
+          cmd == MachOConstants.LC_LOADFVMLIB ||
+          cmd == MachOConstants.LC_IDFVMLIB ||
+          cmd == MachOConstants.LC_IDENT ||
+          cmd == MachOConstants.LC_FVMFILE ||
+          cmd == MachOConstants.LC_PREPAGE ||
+          cmd == MachOConstants.LC_LOAD_DYLIB ||
+          cmd == MachOConstants.LC_ID_DYLIB ||
+          cmd == MachOConstants.LC_LOAD_DYLINKER ||
+          cmd == MachOConstants.LC_ID_DYLINKER ||
+          cmd == MachOConstants.LC_PREBOUND_DYLIB ||
+          cmd == MachOConstants.LC_ROUTINES ||
+          cmd == MachOConstants.LC_SUB_FRAMEWORK ||
+          cmd == MachOConstants.LC_SUB_UMBRELLA ||
+          cmd == MachOConstants.LC_SUB_CLIENT ||
+          cmd == MachOConstants.LC_SUB_LIBRARY ||
+          cmd == MachOConstants.LC_TWOLEVEL_HINTS ||
+          cmd == MachOConstants.LC_PREBIND_CKSUM ||
+          cmd == MachOConstants.LC_LOAD_WEAK_DYLIB ||
+          cmd == MachOConstants.LC_ROUTINES_64 ||
+          cmd == MachOConstants.LC_UUID ||
+          cmd == MachOConstants.LC_RPATH ||
+          cmd == MachOConstants.LC_REEXPORT_DYLIB ||
+          cmd == MachOConstants.LC_LAZY_LOAD_DYLIB ||
+          cmd == MachOConstants.LC_ENCRYPTION_INFO ||
+          cmd == MachOConstants.LC_LOAD_UPWARD_DYLIB ||
+          cmd == MachOConstants.LC_VERSION_MIN_MACOSX ||
+          cmd == MachOConstants.LC_VERSION_MIN_IPHONEOS ||
+          cmd == MachOConstants.LC_DYLD_ENVIRONMENT ||
+          cmd == MachOConstants.LC_MAIN ||
+          cmd == MachOConstants.LC_SOURCE_VERSION ||
+          cmd == MachOConstants.LC_BUILD_VERSION) {
+        // cmdsize includes the size of the contents + cmd + cmdsize
+        final contents = await stream.read(cmdsize.asInt() - 2 * 4);
+        commands.add(MachOGenericLoadCommand(cmd, cmdsize, contents));
+      } else {
+        // cmdsize includes the size of the contents + cmd + cmdsize
+        final contents = await stream.read(cmdsize.asInt() - 2 * 4);
+        commands.add(MachOGenericLoadCommand(cmd, cmdsize, contents));
+        final cmdString = "0x${cmd.asInt().toRadixString(16)}";
+        print("Found unknown MachO load command: $cmdString");
+      }
+    }
+
+    return commands;
+  }
+}
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index bac5e34..e5c15b0 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -4,6 +4,8 @@
 
 import 'dart:io';
 
+import 'package:dart2native/macho.dart';
+import 'package:dart2native/macho_parser.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
@@ -21,6 +23,61 @@
 
 void defineCompileTests() {
   final isRunningOnIA32 = Platform.version.contains('ia32');
+
+  if (Platform.isMacOS) {
+    test('Compile exe for MacOS signing', () async {
+      final p = project(mainSrc: '''void main() {}''');
+      final inFile =
+          path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+      final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+      var result = await p.run(
+        [
+          'compile',
+          'exe',
+          '-o',
+          outFile,
+          inFile,
+        ],
+      );
+
+      expect(result.stdout, contains(soundNullSafetyMessage));
+      expect(result.stderr, isEmpty);
+      expect(result.exitCode, 0);
+      expect(File(outFile).existsSync(), true,
+          reason: 'File not found: $outFile');
+
+      // Ensure the file contains the __CUSTOM segment.
+      final machOFile = MachOFile();
+      await machOFile.loadFromFile(File(outFile));
+
+      // Throws an exception (and thus the test fails) if the segment doesn't
+      // exist.
+      machOFile.commands.where((segment) {
+        if (segment.asType() is MachOSegmentCommand64) {
+          final segmentName = (segment as MachOSegmentCommand64).segname;
+          final segmentNameTrimmed = String.fromCharCodes(
+              segmentName.takeWhile((value) => value != 0));
+          return segmentNameTrimmed == '__CUSTOM';
+        } else {
+          return false;
+        }
+      }).first;
+
+      // Ensure that the exe can be signed.
+      final codeSigningProcess = await Process.start('codesign', [
+        '-o',
+        'runtime',
+        '-s',
+        '-',
+        outFile,
+      ]);
+
+      final signingResult = await codeSigningProcess.exitCode;
+      expect(signingResult, 0);
+    }, skip: isRunningOnIA32);
+  }
+
   // *** NOTE ***: These tests *must* be run with the `--use-sdk` option
   // as they depend on a fully built SDK to resolve various snapshot files
   // used by compilation.
@@ -153,9 +210,9 @@
       [],
     );
 
-    expect(result.stdout, contains('I love executables'));
     expect(result.stderr, isEmpty);
     expect(result.exitCode, 0);
+    expect(result.stdout, contains('I love executables'));
   }, skip: isRunningOnIA32);
 
   test('Compile to executable disabled on IA32', () async {
@@ -220,9 +277,9 @@
       [],
     );
 
-    expect(result.stdout, contains('42'));
     expect(result.stderr, isEmpty);
     expect(result.exitCode, 0);
+    expect(result.stdout, contains('42'));
   }, skip: isRunningOnIA32);
 
   test('Compile and run aot snapshot', () async {
@@ -443,9 +500,9 @@
       [],
     );
 
-    expect(result.stdout, contains('sound'));
     expect(result.stderr, isEmpty);
     expect(result.exitCode, 0);
+    expect(result.stdout, contains('sound'));
   }, skip: isRunningOnIA32);
 
   test('Compile and run exe with --no-sound-null-safety', () async {
diff --git a/pkg/dds/lib/devtools_server.dart b/pkg/dds/lib/devtools_server.dart
index 0a267d6..299044c 100644
--- a/pkg/dds/lib/devtools_server.dart
+++ b/pkg/dds/lib/devtools_server.dart
@@ -489,11 +489,12 @@
   }
 
   Future<Map<String, dynamic>> launchDevTools(
-      Map<String, dynamic> params,
-      Uri vmServiceUri,
-      String devToolsUrl,
-      bool headlessMode,
-      bool machineMode) async {
+    Map<String, dynamic> params,
+    Uri vmServiceUri,
+    String devToolsUrl,
+    bool headlessMode,
+    bool machineMode,
+  ) async {
     // First see if we have an existing DevTools client open that we can
     // reuse.
     final canReuse =
@@ -508,10 +509,11 @@
           shouldNotify,
         )) {
       _emitLaunchEvent(
-          reused: true,
-          notified: shouldNotify,
-          pid: null,
-          machineMode: machineMode);
+        reused: true,
+        notified: shouldNotify,
+        pid: null,
+        machineMode: machineMode,
+      );
       return {
         'reused': true,
         'notified': shouldNotify,
@@ -582,7 +584,7 @@
 
   bool _tryReuseExistingDevToolsInstance(
     Uri vmServiceUri,
-    String page,
+    String? page,
     bool notifyUser,
   ) {
     // First try to find a client that's already connected to this VM service,
@@ -591,7 +593,9 @@
         clientManager.findExistingConnectedReusableClient(vmServiceUri);
     if (existingClient != null) {
       try {
-        existingClient.showPage(page);
+        if (page != null) {
+          existingClient.showPage(page);
+        }
         if (notifyUser) {
           existingClient.notify();
         }
@@ -617,7 +621,7 @@
 
   String _buildUriToLaunch(
     Map<String, dynamic> uriParams,
-    page,
+    String? page,
     Uri devToolsUri,
   ) {
     final queryStringNameValues = [];
diff --git a/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
index 5b65d4c..e98f23e 100644
--- a/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
+++ b/pkg/dds/lib/src/devtools/machine_mode_command_handler.dart
@@ -18,7 +18,7 @@
 class MachineModeCommandHandler {
   static const launchDevToolsService = 'launchDevTools';
   static const copyAndCreateDevToolsFile = 'copyAndCreateDevToolsFile';
-  static const restoreDevToolsFile = 'restoreDevToolsFiles';
+  static const restoreDevToolsFile = 'restoreDevToolsFile';
   static const errorLaunchingBrowserCode = 500;
   static const bool machineMode = true;
 
@@ -67,7 +67,7 @@
     _stdinCommandStream.listen((Map<String, dynamic> json) async {
       // ID can be String, int or null
       final dynamic id = json['id'];
-      final Map<String, dynamic> params = json['params'];
+      final Map<String, dynamic> params = json['params'] ?? <String, dynamic>{};
       final method = json['method'];
       switch (method) {
         case 'vm.register':
@@ -200,7 +200,7 @@
   void _handleDevToolsSurvey(dynamic id, Map<String, dynamic> params) {
     _devToolsUsage ??= DevToolsUsage();
     final String surveyRequest = params['surveyRequest'];
-    final String value = params['value'];
+    final String value = params['value'] ?? '';
 
     switch (surveyRequest) {
       case copyAndCreateDevToolsFile:
@@ -212,8 +212,6 @@
             {
               'id': id,
               'result': {
-                // TODO(bkonyi): fix incorrect spelling of "success" here and
-                // below once we figure out the impact of changing this key.
                 'success': true,
               },
             },
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index cb3837e..68a6e019 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -16,7 +16,7 @@
   browser_launcher: ^1.0.0
   collection: ^1.15.0
   dds_service_extensions: ^1.3.0
-  devtools_shared: ^2.3.0
+  devtools_shared: ^2.11.4
   http_multi_server: ^3.0.0
   json_rpc_2: ^3.0.0
   meta: ^1.1.8
diff --git a/pkg/dds/test/common/test_helper.dart b/pkg/dds/test/common/test_helper.dart
index a9473ea..7325d97 100644
--- a/pkg/dds/test/common/test_helper.dart
+++ b/pkg/dds/test/common/test_helper.dart
@@ -61,3 +61,12 @@
   await service.resume(isolate.id!);
   await completer.future;
 }
+
+/// Returns the resolved URI to the pre-built devtools app.
+///
+/// The method caller is responsible for providing the relative [prefix] that
+/// will resolve to the sdk/ directory (e.g. '../../../').
+Uri devtoolsAppUri({required String prefix}) {
+  const pathFromSdkDirectory = 'third_party/devtools/web';
+  return Platform.script.resolve('$prefix$pathFromSdkDirectory');
+}
diff --git a/pkg/dds/test/devtools_observatory_connection_test.dart b/pkg/dds/test/devtools_observatory_connection_test.dart
index 3dcc6cc..ae3eb38 100644
--- a/pkg/dds/test/devtools_observatory_connection_test.dart
+++ b/pkg/dds/test/devtools_observatory_connection_test.dart
@@ -43,9 +43,7 @@
         remoteVmServiceUri,
         devToolsConfiguration: DevToolsConfiguration(
           enable: true,
-          customBuildDirectoryPath: Platform.script.resolve(
-            '../../../third_party/devtools/web',
-          ),
+          customBuildDirectoryPath: devtoolsAppUri(prefix: '../../../'),
         ),
       );
       expect(dds.isRunning, true);
diff --git a/pkg/dds/test/devtools_server/devtools_server_driver.dart b/pkg/dds/test/devtools_server/devtools_server_driver.dart
new file mode 100644
index 0000000..99e9191
--- /dev/null
+++ b/pkg/dds/test/devtools_server/devtools_server_driver.dart
@@ -0,0 +1,93 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+
+const verbose = true;
+
+class DevToolsServerDriver {
+  DevToolsServerDriver._(
+    this._process,
+    this._stdin,
+    Stream<String> _stdout,
+    Stream<String> _stderr,
+  )   : stdout = _convertToMapStream(_stdout),
+        stderr = _stderr.map((line) {
+          _trace('<== STDERR $line');
+          return line;
+        });
+
+  final Process _process;
+  final Stream<Map<String, dynamic>?> stdout;
+  final Stream<String> stderr;
+  final StringSink _stdin;
+
+  void write(Map<String, dynamic> request) {
+    final line = jsonEncode(request);
+    _trace('==> $line');
+    _stdin.writeln(line);
+  }
+
+  static Stream<Map<String, dynamic>?> _convertToMapStream(
+    Stream<String> stream,
+  ) {
+    return stream.map((line) {
+      _trace('<== $line');
+      return line;
+    }).map((line) {
+      try {
+        return jsonDecode(line) as Map<String, dynamic>;
+      } catch (e) {
+        return null;
+      }
+    }).where((item) => item != null);
+  }
+
+  static void _trace(String message) {
+    if (verbose) {
+      print(message);
+    }
+  }
+
+  bool kill() => _process.kill();
+
+  static Future<DevToolsServerDriver> create({
+    int port = 0,
+    int? tryPorts,
+    List<String> additionalArgs = const [],
+  }) async {
+    final script =
+        Platform.script.resolveUri(Uri.parse('./serve_devtools.dart'));
+    final args = [
+      script.path,
+      '--machine',
+      '--port',
+      '$port',
+      ...additionalArgs,
+    ];
+
+    if (tryPorts != null) {
+      args.addAll(['--try-ports', '$tryPorts']);
+    }
+
+    if (useChromeHeadless && headlessModeIsSupported) {
+      args.add('--headless');
+    }
+    final Process process = await Process.start(
+      Platform.resolvedExecutable,
+      args,
+    );
+
+    return DevToolsServerDriver._(
+      process,
+      process.stdin,
+      process.stdout.transform(utf8.decoder).transform(const LineSplitter()),
+      process.stderr.transform(utf8.decoder).transform(const LineSplitter()),
+    );
+  }
+}
diff --git a/pkg/dds/test/devtools_server/devtools_server_test.dart b/pkg/dds/test/devtools_server/devtools_server_test.dart
new file mode 100644
index 0000000..2f34765
--- /dev/null
+++ b/pkg/dds/test/devtools_server/devtools_server_test.dart
@@ -0,0 +1,569 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dds/devtools_server.dart';
+import 'package:dds/src/devtools/machine_mode_command_handler.dart';
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'devtools_server_driver.dart';
+
+late CliAppFixture appFixture;
+late DevToolsServerDriver server;
+final completers = <String, Completer<Map<String, dynamic>>>{};
+
+/// A broadcast stream controller for streaming events from the server.
+late StreamController<Map<String, dynamic>> eventController;
+
+/// A broadcast stream of events from the server.
+///
+/// Listening for "server.started" events on this stream may be unreliable
+/// because it may have occurred before the test starts. Use the
+/// [serverStartedEvent] instead.
+Stream<Map<String, dynamic>> get events => eventController.stream;
+
+/// Completer that signals when the server started event has been received.
+late Completer<Map<String, dynamic>> serverStartedEvent;
+
+final Map<String, String> registeredServices = {};
+
+// A list of PIDs for Chrome instances spawned by tests that should be
+// cleaned up.
+final List<int> browserPids = [];
+
+void main() {
+  late StreamSubscription<String> stderrSub;
+  late StreamSubscription<Map<String, dynamic>?> stdoutSub;
+
+  setUp(() async {
+    serverStartedEvent = Completer<Map<String, dynamic>>();
+    eventController = StreamController<Map<String, dynamic>>.broadcast();
+
+    // Start the command-line server.
+    server = await DevToolsServerDriver.create();
+
+    // Fail tests on any stderr.
+    stderrSub = server.stderr.listen((text) => throw 'STDERR: $text');
+    stdoutSub = server.stdout.listen((map) {
+      if (map!.containsKey('id')) {
+        if (map.containsKey('result')) {
+          completers[map['id']]!.complete(map['result']);
+        } else {
+          completers[map['id']]!.completeError(map['error']);
+        }
+      } else if (map.containsKey('event')) {
+        if (map['event'] == 'server.started') {
+          serverStartedEvent.complete(map);
+        }
+        eventController.add(map);
+      }
+    });
+
+    await serverStartedEvent.future;
+    await _startApp();
+  });
+
+  tearDown(() async {
+    browserPids
+      ..forEach((pid) => Process.killPid(pid, ProcessSignal.sigkill))
+      ..clear();
+    await stdoutSub.cancel();
+    await stderrSub.cancel();
+    server.kill();
+    await appFixture.teardown();
+  });
+
+  test('registers service', () async {
+    final serverResponse = await _send(
+      'vm.register',
+      {'uri': appFixture.serviceUri.toString()},
+    );
+    expect(serverResponse['success'], isTrue);
+
+    // Expect the VM service to see the launchDevTools service registered.
+    expect(registeredServices, contains(DevToolsServer.launchDevToolsService));
+  }, timeout: const Timeout.factor(10));
+
+  test('can bind to next available port', () async {
+    final server1 = await DevToolsServerDriver.create(port: 8855);
+    try {
+      // Wait for the first server to start up and ensure it got the
+      // expected port.
+      final event1 = (await server1.stdout.firstWhere(
+        (map) => map!['event'] == 'server.started',
+      ))!;
+      expect(event1['params']['port'], 8855);
+
+      // Now spawn another requesting the same port and ensure it got the next
+      // port number.
+      final server2 = await DevToolsServerDriver.create(
+        port: 8855,
+        tryPorts: 2,
+      );
+      try {
+        final event2 = (await server2.stdout.firstWhere(
+          (map) => map!['event'] == 'server.started',
+        ))!;
+
+        expect(event2['params']['port'], 8856);
+      } finally {
+        server2.kill();
+      }
+    } finally {
+      server1.kill();
+    }
+  }, timeout: const Timeout.factor(10));
+
+  test('allows embedding without flag', () async {
+    final server = await DevToolsServerDriver.create();
+    final httpClient = HttpClient();
+    late HttpClientResponse resp;
+    try {
+      final startedEvent = (await server.stdout.firstWhere(
+        (map) => map!['event'] == 'server.started',
+      ))!;
+      final host = startedEvent['params']['host'];
+      final port = startedEvent['params']['port'];
+
+      final req = await httpClient.get(host, port, '/');
+      resp = await req.close();
+      expect(resp.headers.value('x-frame-options'), isNull);
+    } finally {
+      httpClient.close();
+      await resp.drain();
+      server.kill();
+    }
+  }, timeout: const Timeout.factor(10));
+
+  test('does not allow embedding with flag', () async {
+    final server = await DevToolsServerDriver.create(
+      additionalArgs: ['--no-allow-embedding'],
+    );
+    final httpClient = HttpClient();
+    late HttpClientResponse resp;
+    try {
+      final startedEvent = (await server.stdout.firstWhere(
+        (map) => map!['event'] == 'server.started',
+      ))!;
+      final host = startedEvent['params']['host'];
+      final port = startedEvent['params']['port'];
+
+      final req = await httpClient.get(host, port, '/');
+      resp = await req.close();
+      expect(resp.headers.value('x-frame-options'), 'SAMEORIGIN');
+    } finally {
+      httpClient.close();
+      await resp.drain();
+      server.kill();
+    }
+  }, timeout: const Timeout.factor(10));
+
+  test('Analytics Survey', () async {
+    var serverResponse = await _send('devTools.survey', {
+      'surveyRequest': 'copyAndCreateDevToolsFile',
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['success'], isTrue);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiSetActiveSurvey,
+      'value': 'Q3-2019',
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['success'], isTrue);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiIncrementSurveyShownCount,
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+    expect(serverResponse['surveyShownCount'], 1);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiIncrementSurveyShownCount,
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+    expect(serverResponse['surveyShownCount'], 2);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiGetSurveyShownCount,
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+    expect(serverResponse['surveyShownCount'], 2);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiGetSurveyActionTaken,
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+    expect(serverResponse['surveyActionTaken'], isFalse);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': apiSetSurveyActionTaken,
+      'value': json.encode(true),
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['activeSurvey'], 'Q3-2019');
+    expect(serverResponse['surveyActionTaken'], isTrue);
+
+    serverResponse = await _send('devTools.survey', {
+      'surveyRequest': MachineModeCommandHandler.restoreDevToolsFile,
+    });
+    expect(serverResponse, isNotNull);
+    expect(serverResponse['success'], isTrue);
+    expect(
+      serverResponse['content'],
+      '{\n'
+      '  \"Q3-2019\": {\n'
+      '    \"surveyActionTaken\": true,\n'
+      '    \"surveyShownCount\": 2\n'
+      '  }\n'
+      '}\n',
+    );
+  }, timeout: const Timeout.factor(10));
+
+  for (final bool useVmService in [true, false]) {
+    group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+      test(
+          'DevTools connects back to server API and registers that it is connected',
+          () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a request to launch DevTools in a browser.
+        await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+        final serverResponse =
+            await _waitForClients(requiredConnectionState: true);
+        expect(serverResponse, isNotNull);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          appFixture.serviceUri.toString(),
+        );
+      }, timeout: const Timeout.factor(10));
+
+      test('can launch on a specific page', () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a request to launch at a certain page.
+        await _sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          page: 'memory',
+        );
+
+        final serverResponse = await _waitForClients(requiredPage: 'memory');
+        expect(serverResponse, isNotNull);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          appFixture.serviceUri.toString(),
+        );
+        expect(serverResponse['clients'][0]['currentPage'], 'memory');
+      }, timeout: const Timeout.factor(10));
+
+      test('can switch page', () async {
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Launch on the memory page and wait for the connection.
+        await _sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          page: 'memory',
+        );
+        await _waitForClients(requiredPage: 'memory');
+
+        // Re-launch, allowing reuse and with a different page.
+        await _sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+          page: 'logging',
+        );
+
+        final serverResponse = await _waitForClients(requiredPage: 'logging');
+        expect(serverResponse, isNotNull);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          appFixture.serviceUri.toString(),
+        );
+        expect(serverResponse['clients'][0]['currentPage'], 'logging');
+      }, timeout: const Timeout.factor(20));
+
+      test('DevTools reports disconnects from a VM', () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a request to launch DevTools in a browser.
+        await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+        // Wait for the DevTools to inform server that it's connected.
+        await _waitForClients(requiredConnectionState: true);
+
+        // Terminate the VM.
+        await appFixture.teardown();
+
+        // Ensure the client is marked as disconnected.
+        final serverResponse =
+            await _waitForClients(requiredConnectionState: false);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isFalse);
+        expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
+      }, timeout: const Timeout.factor(20));
+
+      test('server removes clients that disconnect from the API', () async {
+        final event = await serverStartedEvent.future;
+
+        // Spawn our own Chrome process so we can terminate it.
+        final devToolsUri =
+            'http://${event['params']['host']}:${event['params']['port']}';
+        final chrome = await Chrome.locate()!.start(url: devToolsUri);
+
+        // Wait for DevTools to inform server that it's connected.
+        await _waitForClients();
+
+        // Close the browser, which will disconnect DevTools SSE connection
+        // back to the server.
+        chrome.kill();
+
+        // Await a long delay to wait for the SSE client to close.
+        await delay(duration: const Duration(seconds: 20));
+
+        // Ensure the client is completely removed from the list.
+        await _waitForClients(expectNone: true, useLongTimeout: true);
+      }, timeout: const Timeout.factor(20));
+
+      test('Server reuses DevTools instance if already connected to same VM',
+          () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a request to launch DevTools in a browser.
+        await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+        {
+          final serverResponse = await _waitForClients(
+            requiredConnectionState: true,
+          );
+          expect(serverResponse['clients'], hasLength(1));
+        }
+
+        // Request again, allowing reuse, and server emits an event saying the
+        // window was reused.
+        final launchResponse = await _sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+        );
+        expect(launchResponse['reused'], isTrue);
+
+        // Ensure there's still only one connection (eg. we didn't spawn a new one
+        // we reused the existing one).
+        final serverResponse =
+            await _waitForClients(requiredConnectionState: true);
+        expect(serverResponse['clients'], hasLength(1));
+      }, timeout: const Timeout.factor(20));
+
+      test('Server does not reuse DevTools instance if embedded', () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Spawn an embedded version of DevTools in a browser.
+        final event = await serverStartedEvent.future;
+        final devToolsUri =
+            'http://${event['params']['host']}:${event['params']['port']}';
+        final launchUrl = '$devToolsUri/?embed=true&page=logging'
+            '&uri=${Uri.encodeQueryComponent(appFixture.serviceUri.toString())}';
+        final chrome = await Chrome.locate()!.start(url: launchUrl);
+        try {
+          {
+            final serverResponse =
+                await _waitForClients(requiredConnectionState: true);
+            expect(serverResponse['clients'], hasLength(1));
+          }
+
+          // Send a request to the server to launch and ensure it did
+          // not reuse the existing connection. Launch it on a different page
+          // so we can easily tell once this one has connected.
+          final launchResponse = await _sendLaunchDevToolsRequest(
+            useVmService: useVmService,
+            reuseWindows: true,
+            page: 'memory',
+          );
+          expect(launchResponse['reused'], isFalse);
+
+          // Ensure there's now two connections.
+          final serverResponse = await _waitForClients(
+            requiredConnectionState: true,
+            requiredPage: 'memory',
+          );
+          expect(serverResponse['clients'], hasLength(2));
+        } finally {
+          chrome.kill();
+        }
+      }, timeout: const Timeout.factor(20));
+
+      test('Server reuses DevTools instance if not connected to a VM',
+          () async {
+        // Register the VM.
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a request to launch DevTools in a browser.
+        await _sendLaunchDevToolsRequest(useVmService: useVmService);
+
+        // Wait for the DevTools to inform server that it's connected.
+        await _waitForClients(requiredConnectionState: true);
+
+        // Terminate the VM.
+        await appFixture.teardown();
+
+        // Ensure the client is marked as disconnected.
+        await _waitForClients(requiredConnectionState: false);
+
+        // Start up a new app.
+        await _startApp();
+        await _send('vm.register', {'uri': appFixture.serviceUri.toString()});
+
+        // Send a new request to launch.
+        await _sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+          notify: true,
+        );
+
+        // Ensure we now have a single connected client.
+        final serverResponse =
+            await _waitForClients(requiredConnectionState: true);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          appFixture.serviceUri.toString(),
+        );
+      }, timeout: const Timeout.factor(20));
+    });
+  }
+}
+
+Future<Map<String, dynamic>> _sendLaunchDevToolsRequest({
+  required bool useVmService,
+  String? page,
+  bool notify = false,
+  bool reuseWindows = false,
+}) async {
+  print('grabbing client.launch event');
+  final launchEvent = events.where((e) => e['event'] == 'client.launch').first;
+  if (useVmService) {
+    await appFixture.serviceConnection.callMethod(
+      registeredServices[DevToolsServer.launchDevToolsService]!,
+      args: {
+        'reuseWindows': reuseWindows,
+        'page': page,
+        'notify': notify,
+      },
+    );
+  } else {
+    await _send(
+      'devTools.launch',
+      {
+        'vmServiceUri': appFixture.serviceUri.toString(),
+        'reuseWindows': reuseWindows,
+        'page': page,
+      },
+    );
+  }
+  final response = await launchEvent;
+  final pid = response['params']['pid'];
+  if (pid != null) {
+    browserPids.add(pid);
+  }
+  return response['params'];
+}
+
+Future<void> _startApp() async {
+  final appUri =
+      Platform.script.resolveUri(Uri.parse('../fixtures/empty_dart_app.dart'));
+  appFixture = await CliAppFixture.create(appUri.path);
+
+  // Track services method names as they're registered.
+  appFixture.serviceConnection
+      .onEvent(EventStreams.kService)
+      .where((e) => e.kind == EventKind.kServiceRegistered)
+      .listen((e) => registeredServices[e.service!] = e.method!);
+  await appFixture.serviceConnection.streamListen(EventStreams.kService);
+}
+
+int nextId = 0;
+Future<Map<String, dynamic>> _send(
+  String method, [
+  Map<String, dynamic>? params,
+]) {
+  final id = (nextId++).toString();
+  completers[id] = Completer<Map<String, dynamic>>();
+  server.write({'id': id.toString(), 'method': method, 'params': params});
+  return completers[id]!.future;
+}
+
+// It may take time for the servers client list to be updated as the web app
+// connects, so this helper just polls waiting for the expected state and
+// then returns the client list.
+Future<Map<String, dynamic>> _waitForClients({
+  bool? requiredConnectionState,
+  String? requiredPage,
+  bool expectNone = false,
+  bool useLongTimeout = false,
+  Duration delayDuration = defaultDelay,
+}) async {
+  late Map<String, dynamic> serverResponse;
+
+  final isOnPage = (client) => client['currentPage'] == requiredPage;
+  final hasConnectionState = (client) => requiredConnectionState ?? false
+      // If we require a connected client, also require a non-null page. This
+      // avoids a race in tests where we may proceed to send messages to a client
+      // that is not fully initialised.
+      ? (client['hasConnection'] && client['currentPage'] != null)
+      : !client['hasConnection'];
+
+  await _waitFor(
+    () async {
+      // Await a short delay to give the client time to connect.
+      await delay();
+
+      serverResponse = await _send('client.list');
+      final clients = serverResponse['clients'];
+      return clients is List &&
+          (clients.isEmpty == expectNone) &&
+          (requiredPage == null || clients.any(isOnPage)) &&
+          (requiredConnectionState == null || clients.any(hasConnectionState));
+    },
+    delayDuration: delayDuration,
+  );
+
+  return serverResponse;
+}
+
+Future<void> _waitFor(
+  Future<bool> condition(), {
+  Duration delayDuration = defaultDelay,
+}) async {
+  while (true) {
+    if (await condition()) {
+      return;
+    }
+    await delay(duration: delayDuration);
+  }
+}
+
+const defaultDelay = Duration(milliseconds: 500);
diff --git a/pkg/dds/test/devtools_server/serve_devtools.dart b/pkg/dds/test/devtools_server/serve_devtools.dart
new file mode 100644
index 0000000..e44c4f8
--- /dev/null
+++ b/pkg/dds/test/devtools_server/serve_devtools.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:dds/devtools_server.dart';
+
+import '../common/test_helper.dart';
+
+void main(List<String> args) async {
+  unawaited(
+    DevToolsServer().serveDevToolsWithArgs(
+      args,
+      customDevToolsPath: devtoolsAppUri(prefix: '../../../../').path,
+    ),
+  );
+}
diff --git a/pkg/dds/test/fixtures/empty_dart_app.dart b/pkg/dds/test/fixtures/empty_dart_app.dart
new file mode 100644
index 0000000..e96e4fe
--- /dev/null
+++ b/pkg/dds/test/fixtures/empty_dart_app.dart
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors. 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';
+
+void main() async {
+  print('starting empty app');
+
+  var myVar = 0;
+  while (true) {
+    myVar++;
+    print(myVar);
+    await (Future.delayed(const Duration(seconds: 2)));
+  }
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart
new file mode 100644
index 0000000..644f54d
--- /dev/null
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_dart_2_17_test.dart
@@ -0,0 +1,303 @@
+// 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 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
+import 'package:test/test.dart';
+
+import 'expression_compiler_e2e_suite.dart';
+
+void main() async {
+  var driver = await TestDriver.init();
+
+  group('Dart 2.17 language features', () {
+    tearDownAll(() async {
+      await driver.finish();
+    });
+
+    group('(Unsound null safety)', () {
+      group('(AMD module system)', () {
+        var setup = SetupCompilerOptions(
+            soundNullSafety: false,
+            legacyCode: false,
+            moduleFormat: ModuleFormat.amd);
+        runSharedTests(setup, driver);
+      });
+
+      group('(DDC module system)', () {
+        var setup = SetupCompilerOptions(
+            soundNullSafety: false,
+            legacyCode: false,
+            moduleFormat: ModuleFormat.ddc);
+        runSharedTests(setup, driver);
+      });
+    });
+
+    group('(Sound null safety)', () {
+      group('(AMD module system)', () {
+        var setup = SetupCompilerOptions(
+            soundNullSafety: true,
+            legacyCode: false,
+            moduleFormat: ModuleFormat.amd);
+        runSharedTests(setup, driver);
+      });
+
+      group('(DDC module system)', () {
+        var setup = SetupCompilerOptions(
+            soundNullSafety: true,
+            legacyCode: false,
+            moduleFormat: ModuleFormat.ddc);
+        runSharedTests(setup, driver);
+      });
+    });
+  });
+}
+
+/// Shared tests for language features introduced in version 2.17.0.
+void runSharedTests(SetupCompilerOptions setup, TestDriver driver) {
+  group('Named arguments anywhere', () {
+    var source = r'''
+      String topLevelMethod(int param1, String param2,
+              {int param3 = -1, String param4 = 'default'}) =>
+          '$param1, $param2, $param3, $param4';
+
+      class C {
+        int param1;
+        String param2;
+        int param3;
+        String param4;
+        C(this.param1, this.param2,
+            {this.param3 = -1, this.param4 = 'default'});
+
+        static String staticMethod(int param1, String param2,
+              {int param3 = -1, String param4 = 'default'}) =>
+          '$param1, $param2, $param3, $param4';
+
+        String instanceMethod(int param1, String param2,
+              {int param3 = -1, String param4 = 'default'}) =>
+          '$param1, $param2, $param3, $param4';
+
+        String toString() => '$param1, $param2, $param3, $param4';
+      }
+
+      main() {
+        String localMethod(int param1, String param2,
+              {int param3 = -1, String param4 = 'default'}) =>
+          '$param1, $param2, $param3, $param4';
+        var c = C(1, 'two');
+        // Breakpoint: bp
+        print('hello world');
+      }
+        ''';
+
+    setUpAll(() async {
+      await driver.initSource(setup, source,
+          experiments: {'named-arguments-anywhere': true});
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('in top level method', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
+          expectedResult: '1, two, 3, four');
+    });
+    test('in local method', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
+          expectedResult: '1, two, 3, four');
+    });
+    test('in class constructor', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'C(param3: 3, 1, param4: "four", "two").toString()',
+          expectedResult: '1, two, 3, four');
+    });
+    test('in class static method', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'C.staticMethod(param3: 3, 1, param4: "four", "two")',
+          expectedResult: '1, two, 3, four');
+    });
+    test('in class instance method', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'c.instanceMethod(param3: 3, 1, param4: "four", "two")',
+          expectedResult: '1, two, 3, four');
+    });
+  });
+
+  group('Super parameters', () {
+    var source = r'''
+      class S {
+        final int i;
+        final String? s;
+        final double d;
+
+        S(this.i, [this.s]): d = 3.14;
+
+        S.named(this.i, {this.d = 3.14}): s = 'default';
+      }
+
+      class C extends S {
+        final int i1;
+        final int i2;
+
+        C(this.i1, super.i, this.i2, [super.s]);
+
+        C.named({super.d}): i1 = 10, i2 = 30, super.named(20);
+      }
+
+      main() {
+        var c = C(1, 2, 3, 'bar');
+        var c2 = C.named(d: 2.71);
+        // Breakpoint: bp
+        print('hello world');
+      }
+        ''';
+
+    setUpAll(() async {
+      await driver
+          .initSource(setup, source, experiments: {'super-parameters': true});
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('in constructor mixed with regular parameters', () async {
+      await driver.check(
+          breakpointId: 'bp', expression: 'c.i1', expectedResult: '1');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c.i', expectedResult: '2');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c.i2', expectedResult: '3');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c.s', expectedResult: 'bar');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c.d', expectedResult: '3.14');
+    });
+    test('in named constructor mixed with regular parameters', () async {
+      await driver.check(
+          breakpointId: 'bp', expression: 'c2.i1', expectedResult: '10');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c2.i', expectedResult: '20');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c2.i2', expectedResult: '30');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c2.s', expectedResult: 'default');
+      await driver.check(
+          breakpointId: 'bp', expression: 'c2.d', expectedResult: '2.71');
+    });
+  });
+
+  group('Enhanced enums', () {
+    var source = r'''
+      enum E<T> with M {
+        id_int<int>(0),
+        id_bool<bool>(true),
+        id_string<String>('hello world', n: 13);
+
+        final T field;
+        final num n;
+        static const constStaticField = id_string;
+
+        const E(T arg0, {num? n}) : this.field = arg0, this.n = n ?? 42;
+
+        T get fieldGetter => field;
+        num instanceMethod() => n;
+      }
+
+      enum E2 with M {
+        v1, v2, id_string;
+        int get index => 10;
+      }
+
+      mixin M on Enum {
+        int mixinMethod() => index * 100;
+      }
+
+      main() {
+        var e = E.id_string;
+        // Breakpoint: bp
+        print('hello world');
+      }
+        ''';
+
+    setUpAll(() async {
+      await driver
+          .initSource(setup, source, experiments: {'enhanced-enums': true});
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('evaluate to the correct string', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E.id_string.toString()',
+          expectedResult: 'E.id_string');
+    });
+    test('evaluate to the correct index', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E.id_string.index',
+          expectedResult: '2');
+    });
+    test('compare properly against themselves', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'e == E.id_string && E.id_string == E.id_string',
+          expectedResult: 'true');
+    });
+    test('compare properly against other enums', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'e != E2.id_string && E.id_string != E2.id_string',
+          expectedResult: 'true');
+    });
+    test('with instance methods', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E.id_bool.instanceMethod()',
+          expectedResult: '42');
+    });
+    test('with instance methods from local instance', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'e.instanceMethod()',
+          expectedResult: '13');
+    });
+    test('with getters', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E.id_int.fieldGetter',
+          expectedResult: '0');
+    });
+    test('with getters from local instance', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'e.fieldGetter',
+          expectedResult: 'hello world');
+    });
+    test('with mixin calls', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E.id_string.mixinMethod()',
+          expectedResult: '200');
+    });
+    test('with mixin calls through overridden indices', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'E2.v2.mixinMethod()',
+          expectedResult: '1000');
+    });
+  });
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
index 7db89ce..a1916b8 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
@@ -61,10 +61,7 @@
 }
 ''';
 
-/// Shared tests that require a language version greater than 2.12.
-///
-/// Tests that exercise language features introduced with 2.12 or after are
-/// valid here.
+/// Shared tests that require a language version >=2.12.0 <2.17.0.
 // TODO(nshahan) Merge with [runAgnosticSharedTests] after we no longer need to
 // test support for evaluation in legacy (pre-null safety) code.
 void runNullSafeSharedTests(SetupCompilerOptions setup, TestDriver driver) {
@@ -316,82 +313,6 @@
     });
   });
 
-  group('Named arguments anywhere', () {
-    var source = r'''
-      String topLevelMethod(int param1, String param2,
-              {int param3 = -1, String param4 = 'default'}) =>
-          '$param1, $param2, $param3, $param4';
-
-      class C {
-        int param1;
-        String param2;
-        int param3;
-        String param4;
-        C(this.param1, this.param2,
-            {this.param3 = -1, this.param4 = 'default'});
-
-        static String staticMethod(int param1, String param2,
-              {int param3 = -1, String param4 = 'default'}) =>
-          '$param1, $param2, $param3, $param4';
-
-        String instanceMethod(int param1, String param2,
-              {int param3 = -1, String param4 = 'default'}) =>
-          '$param1, $param2, $param3, $param4';
-
-        String toString() => '$param1, $param2, $param3, $param4';
-      }
-
-      main() {
-        String localMethod(int param1, String param2,
-              {int param3 = -1, String param4 = 'default'}) =>
-          '$param1, $param2, $param3, $param4';
-        var c = C(1, 'two');
-        // Breakpoint: bp
-        print('hello world');
-      }
-        ''';
-
-    setUpAll(() async {
-      await driver.initSource(setup, source,
-          experiments: {'named-arguments-anywhere': true});
-    });
-
-    tearDownAll(() async {
-      await driver.cleanupTest();
-    });
-
-    test('in top level method', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
-          expectedResult: '1, two, 3, four');
-    });
-    test('in local method', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'topLevelMethod(param3: 3, 1, param4: "four", "two")',
-          expectedResult: '1, two, 3, four');
-    });
-    test('in class constructor', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'C(param3: 3, 1, param4: "four", "two").toString()',
-          expectedResult: '1, two, 3, four');
-    });
-    test('in class static method', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'C.staticMethod(param3: 3, 1, param4: "four", "two")',
-          expectedResult: '1, two, 3, four');
-    });
-    test('in class instance method', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'c.instanceMethod(param3: 3, 1, param4: "four", "two")',
-          expectedResult: '1, two, 3, four');
-    });
-  });
-
   group('Enums', () {
     var source = r'''
       enum E {id1, id2, id3}
@@ -436,110 +357,6 @@
           expectedResult: 'true');
     });
   });
-
-  group('Enhanced enums', () {
-    var source = r'''
-      enum E<T> with M {
-        id_int<int>(0),
-        id_bool<bool>(true),
-        id_string<String>('hello world', n: 13);
-
-        final T field;
-        final num n;
-        static const constStaticField = id_string;
-
-        const E(T arg0, {num? n}) : this.field = arg0, this.n = n ?? 42;
-
-        T get fieldGetter => field;
-        num instanceMethod() => n;
-      }
-
-      enum E2 with M {
-        v1, v2, id_string;
-        int get index => 10;
-      }
-
-      mixin M on Enum {
-        int mixinMethod() => index * 100;
-      }
-
-      main() {
-        var e = E.id_string;
-        // Breakpoint: bp
-        print('hello world');
-      }
-        ''';
-
-    setUpAll(() async {
-      await driver
-          .initSource(setup, source, experiments: {'enhanced-enums': true});
-    });
-
-    tearDownAll(() async {
-      await driver.cleanupTest();
-    });
-
-    test('evaluate to the correct string', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E.id_string.toString()',
-          expectedResult: 'E.id_string');
-    });
-    test('evaluate to the correct index', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E.id_string.index',
-          expectedResult: '2');
-    });
-    test('compare properly against themselves', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'e == E.id_string && E.id_string == E.id_string',
-          expectedResult: 'true');
-    });
-    test('compare properly against other enums', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'e != E2.id_string && E.id_string != E2.id_string',
-          expectedResult: 'true');
-    });
-    test('with instance methods', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E.id_bool.instanceMethod()',
-          expectedResult: '42');
-    });
-    test('with instance methods from local instance', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'e.instanceMethod()',
-          expectedResult: '13');
-    });
-    test('with getters', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E.id_int.fieldGetter',
-          expectedResult: '0');
-    });
-    test('with getters from local instance', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'e.fieldGetter',
-          expectedResult: 'hello world');
-    });
-    test('with mixin calls', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E.id_string.mixinMethod()',
-          expectedResult: '200');
-    });
-    test('with mixin calls through overridden indices', () async {
-      await driver.check(
-          breakpointId: 'bp',
-          expression: 'E2.v2.mixinMethod()',
-          expectedResult: '1000');
-    });
-  });
 }
 
 /// Shared tests that are valid in legacy (before 2.12) and are agnostic to
diff --git a/pkg/nnbd_migration/lib/migration_cli.dart b/pkg/nnbd_migration/lib/migration_cli.dart
index 2a30f55..a6bd912 100644
--- a/pkg/nnbd_migration/lib/migration_cli.dart
+++ b/pkg/nnbd_migration/lib/migration_cli.dart
@@ -896,7 +896,7 @@
     logger.stdout(ansi.emphasized('Re-analyzing project...'));
 
     _dartFixListener!.reset();
-    _fixCodeProcessor!.prepareToRerun();
+    await _fixCodeProcessor!.prepareToRerun();
     var analysisResult = await _fixCodeProcessor!.runFirstPhase();
     if (analysisResult.hasErrors && !options.ignoreErrors!) {
       _logErrors(analysisResult);
@@ -997,10 +997,11 @@
 
   bool get isPreviewServerRunning => _task?.isPreviewServerRunning ?? false;
 
-  void prepareToRerun() {
+  Future<void> prepareToRerun() async {
     var driver = context.driver;
     pathsToProcess = _migrationCli.computePathsToProcess(context);
     pathsToProcess.forEach(driver.changeFile);
+    await driver.applyPendingFileChanges();
   }
 
   /// Call the supplied [process] function to process each compilation unit.
diff --git a/pkg/nnbd_migration/test/abstract_context.dart b/pkg/nnbd_migration/test/abstract_context.dart
index 2965a8a..b096215 100644
--- a/pkg/nnbd_migration/test/abstract_context.dart
+++ b/pkg/nnbd_migration/test/abstract_context.dart
@@ -126,10 +126,7 @@
 
   Source addSource(String path, String content, [Uri? uri]) {
     File file = newFile(path, content: content);
-    Source source = file.createSource(uri);
-    driver!.addFile(file.path);
-    driver!.changeFile(file.path);
-    return source;
+    return file.createSource(uri);
   }
 
   /// Add the test_core package and a library with URI,
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 2aef55f..185ab05 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -47,6 +47,7 @@
 dartdev/test/commands/analyze_test: Slow, Pass
 dartdev/test/commands/help_test: Slow, Pass
 dartdev/test/smoke/*: Slow, Pass
+dds/test/devtools_server/devtools_server_test: Slow, Pass
 dev_compiler/test/modular/*: Slow, Pass
 dev_compiler/test/options/*: Skip # test needs fixes
 dev_compiler/test/sourcemap/*: SkipByDesign # Skip sourcemap tests
@@ -145,6 +146,7 @@
 vm_snapshot_analysis/test/*: SkipByDesign # Only meant to run on vm
 
 [ $system == windows ]
+dds/test/devtools_server/devtools_server_test: Skip # Issue 48528
 front_end/test/fasta/bootstrap_test: Skip # Issue 31902
 front_end/test/fasta/strong_test: Pass, Slow, Timeout
 front_end/test/fasta/text_serialization_test: Pass, Slow, Timeout
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 6f14afa..52ee6d0 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## 8.2.1
+- Remove `example/vm_service_asserts.dart'
+
 ## 8.2.0
 - Update to version `3.56` of the spec.
 - Added optional `line` and `column` properties to `SourceLocation`.
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
deleted file mode 100644
index 2955af8..0000000
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ /dev/null
@@ -1,1209 +0,0 @@
-// Copyright (c) 2015, 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.
-
-// This is a generated file.
-
-/// A library for asserting correct responses from the VM Service.
-
-import 'package:vm_service/vm_service.dart' as vms;
-
-dynamic assertNotNull(dynamic obj) {
-  if (obj == null) throw 'assert failed';
-  return obj;
-}
-
-bool assertBool(bool obj) {
-  return obj;
-}
-
-int assertInt(int obj) {
-  return obj;
-}
-
-double assertDouble(double obj) {
-  return obj;
-}
-
-dynamic assertDynamic(dynamic obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-List<dynamic> assertListOfDynamic(List<dynamic> list) {
-  return list;
-}
-
-List<int> assertListOfInt(List<int> list) {
-  for (int elem in list) {
-    assertInt(elem);
-  }
-  return list;
-}
-
-List<String> assertListOfString(List<String> list) {
-  for (String elem in list) {
-    assertString(elem);
-  }
-  return list;
-}
-
-List<vms.IsolateFlag> assertListOfIsolateFlag(List<vms.IsolateFlag> list) {
-  for (vms.IsolateFlag elem in list) {
-    assertIsolateFlag(elem);
-  }
-  return list;
-}
-
-String assertString(String obj) {
-  if (obj.isEmpty) throw 'expected non-zero length string';
-  return obj;
-}
-
-vms.Success assertSuccess(vms.Success obj) {
-  if (obj.type != 'Success') throw 'expected Success';
-  return obj;
-}
-
-/// Assert PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
-/// PauseException, Resume, BreakpointAdded, BreakpointResolved,
-/// BreakpointRemoved, and Inspect events.
-vms.Event assertDebugEvent(vms.Event event) {
-  assertEvent(event);
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kBreakpointAdded ||
-      event.kind == vms.EventKind.kBreakpointRemoved ||
-      event.kind == vms.EventKind.kBreakpointResolved) {
-    assertBreakpoint(event.breakpoint!);
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint) {
-    for (vms.Breakpoint elem in event.pauseBreakpoints!) {
-      assertBreakpoint(elem);
-    }
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kPauseInterrupted ||
-      event.kind == vms.EventKind.kPauseException ||
-      event.kind == vms.EventKind.kResume) {
-    // For PauseInterrupted events, there will be no top frame if the isolate is
-    // idle (waiting in the message loop).
-    // For the Resume event, the top frame is provided at all times except for
-    // the initial resume event that is delivered when an isolate begins
-    // execution.
-    if (event.topFrame != null ||
-        (event.kind != vms.EventKind.kPauseInterrupted &&
-            event.kind != vms.EventKind.kResume)) {
-      assertFrame(event.topFrame!);
-    }
-  }
-  if (event.kind == vms.EventKind.kPauseException) {
-    assertInstanceRef(event.exception!);
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kPauseInterrupted) {
-    assertBool(event.atAsyncSuspension!);
-  }
-  if (event.kind == vms.EventKind.kInspect) {
-    assertInstanceRef(event.inspectee!);
-  }
-  return event;
-}
-
-/// Assert IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate,
-/// and ServiceExtensionAdded events.
-vms.Event assertIsolateEvent(vms.Event event) {
-  assertEvent(event);
-  if (event.kind == vms.EventKind.kServiceExtensionAdded) {
-    assertString(event.extensionRPC!);
-  }
-  return event;
-}
-
-String assertCodeKind(String obj) {
-  if (obj == "Collected") return obj;
-  if (obj == "Dart") return obj;
-  if (obj == "Native") return obj;
-  if (obj == "Stub") return obj;
-  if (obj == "Tag") return obj;
-  throw "invalid CodeKind: $obj";
-}
-
-String assertErrorKind(String obj) {
-  if (obj == "InternalError") return obj;
-  if (obj == "LanguageError") return obj;
-  if (obj == "TerminationError") return obj;
-  if (obj == "UnhandledException") return obj;
-  throw "invalid ErrorKind: $obj";
-}
-
-String assertEventKind(String obj) {
-  if (obj == "BreakpointAdded") return obj;
-  if (obj == "BreakpointRemoved") return obj;
-  if (obj == "BreakpointResolved") return obj;
-  if (obj == "BreakpointUpdated") return obj;
-  if (obj == "CpuSamples") return obj;
-  if (obj == "Extension") return obj;
-  if (obj == "GC") return obj;
-  if (obj == "Inspect") return obj;
-  if (obj == "IsolateExit") return obj;
-  if (obj == "IsolateReload") return obj;
-  if (obj == "IsolateRunnable") return obj;
-  if (obj == "IsolateStart") return obj;
-  if (obj == "IsolateUpdate") return obj;
-  if (obj == "Logging") return obj;
-  if (obj == "None") return obj;
-  if (obj == "PauseBreakpoint") return obj;
-  if (obj == "PauseException") return obj;
-  if (obj == "PauseExit") return obj;
-  if (obj == "PauseInterrupted") return obj;
-  if (obj == "PausePostRequest") return obj;
-  if (obj == "PauseStart") return obj;
-  if (obj == "Resume") return obj;
-  if (obj == "ServiceExtensionAdded") return obj;
-  if (obj == "ServiceRegistered") return obj;
-  if (obj == "ServiceUnregistered") return obj;
-  if (obj == "TimelineEvents") return obj;
-  if (obj == "TimelineStreamSubscriptionsUpdate") return obj;
-  if (obj == "UserTagChanged") return obj;
-  if (obj == "VMFlagUpdate") return obj;
-  if (obj == "VMUpdate") return obj;
-  if (obj == "WriteEvent") return obj;
-  throw "invalid EventKind: $obj";
-}
-
-String assertInstanceKind(String obj) {
-  if (obj == "Bool") return obj;
-  if (obj == "BoundedType") return obj;
-  if (obj == "Closure") return obj;
-  if (obj == "Double") return obj;
-  if (obj == "Float32List") return obj;
-  if (obj == "Float32x4") return obj;
-  if (obj == "Float32x4List") return obj;
-  if (obj == "Float64List") return obj;
-  if (obj == "Float64x2") return obj;
-  if (obj == "Float64x2List") return obj;
-  if (obj == "FunctionType") return obj;
-  if (obj == "Int") return obj;
-  if (obj == "Int16List") return obj;
-  if (obj == "Int32List") return obj;
-  if (obj == "Int32x4") return obj;
-  if (obj == "Int32x4List") return obj;
-  if (obj == "Int64List") return obj;
-  if (obj == "Int8List") return obj;
-  if (obj == "List") return obj;
-  if (obj == "Map") return obj;
-  if (obj == "MirrorReference") return obj;
-  if (obj == "Null") return obj;
-  if (obj == "PlainInstance") return obj;
-  if (obj == "ReceivePort") return obj;
-  if (obj == "RegExp") return obj;
-  if (obj == "StackTrace") return obj;
-  if (obj == "String") return obj;
-  if (obj == "Type") return obj;
-  if (obj == "TypeParameter") return obj;
-  if (obj == "TypeRef") return obj;
-  if (obj == "Uint16List") return obj;
-  if (obj == "Uint32List") return obj;
-  if (obj == "Uint64List") return obj;
-  if (obj == "Uint8ClampedList") return obj;
-  if (obj == "Uint8List") return obj;
-  if (obj == "WeakProperty") return obj;
-  throw "invalid InstanceKind: $obj";
-}
-
-String assertSentinelKind(String obj) {
-  if (obj == "BeingInitialized") return obj;
-  if (obj == "Collected") return obj;
-  if (obj == "Expired") return obj;
-  if (obj == "Free") return obj;
-  if (obj == "NotInitialized") return obj;
-  if (obj == "OptimizedOut") return obj;
-  throw "invalid SentinelKind: $obj";
-}
-
-String assertFrameKind(String obj) {
-  if (obj == "AsyncActivation") return obj;
-  if (obj == "AsyncCausal") return obj;
-  if (obj == "AsyncSuspensionMarker") return obj;
-  if (obj == "Regular") return obj;
-  throw "invalid FrameKind: $obj";
-}
-
-String assertSourceReportKind(String obj) {
-  if (obj == "BranchCoverage") return obj;
-  if (obj == "Coverage") return obj;
-  if (obj == "PossibleBreakpoints") return obj;
-  throw "invalid SourceReportKind: $obj";
-}
-
-String assertExceptionPauseMode(String obj) {
-  if (obj == "All") return obj;
-  if (obj == "None") return obj;
-  if (obj == "Unhandled") return obj;
-  throw "invalid ExceptionPauseMode: $obj";
-}
-
-String assertStepOption(String obj) {
-  if (obj == "Into") return obj;
-  if (obj == "Out") return obj;
-  if (obj == "Over") return obj;
-  if (obj == "OverAsyncSuspension") return obj;
-  if (obj == "Rewind") return obj;
-  throw "invalid StepOption: $obj";
-}
-
-vms.AllocationProfile assertAllocationProfile(vms.AllocationProfile obj) {
-  assertNotNull(obj);
-  assertListOfClassHeapStats(obj.members!);
-  assertMemoryUsage(obj.memoryUsage!);
-  return obj;
-}
-
-vms.BoundField assertBoundField(vms.BoundField obj) {
-  assertNotNull(obj);
-  assertFieldRef(obj.decl!);
-  if (obj.value is vms.InstanceRef) {
-    assertInstanceRef(obj.value!);
-  } else if (obj.value is vms.Sentinel) {
-    assertSentinel(obj.value!);
-  } else {
-    throw "Unexpected value: ${obj.value}";
-  }
-  return obj;
-}
-
-vms.BoundVariable assertBoundVariable(vms.BoundVariable obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  if (obj.value is vms.InstanceRef) {
-    assertInstanceRef(obj.value!);
-  } else if (obj.value is vms.TypeArgumentsRef) {
-    assertTypeArgumentsRef(obj.value!);
-  } else if (obj.value is vms.Sentinel) {
-    assertSentinel(obj.value!);
-  } else {
-    throw "Unexpected value: ${obj.value}";
-  }
-  assertInt(obj.declarationTokenPos!);
-  assertInt(obj.scopeStartTokenPos!);
-  assertInt(obj.scopeEndTokenPos!);
-  return obj;
-}
-
-List<vms.BoundVariable> assertListOfBoundVariable(
-    List<vms.BoundVariable> list) {
-  for (vms.BoundVariable elem in list) {
-    assertBoundVariable(elem);
-  }
-  return list;
-}
-
-vms.Breakpoint assertBreakpoint(vms.Breakpoint obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInt(obj.breakpointNumber!);
-  assertBool(obj.enabled!);
-  assertBool(obj.resolved!);
-  if (obj.location is vms.SourceLocation) {
-    assertSourceLocation(obj.location!);
-  } else if (obj.location is vms.UnresolvedSourceLocation) {
-    assertUnresolvedSourceLocation(obj.location!);
-  } else {
-    throw "Unexpected value: ${obj.location}";
-  }
-  return obj;
-}
-
-List<vms.Breakpoint> assertListOfBreakpoint(List<vms.Breakpoint> list) {
-  for (vms.Breakpoint elem in list) {
-    assertBreakpoint(elem);
-  }
-  return list;
-}
-
-vms.ClassRef assertClassRef(vms.ClassRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertLibraryRef(obj.library!);
-  return obj;
-}
-
-List<vms.ClassRef> assertListOfClassRef(List<vms.ClassRef> list) {
-  for (vms.ClassRef elem in list) {
-    assertClassRef(elem);
-  }
-  return list;
-}
-
-vms.Class assertClass(vms.Class obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertLibraryRef(obj.library!);
-  assertBool(obj.isAbstract!);
-  assertBool(obj.isConst!);
-  assertBool(obj.traceAllocations!);
-  assertListOfInstanceRef(obj.interfaces!);
-  assertListOfFieldRef(obj.fields!);
-  assertListOfFuncRef(obj.functions!);
-  assertListOfClassRef(obj.subclasses!);
-  return obj;
-}
-
-vms.ClassHeapStats assertClassHeapStats(vms.ClassHeapStats obj) {
-  assertNotNull(obj);
-  assertClassRef(obj.classRef!);
-  assertInt(obj.accumulatedSize!);
-  assertInt(obj.bytesCurrent!);
-  assertInt(obj.instancesAccumulated!);
-  assertInt(obj.instancesCurrent!);
-  return obj;
-}
-
-List<vms.ClassHeapStats> assertListOfClassHeapStats(
-    List<vms.ClassHeapStats> list) {
-  for (vms.ClassHeapStats elem in list) {
-    assertClassHeapStats(elem);
-  }
-  return list;
-}
-
-vms.ClassList assertClassList(vms.ClassList obj) {
-  assertNotNull(obj);
-  assertListOfClassRef(obj.classes!);
-  return obj;
-}
-
-vms.CodeRef assertCodeRef(vms.CodeRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertCodeKind(obj.kind!);
-  return obj;
-}
-
-List<vms.CodeRef> assertListOfCodeRef(List<vms.CodeRef> list) {
-  for (vms.CodeRef elem in list) {
-    assertCodeRef(elem);
-  }
-  return list;
-}
-
-vms.Code assertCode(vms.Code obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertCodeKind(obj.kind!);
-  return obj;
-}
-
-vms.ContextRef assertContextRef(vms.ContextRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInt(obj.length!);
-  return obj;
-}
-
-List<vms.ContextRef> assertListOfContextRef(List<vms.ContextRef> list) {
-  for (vms.ContextRef elem in list) {
-    assertContextRef(elem);
-  }
-  return list;
-}
-
-vms.Context assertContext(vms.Context obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInt(obj.length!);
-  assertListOfContextElement(obj.variables!);
-  return obj;
-}
-
-vms.ContextElement assertContextElement(vms.ContextElement obj) {
-  assertNotNull(obj);
-  if (obj.value is vms.InstanceRef) {
-    assertInstanceRef(obj.value!);
-  } else if (obj.value is vms.Sentinel) {
-    assertSentinel(obj.value!);
-  } else {
-    throw "Unexpected value: ${obj.value}";
-  }
-  return obj;
-}
-
-List<vms.ContextElement> assertListOfContextElement(
-    List<vms.ContextElement> list) {
-  for (vms.ContextElement elem in list) {
-    assertContextElement(elem);
-  }
-  return list;
-}
-
-vms.CpuSamples assertCpuSamples(vms.CpuSamples obj) {
-  assertNotNull(obj);
-  assertInt(obj.samplePeriod!);
-  assertInt(obj.maxStackDepth!);
-  assertInt(obj.sampleCount!);
-  assertInt(obj.timeSpan!);
-  assertInt(obj.timeOriginMicros!);
-  assertInt(obj.timeExtentMicros!);
-  assertInt(obj.pid!);
-  assertListOfProfileFunction(obj.functions!);
-  assertListOfCpuSample(obj.samples!);
-  return obj;
-}
-
-vms.CpuSamplesEvent assertCpuSamplesEvent(vms.CpuSamplesEvent obj) {
-  assertNotNull(obj);
-  assertInt(obj.samplePeriod!);
-  assertInt(obj.maxStackDepth!);
-  assertInt(obj.sampleCount!);
-  assertInt(obj.timeSpan!);
-  assertInt(obj.timeOriginMicros!);
-  assertInt(obj.timeExtentMicros!);
-  assertInt(obj.pid!);
-  assertListOfDynamic(obj.functions!);
-  assertListOfCpuSample(obj.samples!);
-  return obj;
-}
-
-vms.CpuSample assertCpuSample(vms.CpuSample obj) {
-  assertNotNull(obj);
-  assertInt(obj.tid!);
-  assertInt(obj.timestamp!);
-  assertListOfInt(obj.stack!);
-  return obj;
-}
-
-List<vms.CpuSample> assertListOfCpuSample(List<vms.CpuSample> list) {
-  for (vms.CpuSample elem in list) {
-    assertCpuSample(elem);
-  }
-  return list;
-}
-
-vms.ErrorRef assertErrorRef(vms.ErrorRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertErrorKind(obj.kind!);
-  assertString(obj.message!);
-  return obj;
-}
-
-List<vms.ErrorRef> assertListOfErrorRef(List<vms.ErrorRef> list) {
-  for (vms.ErrorRef elem in list) {
-    assertErrorRef(elem);
-  }
-  return list;
-}
-
-vms.Error assertError(vms.Error obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertErrorKind(obj.kind!);
-  assertString(obj.message!);
-  return obj;
-}
-
-vms.Event assertEvent(vms.Event obj) {
-  assertNotNull(obj);
-  assertEventKind(obj.kind!);
-  assertInt(obj.timestamp!);
-  return obj;
-}
-
-vms.ExtensionData assertExtensionData(vms.ExtensionData obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-vms.FieldRef assertFieldRef(vms.FieldRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertObjRef(obj.owner!);
-  assertInstanceRef(obj.declaredType!);
-  assertBool(obj.isConst!);
-  assertBool(obj.isFinal!);
-  assertBool(obj.isStatic!);
-  return obj;
-}
-
-List<vms.FieldRef> assertListOfFieldRef(List<vms.FieldRef> list) {
-  for (vms.FieldRef elem in list) {
-    assertFieldRef(elem);
-  }
-  return list;
-}
-
-vms.Field assertField(vms.Field obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertObjRef(obj.owner!);
-  assertInstanceRef(obj.declaredType!);
-  assertBool(obj.isConst!);
-  assertBool(obj.isFinal!);
-  assertBool(obj.isStatic!);
-  return obj;
-}
-
-vms.Flag assertFlag(vms.Flag obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  assertString(obj.comment!);
-  assertBool(obj.modified!);
-  return obj;
-}
-
-List<vms.Flag> assertListOfFlag(List<vms.Flag> list) {
-  for (vms.Flag elem in list) {
-    assertFlag(elem);
-  }
-  return list;
-}
-
-vms.FlagList assertFlagList(vms.FlagList obj) {
-  assertNotNull(obj);
-  assertListOfFlag(obj.flags!);
-  return obj;
-}
-
-vms.Frame assertFrame(vms.Frame obj) {
-  assertNotNull(obj);
-  assertInt(obj.index!);
-  return obj;
-}
-
-List<vms.Frame> assertListOfFrame(List<vms.Frame> list) {
-  for (vms.Frame elem in list) {
-    assertFrame(elem);
-  }
-  return list;
-}
-
-vms.FuncRef assertFuncRef(vms.FuncRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  if (obj.owner is vms.LibraryRef) {
-    assertLibraryRef(obj.owner!);
-  } else if (obj.owner is vms.ClassRef) {
-    assertClassRef(obj.owner!);
-  } else if (obj.owner is vms.FuncRef) {
-    assertFuncRef(obj.owner!);
-  } else {
-    throw "Unexpected value: ${obj.owner}";
-  }
-  assertBool(obj.isStatic!);
-  assertBool(obj.isConst!);
-  assertBool(obj.implicit!);
-  return obj;
-}
-
-List<vms.FuncRef> assertListOfFuncRef(List<vms.FuncRef> list) {
-  for (vms.FuncRef elem in list) {
-    assertFuncRef(elem);
-  }
-  return list;
-}
-
-vms.Func assertFunc(vms.Func obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  if (obj.owner is vms.LibraryRef) {
-    assertLibraryRef(obj.owner!);
-  } else if (obj.owner is vms.ClassRef) {
-    assertClassRef(obj.owner!);
-  } else if (obj.owner is vms.FuncRef) {
-    assertFuncRef(obj.owner!);
-  } else {
-    throw "Unexpected value: ${obj.owner}";
-  }
-  assertBool(obj.isStatic!);
-  assertBool(obj.isConst!);
-  assertBool(obj.implicit!);
-  assertInstanceRef(obj.signature!);
-  return obj;
-}
-
-vms.InstanceRef assertInstanceRef(vms.InstanceRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInstanceKind(obj.kind!);
-  assertInt(obj.identityHashCode!);
-  assertClassRef(obj.classRef!);
-  return obj;
-}
-
-List<vms.InstanceRef> assertListOfInstanceRef(List<vms.InstanceRef> list) {
-  for (vms.InstanceRef elem in list) {
-    assertInstanceRef(elem);
-  }
-  return list;
-}
-
-vms.Instance assertInstance(vms.Instance obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInstanceKind(obj.kind!);
-  assertInt(obj.identityHashCode!);
-  assertClassRef(obj.classRef!);
-  return obj;
-}
-
-vms.IsolateRef assertIsolateRef(vms.IsolateRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.number!);
-  assertString(obj.name!);
-  assertBool(obj.isSystemIsolate!);
-  return obj;
-}
-
-List<vms.IsolateRef> assertListOfIsolateRef(List<vms.IsolateRef> list) {
-  for (vms.IsolateRef elem in list) {
-    assertIsolateRef(elem);
-  }
-  return list;
-}
-
-vms.Isolate assertIsolate(vms.Isolate obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.number!);
-  assertString(obj.name!);
-  assertBool(obj.isSystemIsolate!);
-  assertListOfIsolateFlag(obj.isolateFlags!);
-  assertInt(obj.startTime!);
-  assertBool(obj.runnable!);
-  assertInt(obj.livePorts!);
-  assertBool(obj.pauseOnExit!);
-  assertEvent(obj.pauseEvent!);
-  assertListOfLibraryRef(obj.libraries!);
-  assertListOfBreakpoint(obj.breakpoints!);
-  assertExceptionPauseMode(obj.exceptionPauseMode!);
-  return obj;
-}
-
-vms.IsolateFlag assertIsolateFlag(vms.IsolateFlag obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  assertString(obj.valueAsString!);
-  return obj;
-}
-
-vms.IsolateGroupRef assertIsolateGroupRef(vms.IsolateGroupRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.number!);
-  assertString(obj.name!);
-  assertBool(obj.isSystemIsolateGroup!);
-  return obj;
-}
-
-List<vms.IsolateGroupRef> assertListOfIsolateGroupRef(
-    List<vms.IsolateGroupRef> list) {
-  for (vms.IsolateGroupRef elem in list) {
-    assertIsolateGroupRef(elem);
-  }
-  return list;
-}
-
-vms.IsolateGroup assertIsolateGroup(vms.IsolateGroup obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.number!);
-  assertString(obj.name!);
-  assertBool(obj.isSystemIsolateGroup!);
-  assertListOfIsolateRef(obj.isolates!);
-  return obj;
-}
-
-vms.InboundReferences assertInboundReferences(vms.InboundReferences obj) {
-  assertNotNull(obj);
-  assertListOfInboundReference(obj.references!);
-  return obj;
-}
-
-vms.InboundReference assertInboundReference(vms.InboundReference obj) {
-  assertNotNull(obj);
-  assertObjRef(obj.source!);
-  return obj;
-}
-
-List<vms.InboundReference> assertListOfInboundReference(
-    List<vms.InboundReference> list) {
-  for (vms.InboundReference elem in list) {
-    assertInboundReference(elem);
-  }
-  return list;
-}
-
-vms.InstanceSet assertInstanceSet(vms.InstanceSet obj) {
-  assertNotNull(obj);
-  assertInt(obj.totalCount!);
-  assertListOfObjRef(obj.instances!);
-  return obj;
-}
-
-vms.LibraryRef assertLibraryRef(vms.LibraryRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertString(obj.uri!);
-  return obj;
-}
-
-List<vms.LibraryRef> assertListOfLibraryRef(List<vms.LibraryRef> list) {
-  for (vms.LibraryRef elem in list) {
-    assertLibraryRef(elem);
-  }
-  return list;
-}
-
-vms.Library assertLibrary(vms.Library obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertString(obj.uri!);
-  assertBool(obj.debuggable!);
-  assertListOfLibraryDependency(obj.dependencies!);
-  assertListOfScriptRef(obj.scripts!);
-  assertListOfFieldRef(obj.variables!);
-  assertListOfFuncRef(obj.functions!);
-  assertListOfClassRef(obj.classes!);
-  return obj;
-}
-
-vms.LibraryDependency assertLibraryDependency(vms.LibraryDependency obj) {
-  assertNotNull(obj);
-  assertBool(obj.isImport!);
-  assertBool(obj.isDeferred!);
-  assertString(obj.prefix!);
-  assertLibraryRef(obj.target!);
-  return obj;
-}
-
-List<vms.LibraryDependency> assertListOfLibraryDependency(
-    List<vms.LibraryDependency> list) {
-  for (vms.LibraryDependency elem in list) {
-    assertLibraryDependency(elem);
-  }
-  return list;
-}
-
-vms.LogRecord assertLogRecord(vms.LogRecord obj) {
-  assertNotNull(obj);
-  assertInstanceRef(obj.message!);
-  assertInt(obj.time!);
-  assertInt(obj.level!);
-  assertInt(obj.sequenceNumber!);
-  assertInstanceRef(obj.loggerName!);
-  assertInstanceRef(obj.zone!);
-  assertInstanceRef(obj.error!);
-  assertInstanceRef(obj.stackTrace!);
-  return obj;
-}
-
-vms.MapAssociation assertMapAssociation(vms.MapAssociation obj) {
-  assertNotNull(obj);
-  if (obj.key is vms.InstanceRef) {
-    assertInstanceRef(obj.key!);
-  } else if (obj.key is vms.Sentinel) {
-    assertSentinel(obj.key!);
-  } else {
-    throw "Unexpected value: ${obj.key}";
-  }
-  if (obj.value is vms.InstanceRef) {
-    assertInstanceRef(obj.value!);
-  } else if (obj.value is vms.Sentinel) {
-    assertSentinel(obj.value!);
-  } else {
-    throw "Unexpected value: ${obj.value}";
-  }
-  return obj;
-}
-
-vms.MemoryUsage assertMemoryUsage(vms.MemoryUsage obj) {
-  assertNotNull(obj);
-  assertInt(obj.externalUsage!);
-  assertInt(obj.heapCapacity!);
-  assertInt(obj.heapUsage!);
-  return obj;
-}
-
-vms.Message assertMessage(vms.Message obj) {
-  assertNotNull(obj);
-  assertInt(obj.index!);
-  assertString(obj.name!);
-  assertString(obj.messageObjectId!);
-  assertInt(obj.size!);
-  return obj;
-}
-
-List<vms.Message> assertListOfMessage(List<vms.Message> list) {
-  for (vms.Message elem in list) {
-    assertMessage(elem);
-  }
-  return list;
-}
-
-vms.NativeFunction assertNativeFunction(vms.NativeFunction obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  return obj;
-}
-
-vms.NullValRef assertNullValRef(vms.NullValRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInstanceKind(obj.kind!);
-  assertInt(obj.identityHashCode!);
-  assertClassRef(obj.classRef!);
-  assertString(obj.valueAsString!);
-  return obj;
-}
-
-List<vms.NullValRef> assertListOfNullValRef(List<vms.NullValRef> list) {
-  for (vms.NullValRef elem in list) {
-    assertNullValRef(elem);
-  }
-  return list;
-}
-
-vms.NullVal assertNullVal(vms.NullVal obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertInstanceKind(obj.kind!);
-  assertInt(obj.identityHashCode!);
-  assertClassRef(obj.classRef!);
-  assertString(obj.valueAsString!);
-  return obj;
-}
-
-vms.ObjRef assertObjRef(vms.ObjRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  return obj;
-}
-
-List<vms.ObjRef> assertListOfObjRef(List<vms.ObjRef> list) {
-  for (vms.ObjRef elem in list) {
-    assertObjRef(elem);
-  }
-  return list;
-}
-
-vms.Obj assertObj(vms.Obj obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  return obj;
-}
-
-vms.Parameter assertParameter(vms.Parameter obj) {
-  assertNotNull(obj);
-  assertInstanceRef(obj.parameterType!);
-  assertBool(obj.fixed!);
-  return obj;
-}
-
-vms.PortList assertPortList(vms.PortList obj) {
-  assertNotNull(obj);
-  assertListOfInstanceRef(obj.ports!);
-  return obj;
-}
-
-vms.ProfileFunction assertProfileFunction(vms.ProfileFunction obj) {
-  assertNotNull(obj);
-  assertString(obj.kind!);
-  assertInt(obj.inclusiveTicks!);
-  assertInt(obj.exclusiveTicks!);
-  assertString(obj.resolvedUrl!);
-  assertDynamic(obj.function!);
-  return obj;
-}
-
-List<vms.ProfileFunction> assertListOfProfileFunction(
-    List<vms.ProfileFunction> list) {
-  for (vms.ProfileFunction elem in list) {
-    assertProfileFunction(elem);
-  }
-  return list;
-}
-
-vms.ProtocolList assertProtocolList(vms.ProtocolList obj) {
-  assertNotNull(obj);
-  assertListOfProtocol(obj.protocols!);
-  return obj;
-}
-
-vms.Protocol assertProtocol(vms.Protocol obj) {
-  assertNotNull(obj);
-  assertString(obj.protocolName!);
-  assertInt(obj.major!);
-  assertInt(obj.minor!);
-  return obj;
-}
-
-List<vms.Protocol> assertListOfProtocol(List<vms.Protocol> list) {
-  for (vms.Protocol elem in list) {
-    assertProtocol(elem);
-  }
-  return list;
-}
-
-vms.ProcessMemoryUsage assertProcessMemoryUsage(vms.ProcessMemoryUsage obj) {
-  assertNotNull(obj);
-  assertProcessMemoryItem(obj.root!);
-  return obj;
-}
-
-vms.ProcessMemoryItem assertProcessMemoryItem(vms.ProcessMemoryItem obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  assertString(obj.description!);
-  assertInt(obj.size!);
-  assertListOfProcessMemoryItem(obj.children!);
-  return obj;
-}
-
-List<vms.ProcessMemoryItem> assertListOfProcessMemoryItem(
-    List<vms.ProcessMemoryItem> list) {
-  for (vms.ProcessMemoryItem elem in list) {
-    assertProcessMemoryItem(elem);
-  }
-  return list;
-}
-
-vms.ReloadReport assertReloadReport(vms.ReloadReport obj) {
-  assertNotNull(obj);
-  assertBool(obj.success!);
-  return obj;
-}
-
-vms.RetainingObject assertRetainingObject(vms.RetainingObject obj) {
-  assertNotNull(obj);
-  assertObjRef(obj.value!);
-  return obj;
-}
-
-List<vms.RetainingObject> assertListOfRetainingObject(
-    List<vms.RetainingObject> list) {
-  for (vms.RetainingObject elem in list) {
-    assertRetainingObject(elem);
-  }
-  return list;
-}
-
-vms.RetainingPath assertRetainingPath(vms.RetainingPath obj) {
-  assertNotNull(obj);
-  assertInt(obj.length!);
-  assertString(obj.gcRootType!);
-  assertListOfRetainingObject(obj.elements!);
-  return obj;
-}
-
-vms.Response assertResponse(vms.Response obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-vms.Sentinel assertSentinel(vms.Sentinel obj) {
-  assertNotNull(obj);
-  assertSentinelKind(obj.kind!);
-  assertString(obj.valueAsString!);
-  return obj;
-}
-
-vms.ScriptRef assertScriptRef(vms.ScriptRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.uri!);
-  return obj;
-}
-
-List<vms.ScriptRef> assertListOfScriptRef(List<vms.ScriptRef> list) {
-  for (vms.ScriptRef elem in list) {
-    assertScriptRef(elem);
-  }
-  return list;
-}
-
-vms.Script assertScript(vms.Script obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.uri!);
-  assertLibraryRef(obj.library!);
-  return obj;
-}
-
-vms.ScriptList assertScriptList(vms.ScriptList obj) {
-  assertNotNull(obj);
-  assertListOfScriptRef(obj.scripts!);
-  return obj;
-}
-
-vms.SourceLocation assertSourceLocation(vms.SourceLocation obj) {
-  assertNotNull(obj);
-  assertScriptRef(obj.script!);
-  assertInt(obj.tokenPos!);
-  return obj;
-}
-
-vms.SourceReport assertSourceReport(vms.SourceReport obj) {
-  assertNotNull(obj);
-  assertListOfSourceReportRange(obj.ranges!);
-  assertListOfScriptRef(obj.scripts!);
-  return obj;
-}
-
-vms.SourceReportCoverage assertSourceReportCoverage(
-    vms.SourceReportCoverage obj) {
-  assertNotNull(obj);
-  assertListOfInt(obj.hits!);
-  assertListOfInt(obj.misses!);
-  return obj;
-}
-
-vms.SourceReportRange assertSourceReportRange(vms.SourceReportRange obj) {
-  assertNotNull(obj);
-  assertInt(obj.scriptIndex!);
-  assertInt(obj.startPos!);
-  assertInt(obj.endPos!);
-  assertBool(obj.compiled!);
-  return obj;
-}
-
-List<vms.SourceReportRange> assertListOfSourceReportRange(
-    List<vms.SourceReportRange> list) {
-  for (vms.SourceReportRange elem in list) {
-    assertSourceReportRange(elem);
-  }
-  return list;
-}
-
-vms.Stack assertStack(vms.Stack obj) {
-  assertNotNull(obj);
-  assertListOfFrame(obj.frames!);
-  assertListOfMessage(obj.messages!);
-  assertBool(obj.truncated!);
-  return obj;
-}
-
-vms.Timeline assertTimeline(vms.Timeline obj) {
-  assertNotNull(obj);
-  assertListOfTimelineEvent(obj.traceEvents!);
-  assertInt(obj.timeOriginMicros!);
-  assertInt(obj.timeExtentMicros!);
-  return obj;
-}
-
-vms.TimelineEvent assertTimelineEvent(vms.TimelineEvent obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-List<vms.TimelineEvent> assertListOfTimelineEvent(
-    List<vms.TimelineEvent> list) {
-  for (vms.TimelineEvent elem in list) {
-    assertTimelineEvent(elem);
-  }
-  return list;
-}
-
-vms.TimelineFlags assertTimelineFlags(vms.TimelineFlags obj) {
-  assertNotNull(obj);
-  assertString(obj.recorderName!);
-  assertListOfString(obj.availableStreams!);
-  assertListOfString(obj.recordedStreams!);
-  return obj;
-}
-
-vms.Timestamp assertTimestamp(vms.Timestamp obj) {
-  assertNotNull(obj);
-  assertInt(obj.timestamp!);
-  return obj;
-}
-
-vms.TypeArgumentsRef assertTypeArgumentsRef(vms.TypeArgumentsRef obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  return obj;
-}
-
-List<vms.TypeArgumentsRef> assertListOfTypeArgumentsRef(
-    List<vms.TypeArgumentsRef> list) {
-  for (vms.TypeArgumentsRef elem in list) {
-    assertTypeArgumentsRef(elem);
-  }
-  return list;
-}
-
-vms.TypeArguments assertTypeArguments(vms.TypeArguments obj) {
-  assertNotNull(obj);
-  assertString(obj.id!);
-  assertString(obj.name!);
-  assertListOfInstanceRef(obj.types!);
-  return obj;
-}
-
-vms.TypeParameters assertTypeParameters(vms.TypeParameters obj) {
-  assertNotNull(obj);
-  assertListOfString(obj.names!);
-  assertTypeArgumentsRef(obj.bounds!);
-  assertTypeArgumentsRef(obj.defaults!);
-  return obj;
-}
-
-vms.UnresolvedSourceLocation assertUnresolvedSourceLocation(
-    vms.UnresolvedSourceLocation obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-vms.UriList assertUriList(vms.UriList obj) {
-  assertNotNull(obj);
-  assertListOfDynamic(obj.uris!);
-  return obj;
-}
-
-vms.Version assertVersion(vms.Version obj) {
-  assertNotNull(obj);
-  assertInt(obj.major!);
-  assertInt(obj.minor!);
-  return obj;
-}
-
-vms.VMRef assertVMRef(vms.VMRef obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  return obj;
-}
-
-List<vms.VMRef> assertListOfVMRef(List<vms.VMRef> list) {
-  for (vms.VMRef elem in list) {
-    assertVMRef(elem);
-  }
-  return list;
-}
-
-vms.VM assertVM(vms.VM obj) {
-  assertNotNull(obj);
-  assertString(obj.name!);
-  assertInt(obj.architectureBits!);
-  assertString(obj.hostCPU!);
-  assertString(obj.operatingSystem!);
-  assertString(obj.targetCPU!);
-  assertString(obj.version!);
-  assertInt(obj.pid!);
-  assertInt(obj.startTime!);
-  assertListOfIsolateRef(obj.isolates!);
-  assertListOfIsolateGroupRef(obj.isolateGroups!);
-  assertListOfIsolateRef(obj.systemIsolates!);
-  assertListOfIsolateGroupRef(obj.systemIsolateGroups!);
-  return obj;
-}
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 8240e95..19d91ff 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
   A library to communicate with a service implementing the Dart VM
   service protocol.
 
-version: 8.2.0
+version: 8.2.1
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
diff --git a/pkg/vm_service/tool/dart/generate_dart.dart b/pkg/vm_service/tool/dart/generate_dart.dart
index d59b1e7..c907438 100644
--- a/pkg/vm_service/tool/dart/generate_dart.dart
+++ b/pkg/vm_service/tool/dart/generate_dart.dart
@@ -901,162 +901,6 @@
     types.where((t) => !t!.skip).forEach((t) => t!.generate(gen));
   }
 
-  void generateAsserts(DartGenerator gen) {
-    gen.out(r'''
-// Copyright (c) 2015, 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.
-
-// This is a generated file.
-
-/// A library for asserting correct responses from the VM Service.
-
-import 'package:vm_service/vm_service.dart' as vms;
-
-dynamic assertNotNull(dynamic obj) {
-  if (obj == null) throw 'assert failed';
-  return obj;
-}
-
-bool assertBool(bool obj) {
-  return obj;
-}
-
-int assertInt(int obj) {
-  return obj;
-}
-
-double assertDouble(double obj) {
-  return obj;
-}
-
-dynamic assertDynamic(dynamic obj) {
-  assertNotNull(obj);
-  return obj;
-}
-
-List<dynamic> assertListOfDynamic(List<dynamic> list) {
-  return list;
-}
-
-List<int> assertListOfInt(List<int> list) {
-  for (int elem in list) {
-    assertInt(elem);
-  }
-  return list;
-}
-
-List<String> assertListOfString(List<String> list) {
-  for (String elem in list) {
-    assertString(elem);
-  }
-  return list;
-}
-
-List<vms.IsolateFlag> assertListOfIsolateFlag(List<vms.IsolateFlag> list) {
-  for (vms.IsolateFlag elem in list) {
-    assertIsolateFlag(elem);
-  }
-  return list;
-}
-
-String assertString(String obj) {
-  if (obj.isEmpty) throw 'expected non-zero length string';
-  return obj;
-}
-
-vms.Success assertSuccess(vms.Success obj) {
-  if (obj.type != 'Success') throw 'expected Success';
-  return obj;
-}
-
-/// Assert PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
-/// PauseException, Resume, BreakpointAdded, BreakpointResolved,
-/// BreakpointRemoved, and Inspect events.
-vms.Event assertDebugEvent(vms.Event event) {
-  assertEvent(event);
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kBreakpointAdded ||
-      event.kind == vms.EventKind.kBreakpointRemoved ||
-      event.kind == vms.EventKind.kBreakpointResolved) {
-    assertBreakpoint(event.breakpoint!);
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint) {
-    for (vms.Breakpoint elem in event.pauseBreakpoints!) {
-      assertBreakpoint(elem);
-    }
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kPauseInterrupted ||
-      event.kind == vms.EventKind.kPauseException ||
-      event.kind == vms.EventKind.kResume) {
-    // For PauseInterrupted events, there will be no top frame if the isolate is
-    // idle (waiting in the message loop).
-    // For the Resume event, the top frame is provided at all times except for
-    // the initial resume event that is delivered when an isolate begins
-    // execution.
-    if (event.topFrame != null ||
-        (event.kind != vms.EventKind.kPauseInterrupted &&
-            event.kind != vms.EventKind.kResume)) {
-      assertFrame(event.topFrame!);
-    }
-  }
-  if (event.kind == vms.EventKind.kPauseException) {
-    assertInstanceRef(event.exception!);
-  }
-  if (event.kind == vms.EventKind.kPauseBreakpoint ||
-      event.kind == vms.EventKind.kPauseInterrupted) {
-    assertBool(event.atAsyncSuspension!);
-  }
-  if (event.kind == vms.EventKind.kInspect) {
-    assertInstanceRef(event.inspectee!);
-  }
-  return event;
-}
-
-/// Assert IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate,
-/// and ServiceExtensionAdded events.
-vms.Event assertIsolateEvent(vms.Event event) {
-  assertEvent(event);
-  if (event.kind == vms.EventKind.kServiceExtensionAdded) {
-    assertString(event.extensionRPC!);
-  }
-  return event;
-}
-
-''');
-    for (Enum e in enums) {
-      e.generateAssert(gen);
-    }
-    for (Type? type in types) {
-      if (type!.name == 'Success') continue;
-      type.generateAssert(gen);
-      if (type.name!.endsWith('Ref') ||
-          [
-            'BoundVariable',
-            'Breakpoint',
-            'ClassHeapStats',
-            'CodeRegion',
-            'ContextElement',
-            'CpuSample',
-            'Flag',
-            'Frame',
-            'InboundReference',
-            'LibraryDependency',
-            'Message',
-            'ProcessMemoryItem',
-            'ProfileFunction',
-            'ProcessMemoryItem',
-            'Protocol',
-            'RetainingObject',
-            'SourceReportRange',
-            'TimelineEvent',
-          ].contains(type.name)) {
-        type.generateListAssert(gen);
-      }
-    }
-  }
-
   void setDefaultValue(String typeName, String fieldName, String defaultValue) {
     types
         .firstWhere((t) => t!.name == typeName)!
diff --git a/pkg/vm_service/tool/generate.dart b/pkg/vm_service/tool/generate.dart
index e91c0f3..2f5f65e 100644
--- a/pkg/vm_service/tool/generate.dart
+++ b/pkg/vm_service/tool/generate.dart
@@ -20,11 +20,12 @@
   String appDirPath = dirname(Platform.script.toFilePath());
 
   // Parse service.md into a model.
-  var file = File(
-      normalize(join(appDirPath, '../../../runtime/vm/service/service.md')));
-  var document = Document();
-  StringBuffer buf = StringBuffer(file.readAsStringSync());
-  var nodes = document.parseLines(buf.toString().split('\n'));
+  final file = File(
+    normalize(join(appDirPath, '../../../runtime/vm/service/service.md')),
+  );
+  final document = Document();
+  final buf = StringBuffer(file.readAsStringSync());
+  final nodes = document.parseLines(buf.toString().split('\n'));
   print('Parsed ${file.path}.');
   print('Service protocol version ${ApiParseUtil.parseVersionString(nodes)}.');
 
@@ -33,7 +34,6 @@
 
   await _generateDart(appDirPath, nodes);
   await _generateJava(appDirPath, nodes);
-  await _generateAsserts(appDirPath, nodes);
 }
 
 Future _generateDart(String appDirPath, List<Node> nodes) async {
@@ -98,34 +98,6 @@
   print('Wrote Java to $srcDirPath.');
 }
 
-Future _generateAsserts(String appDirPath, List<Node> nodes) async {
-  var outDirPath = normalize(join(appDirPath, '..', 'example'));
-  var outDir = Directory(outDirPath);
-  if (!outDir.existsSync()) outDir.createSync(recursive: true);
-  var outputFile = File(join(outDirPath, 'vm_service_assert.dart'));
-  var generator = dart.DartGenerator();
-  dart.api = dart.Api();
-  dart.api.parse(nodes);
-  dart.api.generateAsserts(generator);
-  outputFile.writeAsStringSync(generator.toString());
-  ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
-  if (result.exitCode != 0) {
-    print('dart format: ${result.stdout}\n${result.stderr}');
-    throw result.exitCode;
-  }
-
-  if (_stampPubspecVersion) {
-    // Update the pubspec file.
-    Version version = ApiParseUtil.parseVersionSemVer(nodes);
-    _stampPubspec(version);
-
-    // Validate that the changelog contains an entry for the current version.
-    _checkUpdateChangelog(version);
-  }
-
-  print('Wrote Dart to ${outputFile.path}.');
-}
-
 // Push the major and minor versions into the pubspec.
 void _stampPubspec(Version version) {
   final String pattern = 'version: ';
diff --git a/runtime/bin/builtin_impl_sources.gni b/runtime/bin/builtin_impl_sources.gni
index 470f0b5..ff6a6c3 100644
--- a/runtime/bin/builtin_impl_sources.gni
+++ b/runtime/bin/builtin_impl_sources.gni
@@ -72,4 +72,6 @@
   "file_test.cc",
   "hashmap_test.cc",
   "priority_heap_test.cc",
+  "snapshot_utils_test.cc",
+  "test_utils.cc",
 ]
diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc
index 894884a..92ca3a4 100644
--- a/runtime/bin/file_macos.cc
+++ b/runtime/bin/file_macos.cc
@@ -88,6 +88,7 @@
   switch (type) {
     case kReadOnly:
       prot = PROT_READ;
+      map_flags |= MAP_RESILIENT_CODESIGN;
       break;
     case kReadExecute:
       // Try to allocate near the VM's binary.
diff --git a/runtime/bin/file_test.cc b/runtime/bin/file_test.cc
index 60adf31..91e53e4 100644
--- a/runtime/bin/file_test.cc
+++ b/runtime/bin/file_test.cc
@@ -2,28 +2,18 @@
 // 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.
 
+#include "bin/file.h"
 #include "bin/dartutils.h"
 #include "bin/directory.h"
-#include "bin/file.h"
+#include "bin/test_utils.h"
 #include "platform/assert.h"
 #include "platform/globals.h"
 #include "vm/unit_test.h"
 
 namespace dart {
 
-// Helper method to be able to run the test from the runtime
-// directory, or the top directory.
-static const char* GetFileName(const char* name) {
-  if (bin::File::Exists(NULL, name)) {
-    return name;
-  } else {
-    static const int kRuntimeLength = strlen("runtime/");
-    return name + kRuntimeLength;
-  }
-}
-
 TEST_CASE(Read) {
-  const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
   bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
   EXPECT(file != NULL);
   char buffer[16];
@@ -36,7 +26,7 @@
 }
 
 TEST_CASE(OpenUri_RelativeFilename) {
-  const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
   char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
       strlen(kFilename) * 3 + 1));
   char* t = encoded;
@@ -63,7 +53,8 @@
 }
 
 TEST_CASE(OpenUri_AbsoluteFilename) {
-  const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kRelativeFilename =
+      bin::test::GetFileName("runtime/bin/file_test.cc");
   const char* kFilename = bin::File::GetCanonicalPath(NULL, kRelativeFilename);
   EXPECT_NOTNULL(kFilename);
   char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
@@ -100,7 +91,8 @@
 }
 
 TEST_CASE(OpenUri_ValidUri) {
-  const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kRelativeFilename =
+      bin::test::GetFileName("runtime/bin/file_test.cc");
   const char* kAbsoluteFilename = bin::File::GetCanonicalPath(NULL,
       kRelativeFilename);
   EXPECT_NOTNULL(kAbsoluteFilename);
@@ -132,7 +124,8 @@
 }
 
 TEST_CASE(OpenUri_UriWithSpaces) {
-  const char* kRelativeFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kRelativeFilename =
+      bin::test::GetFileName("runtime/bin/file_test.cc");
   const char* strSystemTemp = bin::Directory::SystemTemp(NULL);
   EXPECT_NOTNULL(strSystemTemp);
   const char* kTempDir = Concat(strSystemTemp, "/foo bar");
@@ -175,7 +168,7 @@
 }
 
 TEST_CASE(OpenUri_InvalidUriPercentEncoding) {
-  const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
   char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
       strlen(kFilename) * 3 + 1));
   char* t = encoded;
@@ -195,7 +188,7 @@
 }
 
 TEST_CASE(OpenUri_TruncatedUriPercentEncoding) {
-  const char* kFilename = GetFileName("runtime/bin/file_test.cc");
+  const char* kFilename = bin::test::GetFileName("runtime/bin/file_test.cc");
   char* encoded = reinterpret_cast<char*>(bin::DartUtils::ScopedCString(
       strlen(kFilename) * 3 + 1));
   char* t = encoded;
@@ -216,7 +209,7 @@
 
 TEST_CASE(FileLength) {
   const char* kFilename =
-      GetFileName("runtime/tests/vm/data/fixed_length_file");
+      bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
   bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
   EXPECT(file != NULL);
   EXPECT_EQ(42, file->Length());
@@ -226,7 +219,7 @@
 TEST_CASE(FilePosition) {
   char buf[42];
   const char* kFilename =
-      GetFileName("runtime/tests/vm/data/fixed_length_file");
+      bin::test::GetFileName("runtime/tests/vm/data/fixed_length_file");
   bin::File* file = bin::File::Open(NULL, kFilename, bin::File::kRead);
   EXPECT(file != NULL);
   EXPECT(file->ReadFully(buf, 12));
diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc
index 4650da4..11b0ead 100644
--- a/runtime/bin/snapshot_utils.cc
+++ b/runtime/bin/snapshot_utils.cc
@@ -13,6 +13,9 @@
 #include "bin/file.h"
 #include "bin/platform.h"
 #include "include/dart_api.h"
+#if defined(DART_TARGET_OS_MACOS)
+#include <platform/mach_o.h>
+#endif
 #include "platform/utils.h"
 
 #define LOG_SECTION_BOUNDARIES false
@@ -23,6 +26,11 @@
 static const int64_t kAppSnapshotHeaderSize = 5 * kInt64Size;
 static const int64_t kAppSnapshotPageSize = 16 * KB;
 
+static const char kMachOAppSnapshotSegmentName[] __attribute__((unused)) =
+    "__CUSTOM";
+static const char kMachOAppSnapshotSectionName[] __attribute__((unused)) =
+    "__dart_app_snap";
+
 class MappedAppSnapshot : public AppSnapshot {
  public:
   MappedAppSnapshot(MappedMemory* vm_snapshot_data,
@@ -233,8 +241,121 @@
   return nullptr;
 }
 
+#if defined(DART_TARGET_OS_MACOS)
+AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElfFromMachO(
+    const char* container_path) {
+  File* file = File::Open(NULL, container_path, File::kRead);
+  if (file == nullptr) {
+    return nullptr;
+  }
+  RefCntReleaseScope<File> rs(file);
+
+  // Ensure file is actually MachO-formatted.
+  if (!IsMachOFormattedBinary(container_path)) {
+    Syslog::PrintErr(
+        "Attempted load target was not formatted as expected: "
+        "expected Mach-O binary.\n");
+    return nullptr;
+  }
+
+  // Parse the first 4bytes and extract the magic number.
+  uint32_t magic;
+  file->SetPosition(0);
+  file->Read(&magic, sizeof(uint32_t));
+
+  const bool is64Bit =
+      magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64;
+  const bool isByteSwapped =
+      magic == mach_o::MH_CIGAM || magic == mach_o::MH_CIGAM_64;
+
+  if (isByteSwapped) {
+    Syslog::PrintErr(
+        "Dart snapshot contained an unexpected binary file layout. "
+        "Expected non-byte swapped header but found a byte-swapped header.\n");
+    return nullptr;
+  }
+
+  file->SetPosition(0);
+
+  // Read in the Mach-O header, which will contain information about all of the
+  // segments in the binary.
+  //
+  // From the header we determine where our special segment is located. This
+  // segment must be named according to the convention captured by
+  // kMachOAppSnapshotSegmentType and kMachOAppSnapshotSegmentName.
+  if (!is64Bit) {
+    Syslog::PrintErr(
+        "Dart snapshot compiled with 32bit architecture. "
+        "Currently only 64bit architectures are supported.\n");
+    return nullptr;
+  } else {
+    mach_o::mach_header_64 header;
+    file->Read(&header, sizeof(header));
+
+    for (uint32_t i = 0; i < header.ncmds; ++i) {
+      mach_o::load_command command;
+      file->Read(&command, sizeof(mach_o::load_command));
+
+      file->SetPosition(file->Position() - sizeof(command));
+      if (command.cmd != mach_o::LC_SEGMENT &&
+          command.cmd != mach_o::LC_SEGMENT_64) {
+        file->SetPosition(file->Position() + command.cmdsize);
+        continue;
+      }
+
+      mach_o::segment_command_64 segment;
+      file->Read(&segment, sizeof(segment));
+
+      for (uint32_t j = 0; j < segment.nsects; ++j) {
+        mach_o::section_64 section;
+        file->Read(&section, sizeof(section));
+
+        if (segment.cmd == mach_o::LC_SEGMENT_64 &&
+            strcmp(section.segname, kMachOAppSnapshotSegmentName) == 0 &&
+            strcmp(section.sectname, kMachOAppSnapshotSectionName) == 0) {
+          // We have to do the loading "by-hand" because we need to set the
+          // snapshot length to a specific length instead of the "rest of the
+          // file", which is the assumption that TryReadAppSnapshotElf makes.
+          const char* error = nullptr;
+          const uint8_t* vm_data_buffer = nullptr;
+          const uint8_t* vm_instructions_buffer = nullptr;
+          const uint8_t* isolate_data_buffer = nullptr;
+          const uint8_t* isolate_instructions_buffer = nullptr;
+
+          std::unique_ptr<uint8_t[]> snapshot(new uint8_t[section.size]);
+          file->SetPosition(section.offset);
+          file->Read(snapshot.get(), sizeof(uint8_t) * section.size);
+
+          Dart_LoadedElf* handle = Dart_LoadELF_Memory(
+              snapshot.get(), section.size, &error, &vm_data_buffer,
+              &vm_instructions_buffer, &isolate_data_buffer,
+              &isolate_instructions_buffer);
+
+          if (handle == nullptr) {
+            Syslog::PrintErr("Loading failed: %s\n", error);
+            return nullptr;
+          }
+
+          return new ElfAppSnapshot(handle, vm_data_buffer,
+                                    vm_instructions_buffer, isolate_data_buffer,
+                                    isolate_instructions_buffer);
+        }
+      }
+    }
+  }
+
+  return nullptr;
+}
+#endif  // defined(DART_TARGET_OS_MACOS)
+
 AppSnapshot* Snapshot::TryReadAppendedAppSnapshotElf(
     const char* container_path) {
+#if defined(DART_TARGET_OS_MACOS)
+  if (IsMachOFormattedBinary(container_path)) {
+    return TryReadAppendedAppSnapshotElfFromMachO(container_path);
+  }
+#endif
+
   File* file = File::Open(NULL, container_path, File::kRead);
   if (file == nullptr) {
     return nullptr;
@@ -329,6 +450,29 @@
 
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
 
+#if defined(DART_TARGET_OS_MACOS)
+bool Snapshot::IsMachOFormattedBinary(const char* filename) {
+  File* file = File::Open(NULL, filename, File::kRead);
+  if (file == nullptr) {
+    return false;
+  }
+  RefCntReleaseScope<File> rs(file);
+
+  // Ensure the file is long enough to even contain the magic bytes.
+  if (file->Length() < 4) {
+    return false;
+  }
+
+  // Parse the first 4bytes and check the magic numbers.
+  uint32_t magic;
+  file->SetPosition(0);
+  file->Read(&magic, sizeof(uint32_t));
+
+  return magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64 ||
+         magic == mach_o::MH_MAGIC || magic == mach_o::MH_CIGAM;
+}
+#endif  // defined(DART_TARGET_OS_MACOS)
+
 AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
                                           bool force_load_elf_from_memory,
                                           bool decode_uri) {
diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h
index ed96fb1..f26f7b2 100644
--- a/runtime/bin/snapshot_utils.h
+++ b/runtime/bin/snapshot_utils.h
@@ -38,6 +38,10 @@
   // an ELF binary). May report false negatives.
   static bool IsAOTSnapshot(const char* snapshot_filename);
 
+#if defined(DART_TARGET_OS_MACOS)
+  static bool IsMachOFormattedBinary(const char* container_path);
+#endif
+
   static AppSnapshot* TryReadAppendedAppSnapshotElf(const char* container_path);
   static AppSnapshot* TryReadAppSnapshot(
       const char* script_uri,
@@ -54,6 +58,11 @@
                                intptr_t isolate_instructions_size);
 
  private:
+#if defined(DART_TARGET_OS_MACOS)
+  static AppSnapshot* TryReadAppendedAppSnapshotElfFromMachO(
+      const char* container_path);
+#endif
+
   DISALLOW_ALLOCATION();
   DISALLOW_IMPLICIT_CONSTRUCTORS(Snapshot);
 };
diff --git a/runtime/bin/snapshot_utils_test.cc b/runtime/bin/snapshot_utils_test.cc
new file mode 100644
index 0000000..7b726fd
--- /dev/null
+++ b/runtime/bin/snapshot_utils_test.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "bin/snapshot_utils.h"
+#include "bin/file.h"
+#include "bin/test_utils.h"
+#include "platform/assert.h"
+#include "platform/globals.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+#if defined(DART_TARGET_OS_MACOS)
+TEST_CASE(CanDetectMachOFiles) {
+  const char* kMachO32BitLittleEndianFilename =
+      bin::test::GetFileName("runtime/tests/vm/data/macho_32bit_little_endian");
+  const char* kMachO64BitLittleEndianFilename =
+      bin::test::GetFileName("runtime/tests/vm/data/macho_64bit_little_endian");
+  const char* kMachO32BitBigEndianFilename =
+      bin::test::GetFileName("runtime/tests/vm/data/macho_32bit_big_endian");
+  const char* kMachO64BitBigEndianFilename =
+      bin::test::GetFileName("runtime/tests/vm/data/macho_64bit_big_endian");
+
+  EXPECT(
+      bin::Snapshot::IsMachOFormattedBinary(kMachO32BitLittleEndianFilename));
+  EXPECT(
+      bin::Snapshot::IsMachOFormattedBinary(kMachO64BitLittleEndianFilename));
+  EXPECT(bin::Snapshot::IsMachOFormattedBinary(kMachO32BitBigEndianFilename));
+  EXPECT(bin::Snapshot::IsMachOFormattedBinary(kMachO64BitBigEndianFilename));
+
+  const char* kFilename =
+      bin::test::GetFileName("runtime/bin/snapshot_utils_test.cc");
+  EXPECT(!bin::Snapshot::IsMachOFormattedBinary(kFilename));
+}
+#endif
+
+}  // namespace dart
diff --git a/runtime/bin/test_utils.cc b/runtime/bin/test_utils.cc
new file mode 100644
index 0000000..66d7fd9
--- /dev/null
+++ b/runtime/bin/test_utils.cc
@@ -0,0 +1,23 @@
+// 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.
+
+#include "bin/test_utils.h"
+#include "bin/file.h"
+
+namespace dart {
+namespace bin {
+namespace test {
+
+const char* GetFileName(const char* name) {
+  if (bin::File::Exists(NULL, name)) {
+    return name;
+  } else {
+    static const int kRuntimeLength = strlen("runtime/");
+    return name + kRuntimeLength;
+  }
+}
+
+}  // namespace test
+}  // namespace bin
+}  // namespace dart
diff --git a/runtime/bin/test_utils.h b/runtime/bin/test_utils.h
new file mode 100644
index 0000000..9291819
--- /dev/null
+++ b/runtime/bin/test_utils.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef RUNTIME_BIN_TEST_UTILS_H_
+#define RUNTIME_BIN_TEST_UTILS_H_
+
+namespace dart {
+namespace bin {
+namespace test {
+
+// Helper method to be able to run the test from the runtime
+// directory, or the top directory.
+const char* GetFileName(const char* name);
+
+}  // namespace test
+}  // namespace bin
+}  // namespace dart
+
+#endif  // RUNTIME_BIN_TEST_UTILS_H_
diff --git a/runtime/platform/mach_o.h b/runtime/platform/mach_o.h
new file mode 100644
index 0000000..d5bf913
--- /dev/null
+++ b/runtime/platform/mach_o.h
@@ -0,0 +1,104 @@
+// 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.
+
+#ifndef RUNTIME_PLATFORM_MACH_O_H_
+#define RUNTIME_PLATFORM_MACH_O_H_
+
+#include <platform/globals.h>
+
+namespace dart {
+
+namespace mach_o {
+
+#pragma pack(push, 1)
+
+typedef int cpu_type_t;
+typedef int cpu_subtype_t;
+typedef int vm_prot_t;
+
+struct mach_header {
+  uint32_t magic;
+  cpu_type_t cputype;
+  cpu_subtype_t cpusubtype;
+  uint32_t filetype;
+  uint32_t ncmds;
+  uint32_t sizeofcmds;
+  uint32_t flags;
+};
+
+static const uint32_t MH_MAGIC = 0xfeedface;
+static const uint32_t MH_CIGAM = 0xcefaedfe;
+
+struct mach_header_64 {
+  uint32_t magic;
+  cpu_type_t cputype;
+  cpu_subtype_t cpusubtype;
+  uint32_t filetype;
+  uint32_t ncmds;
+  uint32_t sizeofcmds;
+  uint32_t flags;
+  uint32_t reserved;
+};
+
+static const uint32_t MH_MAGIC_64 = 0xfeedfacf;
+static const uint32_t MH_CIGAM_64 = 0xcffaedfe;
+
+struct load_command {
+  uint32_t cmd;
+  uint32_t cmdsize;
+};
+
+static const uint32_t LC_SEGMENT = 0x1;
+static const uint32_t LC_SEGMENT_64 = 0x19;
+
+struct section {
+  char sectname[16];
+  char segname[16];
+  uint32_t addr;
+  uint32_t size;
+  uint32_t offset;
+  uint32_t align;
+  uint32_t reloff;
+  uint32_t nreloc;
+  uint32_t flags;
+  uint32_t reserved1;
+  uint32_t reserved2;
+};
+
+struct section_64 {
+  char sectname[16];
+  char segname[16];
+  uint64_t addr;
+  uint64_t size;
+  uint32_t offset;
+  uint32_t align;
+  uint32_t reloff;
+  uint32_t nreloc;
+  uint32_t flags;
+  uint32_t reserved1;
+  uint32_t reserved2;
+  uint32_t reserved3;
+};
+
+struct segment_command_64 {
+  uint32_t cmd;
+  uint32_t cmdsize;
+  char segname[16];
+  uint64_t vmaddr;
+  uint64_t vmsize;
+  uint64_t fileoff;
+  uint64_t filesize;
+  vm_prot_t maxprot;
+  vm_prot_t initprot;
+  uint32_t nsects;
+  uint32_t flags;
+};
+
+#pragma pack(pop)
+
+}  // namespace mach_o
+
+}  // namespace dart
+
+#endif  // RUNTIME_PLATFORM_MACH_O_H_
diff --git a/runtime/tests/vm/data/macho_32bit_big_endian b/runtime/tests/vm/data/macho_32bit_big_endian
new file mode 100644
index 0000000..c271589
--- /dev/null
+++ b/runtime/tests/vm/data/macho_32bit_big_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_32bit_little_endian b/runtime/tests/vm/data/macho_32bit_little_endian
new file mode 100644
index 0000000..7c7003f
--- /dev/null
+++ b/runtime/tests/vm/data/macho_32bit_little_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_64bit_big_endian b/runtime/tests/vm/data/macho_64bit_big_endian
new file mode 100644
index 0000000..95b3475
--- /dev/null
+++ b/runtime/tests/vm/data/macho_64bit_big_endian
Binary files differ
diff --git a/runtime/tests/vm/data/macho_64bit_little_endian b/runtime/tests/vm/data/macho_64bit_little_endian
new file mode 100644
index 0000000..fcd8a04c
--- /dev/null
+++ b/runtime/tests/vm/data/macho_64bit_little_endian
Binary files differ
diff --git a/tools/VERSION b/tools/VERSION
index cafe4e0..d7d6b9c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 194
+PRERELEASE 195
 PRERELEASE_PATCH 0
\ No newline at end of file