Version 2.18.0-40.0.dev

Merge commit 'd2126a3d5b05889e52d99c1cbf00e96d4eafd017' into 'dev'
diff --git a/DEPS b/DEPS
index a2c215f..f68d086 100644
--- a/DEPS
+++ b/DEPS
@@ -185,7 +185,7 @@
   "download_chrome": False,
   "chrome_tag": "91",
   "download_firefox": False,
-  "firefox_tag": "67",
+  "firefox_tag": "98.0.2",
 }
 
 gclient_gn_args_file = Var("dart_root") + '/build/config/gclient_args.gni'
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index ccaf338..ac929fb 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -1952,7 +1952,7 @@
       FunctionNode parameters = new FunctionNode(null,
           typeParameters: typeDefinitions,
           positionalParameters: definitions.keys
-              .map((name) =>
+              .map<VariableDeclaration>((name) =>
                   new VariableDeclarationImpl(name, 0, type: definitions[name])
                     ..fileOffset = cls?.fileOffset ??
                         extension?.fileOffset ??
diff --git a/pkg/front_end/lib/src/testing/analysis_helper.dart b/pkg/front_end/lib/src/testing/analysis_helper.dart
new file mode 100644
index 0000000..77a3b77
--- /dev/null
+++ b/pkg/front_end/lib/src/testing/analysis_helper.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:front_end/src/api_prototype/compiler_options.dart';
+import 'package:front_end/src/api_prototype/kernel_generator.dart';
+import 'package:front_end/src/api_prototype/terminal_color_support.dart';
+import 'package:front_end/src/compute_platform_binaries_location.dart';
+import 'package:front_end/src/fasta/command_line_reporting.dart';
+import 'package:front_end/src/fasta/fasta_codes.dart';
+import 'package:front_end/src/fasta/kernel/redirecting_factory_body.dart';
+import 'package:front_end/src/kernel_generator_impl.dart';
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/type_environment.dart';
+
+typedef PerformAnalysisFunction = void Function(
+    DiagnosticMessageHandler onDiagnostic, Component component);
+typedef UriFilter = bool Function(Uri uri);
+
+Future<void> runAnalysis(
+    List<Uri> entryPoints, PerformAnalysisFunction performAnalysis) async {
+  CompilerOptions options = new CompilerOptions();
+  options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
+  options.packagesFileUri = Uri.base.resolve('.dart_tool/package_config.json');
+
+  options.onDiagnostic = (DiagnosticMessage message) {
+    printDiagnosticMessage(message, print);
+  };
+  InternalCompilerResult compilerResult = await kernelForProgramInternal(
+          entryPoints.first, options,
+          retainDataForTesting: true,
+          requireMain: false,
+          additionalSources: entryPoints.skip(1).toList())
+      as InternalCompilerResult;
+
+  performAnalysis(options.onDiagnostic!, compilerResult.component!);
+}
+
+class StaticTypeVisitorBase extends RecursiveVisitor {
+  final TypeEnvironment typeEnvironment;
+
+  StaticTypeContext? staticTypeContext;
+
+  StaticTypeVisitorBase(Component component, ClassHierarchy classHierarchy)
+      : typeEnvironment =
+            new TypeEnvironment(new CoreTypes(component), classHierarchy);
+
+  @override
+  void visitProcedure(Procedure node) {
+    if (node.kind == ProcedureKind.Factory && isRedirectingFactory(node)) {
+      // Don't visit redirecting factories.
+      return;
+    }
+    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+    super.visitProcedure(node);
+    staticTypeContext = null;
+  }
+
+  @override
+  void visitField(Field node) {
+    if (isRedirectingFactoryField(node)) {
+      // Skip synthetic .dill members.
+      return;
+    }
+    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+    super.visitField(node);
+    staticTypeContext = null;
+  }
+
+  @override
+  void visitConstructor(Constructor node) {
+    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+    super.visitConstructor(node);
+    staticTypeContext = null;
+  }
+}
+
+class AnalysisVisitor extends StaticTypeVisitorBase {
+  final DiagnosticMessageHandler onDiagnostic;
+  final Component component;
+  final UriFilter? uriFilter;
+  late final AnalysisInterface interface;
+
+  Map<String, Map<String, List<FormattedMessage>>> _messages = {};
+
+  AnalysisVisitor(this.onDiagnostic, this.component, this.uriFilter)
+      : super(component,
+            new ClassHierarchy(component, new CoreTypes(component))) {
+    interface = new AnalysisInterface(this);
+  }
+
+  @override
+  void visitLibrary(Library node) {
+    if (uriFilter != null) {
+      if (uriFilter!(node.importUri)) {
+        super.visitLibrary(node);
+      }
+    } else {
+      super.visitLibrary(node);
+    }
+  }
+
+  void registerMessage(TreeNode node, String message) {
+    Location location = node.location!;
+    Uri uri = location.file;
+    String uriString = relativizeUri(uri)!;
+    Map<String, List<FormattedMessage>> actualMap = _messages.putIfAbsent(
+        uriString, () => <String, List<FormattedMessage>>{});
+    if (uri.isScheme('org-dartlang-sdk')) {
+      location = new Location(Uri.base.resolve(uri.path.substring(1)),
+          location.line, location.column);
+    }
+    LocatedMessage locatedMessage = templateUnspecified
+        .withArguments(message)
+        .withLocation(uri, node.fileOffset, noLength);
+    FormattedMessage diagnosticMessage = locatedMessage.withFormatting(
+        format(locatedMessage, Severity.warning,
+            location: location, uriToSource: component.uriToSource),
+        location.line,
+        location.column,
+        Severity.warning,
+        []);
+    actualMap
+        .putIfAbsent(message, () => <FormattedMessage>[])
+        .add(diagnosticMessage);
+  }
+
+  void forEachMessage(
+      void Function(String, Map<String, List<FormattedMessage>>) f) {
+    _messages.forEach(f);
+  }
+
+  Map<String, List<FormattedMessage>>? getMessagesForUri(String uri) {
+    return _messages[uri];
+  }
+
+  void printMessages() {
+    forEachMessage((String uri, Map<String, List<FormattedMessage>> messages) {
+      messages.forEach((String message, List<FormattedMessage> actualMessages) {
+        for (FormattedMessage message in actualMessages) {
+          onDiagnostic(message);
+        }
+      });
+    });
+  }
+}
+
+/// Convenience interface for performing analysis.
+class AnalysisInterface {
+  final AnalysisVisitor _visitor;
+  final ComponentLookup _componentLookup;
+
+  AnalysisInterface(this._visitor)
+      : _componentLookup = new ComponentLookup(_visitor.component);
+
+  void reportMessage(TreeNode node, String message) {
+    _visitor.registerMessage(node, message);
+  }
+
+  InterfaceType createInterfaceType(String className,
+      {String? uri, List<DartType>? typeArguments}) {
+    LibraryLookup libraryLookup =
+        _componentLookup.getLibrary(Uri.parse(uri ?? 'dart:core'));
+    ClassLookup classLookup = libraryLookup.getClass(className);
+    Class cls = classLookup.cls;
+    return new InterfaceType(
+        cls,
+        Nullability.nonNullable,
+        typeArguments ??
+            new List<DartType>.generate(
+                cls.typeParameters.length, (index) => const DynamicType()));
+  }
+
+  bool isSubtypeOf(DartType subtype, DartType supertype) {
+    return _visitor.typeEnvironment
+        .isSubtypeOf(subtype, supertype, SubtypeCheckMode.withNullabilities);
+  }
+}
+
+typedef GeneralAnalysisFunction = void Function(
+    TreeNode node, AnalysisInterface interface);
+
+/// Generalized analyzer that uses a single [GeneralAnalysisFunction] on all
+/// [TreeNode]s.
+class GeneralAnalyzer extends AnalysisVisitor {
+  final GeneralAnalysisFunction analyzer;
+
+  GeneralAnalyzer(DiagnosticMessageHandler onDiagnostic, Component component,
+      bool Function(Uri uri)? analyzedUrisFilter, this.analyzer)
+      : super(onDiagnostic, component, analyzedUrisFilter);
+
+  @override
+  void defaultTreeNode(TreeNode node) {
+    analyzer(node, interface);
+    super.defaultTreeNode(node);
+  }
+}
+
+/// Returns a function that will perform [analysisFunction] on [TreeNode]s
+/// in a component, using [uriFilter] to filter which libraries that will be
+/// visited.
+PerformAnalysisFunction performGeneralAnalysis(
+    UriFilter? uriFilter, GeneralAnalysisFunction analysisFunction) {
+  return (DiagnosticMessageHandler onDiagnostic, Component component) {
+    GeneralAnalyzer analyzer = new GeneralAnalyzer(
+        onDiagnostic, component, uriFilter, analysisFunction);
+    component.accept(analyzer);
+    analyzer.printMessages();
+  };
+}
+
+/// Helper class for looking up libraries in a [Component].
+class ComponentLookup {
+  final Component _component;
+
+  ComponentLookup(this._component);
+
+  Map<Uri, LibraryLookup>? _libraries;
+
+  LibraryLookup getLibrary(Uri uri) {
+    LibraryLookup? libraryLookup = (_libraries ??= new Map.fromIterable(
+        _component.libraries,
+        key: (library) => library.importUri,
+        value: (library) => new LibraryLookup(library)))[uri];
+    if (libraryLookup == null) {
+      throw "Couldn't find library for '$uri'.";
+    }
+    return libraryLookup;
+  }
+}
+
+/// Helper class for looking up classes and members in a [Library].
+// TODO(johnniwinther): Support member lookup.
+class LibraryLookup {
+  final Library library;
+
+  LibraryLookup(this.library);
+
+  Map<String, ClassLookup>? _classes;
+
+  ClassLookup getClass(String name) {
+    ClassLookup? classLookup = (_classes ??= new Map.fromIterable(
+        library.classes,
+        key: (cls) => cls.name,
+        value: (cls) => new ClassLookup(cls)))[name];
+    if (classLookup == null) {
+      throw "Couldn't find class '$name' in ${library.importUri}.";
+    }
+    return classLookup;
+  }
+}
+
+/// Helper class for looking up members in a [Class].
+// TODO(johnniwinther): Support member lookup.
+class ClassLookup {
+  final Class cls;
+
+  ClassLookup(this.cls);
+}
+
+/// Entry points used for analyzing cfe source code.
+// TODO(johnniwinther): Update this to include all files in the cfe, and not
+//  only those reachable from 'compiler.dart'.
+final List<Uri> cfeOnlyEntryPoints = [
+  Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart')
+];
+
+/// Filter function used to only analyze cfe source code.
+bool cfeOnly(Uri uri) {
+  String text = '$uri';
+  for (String path in [
+    'package:_fe_analyzer_shared/',
+    'package:kernel/',
+    'package:front_end/',
+  ]) {
+    if (text.startsWith(path)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/// Entry points used for analyzing cfe and backend source code.
+// TODO(johnniwinther): Update this to include all files in cfe and backends,
+//  and not only those reachable from these entry points.
+List<Uri> cfeAndBackendsEntryPoints = [
+  Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'),
+  Uri.base.resolve('pkg/vm/lib/kernel_front_end.dart'),
+  Uri.base.resolve('pkg/compiler/bin/dart2js.dart'),
+  Uri.base.resolve('pkg/dev_compiler/bin/dartdevc.dart'),
+  Uri.base.resolve('pkg/frontend_server/bin/frontend_server_starter.dart'),
+];
+
+/// Filter function used to only analyze cfe and backend source code.
+bool cfeAndBackends(Uri uri) {
+  String text = '$uri';
+  for (String path in [
+    'package:_fe_analyzer_shared/',
+    'package:kernel/',
+    'package:front_end/',
+    'package:frontend_server/',
+    'package:vm/',
+    'package:compiler/',
+    'package:dartdevc/',
+    'package:_js_interop_checks/',
+  ]) {
+    if (text.startsWith(path)) {
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index bb4b5ed..5590566 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -645,6 +645,7 @@
 interleaved
 intermediate
 internet
+interop
 interpolations
 interrupted
 intersects
diff --git a/pkg/front_end/test/static_types/analysis_helper.dart b/pkg/front_end/test/static_types/analysis_helper.dart
deleted file mode 100644
index ab60ab7..0000000
--- a/pkg/front_end/test/static_types/analysis_helper.dart
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:convert' as json;
-import 'dart:io';
-
-import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
-import 'package:expect/expect.dart';
-import 'package:front_end/src/api_prototype/compiler_options.dart';
-import 'package:front_end/src/api_prototype/kernel_generator.dart';
-import 'package:front_end/src/api_prototype/terminal_color_support.dart';
-import 'package:front_end/src/compute_platform_binaries_location.dart';
-import 'package:front_end/src/fasta/command_line_reporting.dart';
-import 'package:front_end/src/fasta/fasta_codes.dart';
-import 'package:front_end/src/fasta/kernel/redirecting_factory_body.dart';
-import 'package:front_end/src/kernel_generator_impl.dart';
-import 'package:kernel/ast.dart';
-import 'package:kernel/class_hierarchy.dart';
-import 'package:kernel/core_types.dart';
-import 'package:kernel/type_environment.dart';
-
-Future<void> run(Uri entryPoint, String allowedListPath,
-    {bool verbose = false,
-    bool generate = false,
-    bool Function(Uri uri)? analyzedUrisFilter}) async {
-  CompilerOptions options = new CompilerOptions();
-  options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
-
-  options.onDiagnostic = (DiagnosticMessage message) {
-    printDiagnosticMessage(message, print);
-  };
-  InternalCompilerResult compilerResult = await kernelForProgramInternal(
-          entryPoint, options, retainDataForTesting: true, requireMain: false)
-      as InternalCompilerResult;
-
-  new DynamicVisitor(options.onDiagnostic!, compilerResult.component!,
-          allowedListPath, analyzedUrisFilter)
-      .run(verbose: verbose, generate: generate);
-}
-
-class StaticTypeVisitorBase extends RecursiveVisitor {
-  final TypeEnvironment typeEnvironment;
-
-  StaticTypeContext? staticTypeContext;
-
-  StaticTypeVisitorBase(Component component, ClassHierarchy classHierarchy)
-      : typeEnvironment =
-            new TypeEnvironment(new CoreTypes(component), classHierarchy);
-
-  @override
-  void visitProcedure(Procedure node) {
-    if (node.kind == ProcedureKind.Factory && isRedirectingFactory(node)) {
-      // Don't visit redirecting factories.
-      return;
-    }
-    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
-    super.visitProcedure(node);
-    staticTypeContext = null;
-  }
-
-  @override
-  void visitField(Field node) {
-    if (isRedirectingFactoryField(node)) {
-      // Skip synthetic .dill members.
-      return;
-    }
-    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
-    super.visitField(node);
-    staticTypeContext = null;
-  }
-
-  @override
-  void visitConstructor(Constructor node) {
-    staticTypeContext = new StaticTypeContext(node, typeEnvironment);
-    super.visitConstructor(node);
-    staticTypeContext = null;
-  }
-}
-
-class DynamicVisitor extends StaticTypeVisitorBase {
-  // TODO(johnniwinther): Enable this when it is less noisy.
-  static const bool checkReturnTypes = false;
-
-  final DiagnosticMessageHandler onDiagnostic;
-  final Component component;
-  final String? _allowedListPath;
-  final bool Function(Uri uri)? analyzedUrisFilter;
-
-  Map _expectedJson = {};
-  Map<String, Map<String, List<FormattedMessage>>> _actualMessages = {};
-
-  DynamicVisitor(this.onDiagnostic, this.component, this._allowedListPath,
-      this.analyzedUrisFilter)
-      : super(
-            component, new ClassHierarchy(component, new CoreTypes(component)));
-
-  void run({bool verbose = false, bool generate = false}) {
-    if (!generate && _allowedListPath != null) {
-      File file = new File(_allowedListPath!);
-      if (file.existsSync()) {
-        try {
-          _expectedJson = json.jsonDecode(file.readAsStringSync());
-        } catch (e) {
-          Expect.fail('Error reading allowed list from $_allowedListPath: $e');
-        }
-      }
-    }
-    component.accept(this);
-    if (generate && _allowedListPath != null) {
-      Map<String, Map<String, int>> actualJson = {};
-      _actualMessages.forEach(
-          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
-        Map<String, int> map = {};
-        actualMessagesMap
-            .forEach((String message, List<FormattedMessage> actualMessages) {
-          map[message] = actualMessages.length;
-        });
-        actualJson[uri] = map;
-      });
-
-      new File(_allowedListPath!).writeAsStringSync(
-          new json.JsonEncoder.withIndent('  ').convert(actualJson));
-      return;
-    }
-
-    int errorCount = 0;
-    _expectedJson.forEach((uri, expectedMessages) {
-      Map<String, List<FormattedMessage>>? actualMessagesMap =
-          _actualMessages[uri];
-      if (actualMessagesMap == null) {
-        print("Error: Allowed-listing of uri '$uri' isn't used. "
-            "Remove it from the allowed-list.");
-        errorCount++;
-      } else {
-        expectedMessages.forEach((expectedMessage, expectedCount) {
-          List<FormattedMessage>? actualMessages =
-              actualMessagesMap[expectedMessage];
-          if (actualMessages == null) {
-            print("Error: Allowed-listing of message '$expectedMessage' "
-                "in uri '$uri' isn't used. Remove it from the allowed-list.");
-            errorCount++;
-          } else {
-            int actualCount = actualMessages.length;
-            if (actualCount != expectedCount) {
-              print("Error: Unexpected count of allowed message "
-                  "'$expectedMessage' in uri '$uri'. "
-                  "Expected $expectedCount, actual $actualCount:");
-              print(
-                  '----------------------------------------------------------');
-              for (FormattedMessage message in actualMessages) {
-                onDiagnostic(message);
-              }
-              print(
-                  '----------------------------------------------------------');
-              errorCount++;
-            }
-          }
-        });
-        actualMessagesMap
-            .forEach((String message, List<FormattedMessage> actualMessages) {
-          if (!expectedMessages.containsKey(message)) {
-            for (FormattedMessage message in actualMessages) {
-              onDiagnostic(message);
-              errorCount++;
-            }
-          }
-        });
-      }
-    });
-    _actualMessages.forEach(
-        (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
-      if (!_expectedJson.containsKey(uri)) {
-        actualMessagesMap
-            .forEach((String message, List<FormattedMessage> actualMessages) {
-          for (FormattedMessage message in actualMessages) {
-            onDiagnostic(message);
-            errorCount++;
-          }
-        });
-      }
-    });
-    if (errorCount != 0) {
-      print('$errorCount error(s) found.');
-      print("""
-
-********************************************************************************
-*  Unexpected dynamic invocations found by test:
-*
-*    ${relativizeUri(Platform.script)}
-*
-*  Please address the reported errors, or, if the errors are as expected, run
-*
-*    dart ${relativizeUri(Platform.script)} -g
-*
-*  to update the expectation file.
-********************************************************************************
-""");
-      exit(-1);
-    }
-    if (verbose) {
-      _actualMessages.forEach(
-          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
-        actualMessagesMap
-            .forEach((String message, List<FormattedMessage> actualMessages) {
-          for (FormattedMessage message in actualMessages) {
-            // TODO(johnniwinther): It is unnecessarily complicated to just
-            // add ' (allowed)' to an existing message!
-            LocatedMessage locatedMessage = message.locatedMessage;
-            String newMessageText =
-                '${locatedMessage.messageObject.problemMessage} (allowed)';
-            message = locatedMessage.withFormatting(
-                format(
-                    new LocatedMessage(
-                        locatedMessage.uri,
-                        locatedMessage.charOffset,
-                        locatedMessage.length,
-                        new Message(locatedMessage.messageObject.code,
-                            problemMessage: newMessageText,
-                            correctionMessage:
-                                locatedMessage.messageObject.correctionMessage,
-                            arguments: locatedMessage.messageObject.arguments)),
-                    Severity.warning,
-                    location: new Location(
-                        message.uri!, message.line, message.column),
-                    uriToSource: component.uriToSource),
-                message.line,
-                message.column,
-                Severity.warning,
-                []);
-            onDiagnostic(message);
-          }
-        });
-      });
-    } else {
-      int total = 0;
-      _actualMessages.forEach(
-          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
-        int count = 0;
-        actualMessagesMap
-            .forEach((String message, List<FormattedMessage> actualMessages) {
-          count += actualMessages.length;
-        });
-
-        print('${count} error(s) allowed in $uri');
-        total += count;
-      });
-      if (total > 0) {
-        print('${total} error(s) allowed in total.');
-      }
-    }
-  }
-
-  @override
-  void visitLibrary(Library node) {
-    if (analyzedUrisFilter != null) {
-      if (analyzedUrisFilter!(node.importUri)) {
-        super.visitLibrary(node);
-      }
-    } else {
-      super.visitLibrary(node);
-    }
-  }
-
-  @override
-  void visitDynamicGet(DynamicGet node) {
-    registerError(node, "Dynamic access of '${node.name}'.");
-    super.visitDynamicGet(node);
-  }
-
-  @override
-  void visitDynamicSet(DynamicSet node) {
-    registerError(node, "Dynamic update to '${node.name}'.");
-    super.visitDynamicSet(node);
-  }
-
-  @override
-  void visitDynamicInvocation(DynamicInvocation node) {
-    registerError(node, "Dynamic invocation of '${node.name}'.");
-    super.visitDynamicInvocation(node);
-  }
-
-  @override
-  void visitFunctionDeclaration(FunctionDeclaration node) {
-    if (checkReturnTypes && node.function.returnType is DynamicType) {
-      registerError(node, "Dynamic return type");
-    }
-    super.visitFunctionDeclaration(node);
-  }
-
-  @override
-  void visitFunctionExpression(FunctionExpression node) {
-    if (checkReturnTypes && node.function.returnType is DynamicType) {
-      registerError(node, "Dynamic return type");
-    }
-    super.visitFunctionExpression(node);
-  }
-
-  @override
-  void visitProcedure(Procedure node) {
-    if (checkReturnTypes &&
-        node.function.returnType is DynamicType &&
-        node.name.text != 'noSuchMethod') {
-      registerError(node, "Dynamic return type on $node");
-    }
-    super.visitProcedure(node);
-  }
-
-  void registerError(TreeNode node, String message) {
-    Location location = node.location!;
-    Uri uri = location.file;
-    String uriString = relativizeUri(uri)!;
-    Map<String, List<FormattedMessage>> actualMap = _actualMessages.putIfAbsent(
-        uriString, () => <String, List<FormattedMessage>>{});
-    if (uri.isScheme('org-dartlang-sdk')) {
-      location = new Location(Uri.base.resolve(uri.path.substring(1)),
-          location.line, location.column);
-    }
-    LocatedMessage locatedMessage = templateUnspecified
-        .withArguments(message)
-        .withLocation(uri, node.fileOffset, noLength);
-    FormattedMessage diagnosticMessage = locatedMessage.withFormatting(
-        format(locatedMessage, Severity.warning,
-            location: location, uriToSource: component.uriToSource),
-        location.line,
-        location.column,
-        Severity.warning,
-        []);
-    actualMap
-        .putIfAbsent(message, () => <FormattedMessage>[])
-        .add(diagnosticMessage);
-  }
-}
diff --git a/pkg/front_end/test/static_types/cfe_dynamic_test.dart b/pkg/front_end/test/static_types/cfe_dynamic_test.dart
index 06d08c1..260823d 100644
--- a/pkg/front_end/test/static_types/cfe_dynamic_test.dart
+++ b/pkg/front_end/test/static_types/cfe_dynamic_test.dart
@@ -2,27 +2,81 @@
 // 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 'analysis_helper.dart';
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'verifying_analysis.dart';
 
-/// Filter function used to only analysis cfe source code.
-bool cfeOnly(Uri uri) {
-  String text = '$uri';
-  for (String path in [
-    'package:_fe_analyzer_shared/',
-    'package:kernel/',
-    'package:front_end/',
-  ]) {
-    if (text.startsWith(path)) {
-      return true;
-    }
-  }
-  return false;
-}
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:kernel/ast.dart';
 
 Future<void> main(List<String> args) async {
-  await run(Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'),
-      'pkg/front_end/test/static_types/cfe_allowed.json',
+  await run(
+      cfeOnlyEntryPoints, 'pkg/front_end/test/static_types/cfe_allowed.json',
       analyzedUrisFilter: cfeOnly,
       verbose: args.contains('-v'),
       generate: args.contains('-g'));
 }
+
+Future<void> run(List<Uri> entryPoints, String allowedListPath,
+    {bool verbose = false,
+    bool generate = false,
+    bool Function(Uri uri)? analyzedUrisFilter}) async {
+  await runAnalysis(entryPoints,
+      (DiagnosticMessageHandler onDiagnostic, Component component) {
+    new DynamicVisitor(
+            onDiagnostic, component, allowedListPath, analyzedUrisFilter)
+        .run(verbose: verbose, generate: generate);
+  });
+}
+
+class DynamicVisitor extends VerifyingAnalysis {
+  // TODO(johnniwinther): Enable this when it is less noisy.
+  static const bool checkReturnTypes = false;
+
+  DynamicVisitor(DiagnosticMessageHandler onDiagnostic, Component component,
+      String? allowedListPath, UriFilter? analyzedUrisFilter)
+      : super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
+
+  @override
+  void visitDynamicGet(DynamicGet node) {
+    registerError(node, "Dynamic access of '${node.name}'.");
+    super.visitDynamicGet(node);
+  }
+
+  @override
+  void visitDynamicSet(DynamicSet node) {
+    registerError(node, "Dynamic update to '${node.name}'.");
+    super.visitDynamicSet(node);
+  }
+
+  @override
+  void visitDynamicInvocation(DynamicInvocation node) {
+    registerError(node, "Dynamic invocation of '${node.name}'.");
+    super.visitDynamicInvocation(node);
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    if (checkReturnTypes && node.function.returnType is DynamicType) {
+      registerError(node, "Dynamic return type");
+    }
+    super.visitFunctionDeclaration(node);
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    if (checkReturnTypes && node.function.returnType is DynamicType) {
+      registerError(node, "Dynamic return type");
+    }
+    super.visitFunctionExpression(node);
+  }
+
+  @override
+  void visitProcedure(Procedure node) {
+    if (checkReturnTypes &&
+        node.function.returnType is DynamicType &&
+        node.name.text != 'noSuchMethod') {
+      registerError(node, "Dynamic return type on $node");
+    }
+    super.visitProcedure(node);
+  }
+}
diff --git a/pkg/front_end/test/static_types/type_arguments.json b/pkg/front_end/test/static_types/type_arguments.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/pkg/front_end/test/static_types/type_arguments.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/pkg/front_end/test/static_types/type_arguments_test.dart b/pkg/front_end/test/static_types/type_arguments_test.dart
new file mode 100644
index 0000000..8998846
--- /dev/null
+++ b/pkg/front_end/test/static_types/type_arguments_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'verifying_analysis.dart';
+
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:kernel/ast.dart';
+
+Future<void> main(List<String> args) async {
+  await run(cfeAndBackendsEntryPoints,
+      'pkg/front_end/test/static_types/type_arguments.json',
+      analyzedUrisFilter: cfeAndBackends,
+      verbose: args.contains('-v'),
+      generate: args.contains('-g'));
+}
+
+Future<void> run(List<Uri> entryPoints, String allowedListPath,
+    {bool verbose = false,
+    bool generate = false,
+    bool Function(Uri uri)? analyzedUrisFilter}) async {
+  await runAnalysis(entryPoints,
+      (DiagnosticMessageHandler onDiagnostic, Component component) {
+    new TypeArgumentsVisitor(
+            onDiagnostic, component, allowedListPath, analyzedUrisFilter)
+        .run(verbose: verbose, generate: generate);
+  });
+}
+
+class TypeArgumentsVisitor extends VerifyingAnalysis {
+  TypeArgumentsVisitor(
+      DiagnosticMessageHandler onDiagnostic,
+      Component component,
+      String? allowedListPath,
+      UriFilter? analyzedUrisFilter)
+      : super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
+
+  @override
+  void visitInstanceInvocation(InstanceInvocation node) {
+    if (node.name.text == 'toList') {
+      TreeNode receiver = node.receiver;
+      if (receiver is InstanceInvocation &&
+          receiver.name.text == 'map' &&
+          receiver.arguments.types.length == 1) {
+        String astUri = 'package:kernel/ast.dart';
+        InterfaceType expressionType =
+            interface.createInterfaceType('Expression', uri: astUri);
+        InterfaceType statementType =
+            interface.createInterfaceType('Statement', uri: astUri);
+        InterfaceType assertStatementType =
+            interface.createInterfaceType('AssertStatement', uri: astUri);
+        InterfaceType variableDeclarationType =
+            interface.createInterfaceType('VariableDeclaration', uri: astUri);
+        DartType typeArgument = receiver.arguments.types.single;
+        if (interface.isSubtypeOf(typeArgument, expressionType) &&
+            typeArgument != expressionType) {
+          registerError(
+              node,
+              "map().toList() with type argument "
+              "${typeArgument} instead of ${expressionType}");
+        }
+        if (interface.isSubtypeOf(typeArgument, statementType)) {
+          if (interface.isSubtypeOf(typeArgument, assertStatementType)) {
+            // [AssertStatement] is used as an exclusive member of
+            // `InstanceCreation.asserts`.
+            if (typeArgument != assertStatementType) {
+              registerError(
+                  node,
+                  "map().toList() with type argument "
+                  "${typeArgument} instead of ${assertStatementType}");
+            }
+          } else if (interface.isSubtypeOf(
+              typeArgument, variableDeclarationType)) {
+            // [VariableDeclaration] is used as an exclusive member of, for
+            // instance, `FunctionNode.positionalParameters`.
+            if (typeArgument != variableDeclarationType) {
+              registerError(
+                  node,
+                  "map().toList() with type argument "
+                  "${typeArgument} instead of ${variableDeclarationType}");
+            }
+          } else if (typeArgument != statementType) {
+            registerError(
+                node,
+                "map().toList() with type argument "
+                "${typeArgument} instead of ${statementType}");
+          }
+        }
+      }
+    }
+    super.visitInstanceInvocation(node);
+  }
+}
diff --git a/pkg/front_end/test/static_types/verifying_analysis.dart b/pkg/front_end/test/static_types/verifying_analysis.dart
new file mode 100644
index 0000000..4305459
--- /dev/null
+++ b/pkg/front_end/test/static_types/verifying_analysis.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert' as json;
+import 'dart:io';
+
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:expect/expect.dart';
+import 'package:front_end/src/fasta/command_line_reporting.dart';
+import 'package:front_end/src/fasta/fasta_codes.dart';
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'package:kernel/ast.dart';
+
+/// [AnalysisVisitor] that supports tracking error/problem occurrences in an
+/// allowed list file.
+class VerifyingAnalysis extends AnalysisVisitor {
+  final String? _allowedListPath;
+
+  Map _expectedJson = {};
+
+  VerifyingAnalysis(DiagnosticMessageHandler onDiagnostic, Component component,
+      this._allowedListPath, UriFilter? analyzedUrisFilter)
+      : super(onDiagnostic, component, analyzedUrisFilter);
+
+  void run({bool verbose = false, bool generate = false}) {
+    if (!generate && _allowedListPath != null) {
+      File file = new File(_allowedListPath!);
+      if (file.existsSync()) {
+        try {
+          _expectedJson = json.jsonDecode(file.readAsStringSync());
+        } catch (e) {
+          Expect.fail('Error reading allowed list from $_allowedListPath: $e');
+        }
+      }
+    }
+    component.accept(this);
+    if (generate && _allowedListPath != null) {
+      Map<String, Map<String, int>> actualJson = {};
+      forEachMessage(
+          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+        Map<String, int> map = {};
+        actualMessagesMap
+            .forEach((String message, List<FormattedMessage> actualMessages) {
+          map[message] = actualMessages.length;
+        });
+        actualJson[uri] = map;
+      });
+
+      new File(_allowedListPath!).writeAsStringSync(
+          new json.JsonEncoder.withIndent('  ').convert(actualJson));
+      return;
+    }
+
+    int errorCount = 0;
+    _expectedJson.forEach((uri, expectedMessages) {
+      Map<String, List<FormattedMessage>>? actualMessagesMap =
+          getMessagesForUri(uri);
+      if (actualMessagesMap == null) {
+        print("Error: Allowed-listing of uri '$uri' isn't used. "
+            "Remove it from the allowed-list.");
+        errorCount++;
+      } else {
+        expectedMessages.forEach((expectedMessage, expectedCount) {
+          List<FormattedMessage>? actualMessages =
+              actualMessagesMap[expectedMessage];
+          if (actualMessages == null) {
+            print("Error: Allowed-listing of message '$expectedMessage' "
+                "in uri '$uri' isn't used. Remove it from the allowed-list.");
+            errorCount++;
+          } else {
+            int actualCount = actualMessages.length;
+            if (actualCount != expectedCount) {
+              print("Error: Unexpected count of allowed message "
+                  "'$expectedMessage' in uri '$uri'. "
+                  "Expected $expectedCount, actual $actualCount:");
+              print(
+                  '----------------------------------------------------------');
+              for (FormattedMessage message in actualMessages) {
+                onDiagnostic(message);
+              }
+              print(
+                  '----------------------------------------------------------');
+              errorCount++;
+            }
+          }
+        });
+        actualMessagesMap
+            .forEach((String message, List<FormattedMessage> actualMessages) {
+          if (!expectedMessages.containsKey(message)) {
+            for (FormattedMessage message in actualMessages) {
+              onDiagnostic(message);
+              errorCount++;
+            }
+          }
+        });
+      }
+    });
+    forEachMessage(
+        (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+      if (!_expectedJson.containsKey(uri)) {
+        actualMessagesMap
+            .forEach((String message, List<FormattedMessage> actualMessages) {
+          for (FormattedMessage message in actualMessages) {
+            onDiagnostic(message);
+            errorCount++;
+          }
+        });
+      }
+    });
+    if (errorCount != 0) {
+      print('$errorCount error(s) found.');
+      print("""
+
+********************************************************************************
+*  Unexpected code patterns found by test:
+*
+*    ${relativizeUri(Platform.script)}
+*
+*  Please address the reported errors, or, if the errors are as expected, run
+*
+*    dart ${relativizeUri(Platform.script)} -g
+*
+*  to update the expectation file.
+********************************************************************************
+""");
+      exit(-1);
+    }
+    if (verbose) {
+      forEachMessage(
+          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+        actualMessagesMap
+            .forEach((String message, List<FormattedMessage> actualMessages) {
+          for (FormattedMessage message in actualMessages) {
+            // TODO(johnniwinther): It is unnecessarily complicated to just
+            // add ' (allowed)' to an existing message!
+            LocatedMessage locatedMessage = message.locatedMessage;
+            String newMessageText =
+                '${locatedMessage.messageObject.problemMessage} (allowed)';
+            message = locatedMessage.withFormatting(
+                format(
+                    new LocatedMessage(
+                        locatedMessage.uri,
+                        locatedMessage.charOffset,
+                        locatedMessage.length,
+                        new Message(locatedMessage.messageObject.code,
+                            problemMessage: newMessageText,
+                            correctionMessage:
+                                locatedMessage.messageObject.correctionMessage,
+                            arguments: locatedMessage.messageObject.arguments)),
+                    Severity.warning,
+                    location: new Location(
+                        message.uri!, message.line, message.column),
+                    uriToSource: component.uriToSource),
+                message.line,
+                message.column,
+                Severity.warning,
+                []);
+            onDiagnostic(message);
+          }
+        });
+      });
+    } else {
+      int total = 0;
+      forEachMessage(
+          (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+        int count = 0;
+        actualMessagesMap
+            .forEach((String message, List<FormattedMessage> actualMessages) {
+          count += actualMessages.length;
+        });
+
+        print('${count} error(s) allowed in $uri');
+        total += count;
+      });
+      if (total > 0) {
+        print('${total} error(s) allowed in total.');
+      }
+    }
+  }
+
+  void registerError(TreeNode node, String message) {
+    registerMessage(node, message);
+  }
+}
diff --git a/pkg/front_end/tool/analyze.dart b/pkg/front_end/tool/analyze.dart
new file mode 100644
index 0000000..d729500
--- /dev/null
+++ b/pkg/front_end/tool/analyze.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'package:kernel/kernel.dart';
+
+Future<void> main(List<String> arguments) async {
+  await runAnalysis(
+      cfeAndBackendsEntryPoints,
+      performGeneralAnalysis(cfeAndBackends,
+          (TreeNode node, AnalysisInterface interface) {
+        // Use 'analyze.dart' to perform advanced analysis/code search by
+        // replacing the "example analysis" with a custom analysis.
+
+        // Example analysis:
+        if (node is InstanceInvocation && node.name.text == 'toList') {
+          TreeNode receiver = node.receiver;
+          if (receiver is InstanceInvocation &&
+              receiver.name.text == 'map' &&
+              receiver.arguments.types.length == 1) {
+            InterfaceType expressionType = interface.createInterfaceType(
+                'Expression',
+                uri: 'package:kernel/ast.dart');
+            DartType typeArgument = receiver.arguments.types.single;
+            if (interface.isSubtypeOf(typeArgument, expressionType) &&
+                typeArgument != expressionType) {
+              interface.reportMessage(node, "map().toList()");
+            }
+          }
+        }
+      }));
+}
diff --git a/pkg/test_runner/bin/test_runner.dart b/pkg/test_runner/bin/test_runner.dart
index 654e5f8..e21cfb5 100644
--- a/pkg/test_runner/bin/test_runner.dart
+++ b/pkg/test_runner/bin/test_runner.dart
@@ -29,17 +29,25 @@
 /// Runs all of the tests specified by the given command line [arguments].
 void main(List<String> arguments) async {
   // Parse the command line arguments to a configuration.
-  var parser = OptionsParser();
-  var configurations = <TestConfiguration>[];
+  final parser = OptionsParser();
+
+  List<TestConfiguration> configurations;
   try {
     configurations = parser.parse(arguments);
   } on OptionParseException catch (exception) {
     print(exception.message);
     exit(1);
   }
+  if (configurations == null) {
+    return;
+  }
 
-  if (configurations.isEmpty) return;
-  await buildConfigurations(configurations);
+  final build_success = await buildConfigurations(configurations);
+  if (!build_success) {
+    print("ERROR: Build failed.");
+    exit(1);
+  }
+
   // Run all of the configured tests.
   await testConfigurations(configurations);
 }
diff --git a/pkg/test_runner/lib/src/build_configurations.dart b/pkg/test_runner/lib/src/build_configurations.dart
index b6c8aa8b..2fd09f5 100644
--- a/pkg/test_runner/lib/src/build_configurations.dart
+++ b/pkg/test_runner/lib/src/build_configurations.dart
@@ -8,25 +8,35 @@
 import 'configuration.dart';
 import 'utils.dart';
 
-Future buildConfigurations(List<TestConfiguration> configurations) async {
-  var startTime = DateTime.now();
-  if (!configurations.first.build) return;
+// Returns false if build failed.
+Future<bool> buildConfigurations(List<TestConfiguration> configurations) async {
+  final startTime = DateTime.now();
+
   final buildTargets = <String>{};
   final modes = <Mode>{};
   final architectures = <Architecture>{};
   final systems = <System>{};
   for (final configuration in configurations) {
+    if (!configuration.build) {
+      continue;
+    }
     final inner = configuration.configuration;
     buildTargets.addAll(_selectBuildTargets(inner));
     modes.add(inner.mode);
     architectures.add(inner.architecture);
     systems.add(inner.system);
   }
-  if (buildTargets.isEmpty) return;
+
+  if (buildTargets.isEmpty) {
+    print('No build targets found.');
+    return true;
+  }
+
   if (systems.length > 1) {
     print('Unimplemented: building for multiple systems ${systems.join(',')}');
     exit(1);
   }
+
   final system = systems.single;
   final osFlags = <String>[];
   if (system == System.android) {
@@ -40,6 +50,7 @@
       exit(1);
     }
   }
+
   final command = [
     'tools/build.py',
     '-m',
@@ -50,6 +61,7 @@
     ...buildTargets
   ];
   print('Running command: python3 ${command.join(' ')}');
+
   final process = await Process.start('python3', command);
   stdout.nonBlocking.addStream(process.stdout);
   stderr.nonBlocking.addStream(process.stderr);
@@ -57,8 +69,11 @@
   if (exitCode != 0) {
     print('exit code: $exitCode');
   }
-  var buildTime = niceTime(DateTime.now().difference(startTime));
+
+  final buildTime = niceTime(DateTime.now().difference(startTime));
   print('--- Build time: $buildTime ---');
+
+  return exitCode == 0;
 }
 
 List<String> _selectBuildTargets(Configuration inner) {
diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
index c6fde24..9686fae 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc
@@ -1275,4 +1275,33 @@
   ENSURE(!Dart_IsError(result));
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Helper for the regression test for b/216834909
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) ||            \
+    defined(DART_HOST_OS_MACOS)
+static bool Regress216834909_hang_at_exit = true;
+
+static void Regress216834909_AtExit() {
+  if (Regress216834909_hang_at_exit) {
+    while (true) {
+      sleep(60 * 60);  // Sleep for 1 hour.
+    }
+  }
+}
+
+DART_EXPORT void Regress216834909_SetAtExit(int64_t install) {
+  if (install != 0) {
+    // Set and arm atexit routine.
+    atexit(&Regress216834909_AtExit);
+    Regress216834909_hang_at_exit = true;
+  } else {
+    // Disarm atexit routine.
+    Regress216834909_hang_at_exit = false;
+  }
+}
+#endif  // defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) ||     \
+        // defined(DART_HOST_OS_MACOS)
+
 }  // namespace dart
diff --git a/runtime/bin/process_android.cc b/runtime/bin/process_android.cc
index 16955e9..955efdc 100644
--- a/runtime/bin/process_android.cc
+++ b/runtime/bin/process_android.cc
@@ -435,7 +435,7 @@
     int bytes_read = FDUtils::ReadFromBlocking(read_in_[0], &msg, sizeof(msg));
     if (bytes_read != sizeof(msg)) {
       perror("Failed receiving notification message");
-      exit(1);
+      _exit(1);
     }
     if (Process::ModeIsAttached(mode_)) {
       ExecProcess();
@@ -568,13 +568,15 @@
           execvp(realpath, const_cast<char* const*>(program_arguments_));
           ReportChildError();
         } else {
-          // Exit the intermediate process.
-          exit(0);
+          // Exit the intermediate process. Avoid calling any atexit callbacks
+          // to avoid potential issues (e.g. deadlocks).
+          _exit(0);
         }
       }
     } else {
-      // Exit the intermediate process.
-      exit(0);
+      // Exit the intermediate process. Avoid calling any atexit callbacks
+      // to avoid potential issues (e.g. deadlocks).
+      _exit(0);
     }
   }
 
diff --git a/runtime/bin/process_linux.cc b/runtime/bin/process_linux.cc
index 95a9c86..ae7f1b5 100644
--- a/runtime/bin/process_linux.cc
+++ b/runtime/bin/process_linux.cc
@@ -170,8 +170,7 @@
     // Wake up the [ExitCodeHandler] thread which is blocked on `wait()` (see
     // [ExitCodeHandlerEntry]).
     if (TEMP_FAILURE_RETRY(fork()) == 0) {
-      // We avoid running through registered atexit() handlers because that is
-      // unnecessary work.
+      // Avoid calling any atexit callbacks to prevent deadlocks.
       _exit(0);
     }
 
@@ -437,7 +436,7 @@
     int bytes_read = FDUtils::ReadFromBlocking(read_in_[0], &msg, sizeof(msg));
     if (bytes_read != sizeof(msg)) {
       perror("Failed receiving notification message");
-      exit(1);
+      _exit(1);
     }
     if (Process::ModeIsAttached(mode_)) {
       ExecProcess();
@@ -569,13 +568,15 @@
           execvp(realpath, const_cast<char* const*>(program_arguments_));
           ReportChildError();
         } else {
-          // Exit the intermediate process.
-          exit(0);
+          // Exit the intermediate process. Avoid calling any atexit callbacks
+          // to avoid potential issues (e.g. deadlocks).
+          _exit(0);
         }
       }
     } else {
-      // Exit the intermediate process.
-      exit(0);
+      // Exit the intermediate process. Avoid calling any atexit callbacks
+      // to avoid potential issues (e.g. deadlocks).
+      _exit(0);
     }
   }
 
@@ -728,7 +729,8 @@
     close(exec_control_[1]);
 
     // We avoid running through registered atexit() handlers because that is
-    // unnecessary work.
+    // unnecessary work. It can also cause deadlocks on exit in the forked
+    // process.
     _exit(1);
   }
 
diff --git a/runtime/bin/process_macos.cc b/runtime/bin/process_macos.cc
index 6bbbfae..f174679 100644
--- a/runtime/bin/process_macos.cc
+++ b/runtime/bin/process_macos.cc
@@ -168,7 +168,7 @@
 
     // Fork to wake up waitpid.
     if (TEMP_FAILURE_RETRY(fork()) == 0) {
-      exit(0);
+      _Exit(0);
     }
 
     monitor_->Notify();
@@ -437,7 +437,7 @@
     int bytes_read = FDUtils::ReadFromBlocking(read_in_[0], &msg, sizeof(msg));
     if (bytes_read != sizeof(msg)) {
       perror("Failed receiving notification message");
-      exit(1);
+      _Exit(1);
     }
     if (Process::ModeIsAttached(mode_)) {
       ExecProcess();
@@ -535,13 +535,15 @@
           execvp(path_, const_cast<char* const*>(program_arguments_));
           ReportChildError();
         } else {
-          // Exit the intermeiate process.
-          exit(0);
+          // Exit the intermeiate process. Avoid any atexit callbacks
+          // to prevent deadlocks.
+          _Exit(0);
         }
       }
     } else {
-      // Exit the intermeiate process.
-      exit(0);
+      // Exit the intermeiate process. Avoid any atexit callbacks
+      // to prevent deadlocks.
+      _Exit(0);
     }
   }
 
@@ -695,7 +697,8 @@
                                strlen(os_error_message) + 1);
     }
     close(exec_control_[1]);
-    exit(1);
+    // Avoid calling any atexit callbacks to prevent deadlocks.
+    _Exit(1);
   }
 
   void ReportPid(int pid) {
diff --git a/runtime/tests/vm/dart/regress_b_216834909_test.dart b/runtime/tests/vm/dart/regress_b_216834909_test.dart
new file mode 100644
index 0000000..a73ff2d
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_b_216834909_test.dart
@@ -0,0 +1,34 @@
+// 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.
+//
+// SharedObjects=ffi_test_functions
+
+// Regression test for b/216834909.
+//
+// Check that subprocess spawning implementation uses _exit rather than exit on
+// paths which terminate fork child without exec-ing.
+
+import 'dart:async';
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:isolate';
+
+import "package:expect/expect.dart";
+import '../../../../tests/ffi/dylib_utils.dart';
+
+final ffiTestFunctions = dlopenPlatformSpecific('ffi_test_functions');
+
+final setAtExit =
+    ffiTestFunctions.lookupFunction<Void Function(Int64), void Function(int)>(
+        'Regress216834909_SetAtExit');
+
+main(List<String> args) async {
+  // We only care about platforms which use fork/exec.
+  if (!Platform.isLinux && !Platform.isAndroid && !Platform.isMacOS) {
+    return;
+  }
+  setAtExit(1); // Install at exit handler.
+  await Process.start('true', [], mode: ProcessStartMode.detached);
+  setAtExit(0); // Clear at exit handler.
+}
diff --git a/runtime/tests/vm/dart_2/regress_b_216834909_test.dart b/runtime/tests/vm/dart_2/regress_b_216834909_test.dart
new file mode 100644
index 0000000..65dd27c
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_b_216834909_test.dart
@@ -0,0 +1,36 @@
+// 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.
+//
+// SharedObjects=ffi_test_functions
+
+// Regression test for b/216834909.
+//
+// Check that subprocess spawning implementation uses _exit rather than exit on
+// paths which terminate fork child without exec-ing.
+
+// @dart = 2.9
+
+import 'dart:async';
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:isolate';
+
+import "package:expect/expect.dart";
+import '../../../../tests/ffi/dylib_utils.dart';
+
+final ffiTestFunctions = dlopenPlatformSpecific('ffi_test_functions');
+
+final setAtExit =
+    ffiTestFunctions.lookupFunction<Void Function(Int64), void Function(int)>(
+        'Regress216834909_SetAtExit');
+
+main(List<String> args) async {
+  // We only care about platforms which use fork/exec.
+  if (!Platform.isLinux && !Platform.isAndroid && !Platform.isMacOS) {
+    return;
+  }
+  setAtExit(1); // Install at exit handler.
+  await Process.start('true', [], mode: ProcessStartMode.detached);
+  setAtExit(0); // Clear at exit handler.
+}
diff --git a/tools/VERSION b/tools/VERSION
index 710b49e..370264a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 39
+PRERELEASE 40
 PRERELEASE_PATCH 0
\ No newline at end of file