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