Version 2.18.0-20.0.dev
Merge commit 'a823f54c9337b040cc9a0571d5a7b51248fdee23' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/pubspec.yaml b/pkg/_fe_analyzer_shared/pubspec.yaml
index e8855e1..4a30fc7 100644
--- a/pkg/_fe_analyzer_shared/pubspec.yaml
+++ b/pkg/_fe_analyzer_shared/pubspec.yaml
@@ -1,5 +1,5 @@
name: _fe_analyzer_shared
-version: 38.0.0
+version: 39.0.0
description: Logic that is shared between the front_end and analyzer packages.
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/_fe_analyzer_shared
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index bf2dee5..689dd80 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -752,11 +752,9 @@
);
}
- /// Add a suggestion for a [method]. If the method is being invoked with a
- /// target of `super`, then the [containingMemberName] should be the name of
- /// the member containing the invocation. If a [kind] is provided it will be
- /// used as the kind for the suggestion. The [inheritanceDistance] is the
- /// value of the inheritance distance feature computed for the method.
+ /// Add a suggestion for a [method]. If a [kind] is provided it will be used
+ /// as the kind for the suggestion. The [inheritanceDistance] is the value of
+ /// the inheritance distance feature computed for the method.
void suggestMethod(MethodElement method,
{required CompletionSuggestionKind kind,
required double inheritanceDistance}) {
@@ -1047,10 +1045,8 @@
);
}
- /// Add a suggestion for a top-level property [accessor]. If a [kind] is
- /// provided it will be used as the kind for the suggestion. If the accessor
- /// can only be referenced using a prefix, then the [prefix] should be
- /// provided.
+ /// Add a suggestion for a top-level property [accessor]. If the accessor can
+ /// only be referenced using a prefix, then the [prefix] should be provided.
void suggestTopLevelPropertyAccessor(PropertyAccessorElement accessor,
{String? prefix}) {
assert(
@@ -1102,8 +1098,7 @@
}
}
- /// Add a suggestion for a top-level [variable]. If a [kind] is provided it
- /// will be used as the kind for the suggestion. If the variable can only be
+ /// Add a suggestion for a top-level [variable]. If the variable can only be
/// referenced using a prefix, then the [prefix] should be provided.
void suggestTopLevelVariable(TopLevelVariableElement variable,
{String? prefix}) {
@@ -1121,9 +1116,8 @@
);
}
- /// Add a suggestion for a [typeAlias]. If a [kind] is provided it
- /// will be used as the kind for the suggestion. If the alias can only be
- /// referenced using a prefix, then the [prefix] should be provided.
+ /// Add a suggestion for a [typeAlias]. If the alias can only be referenced
+ /// using a prefix, then the [prefix] should be provided.
void suggestTypeAlias(TypeAliasElement typeAlias, {String? prefix}) {
var relevance = _computeTopLevelRelevance(typeAlias,
elementType: _instantiateTypeAlias(typeAlias));
diff --git a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
index 639b3eb..9702d0e 100644
--- a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
+++ b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
@@ -161,6 +161,57 @@
});
});
+ group('PercentileComputer', () {
+ test('empty', () {
+ var computer = PercentileComputer('empty', valueLimit: 2000);
+ expect(computer.median, equals(0));
+ expect(computer.valueCount, equals(0));
+ });
+
+ test('clear', () {
+ var computer = PercentileComputer('name', valueLimit: 2000);
+ computer.addValue(4);
+ computer.addValue(5);
+ computer.addValue(6);
+ computer.addValue(3000);
+
+ expect(computer.median, equals(5));
+ expect(computer.valueCount, equals(3));
+ expect(computer.aboveValueMaxCount, equals(1));
+ computer.clear();
+
+ expect(computer.median, equals(0));
+ expect(computer.valueCount, equals(0));
+ expect(computer.aboveValueMaxCount, equals(0));
+ expect(computer.aboveValueMaxSamples, isEmpty);
+ });
+
+ test('percentiles', () {
+ var computer = PercentileComputer('name', valueLimit: 2000);
+ for (var i = 0; i < 100; i++) {
+ computer.addValue(i);
+ }
+
+ expect(computer.median, equals(50));
+ expect(computer.p90, equals(90));
+ expect(computer.p95, equals(95));
+ });
+
+ test('values above maxValue', () {
+ var computer = PercentileComputer('name', valueLimit: 2000);
+
+ computer.addValue(1);
+ computer.addValue(2);
+ computer.addValue(3);
+ computer.addValue(2500);
+ computer.addValue(3000);
+
+ expect(computer.median, equals(2));
+ expect(computer.aboveValueMaxCount, equals(2));
+ expect(computer.aboveValueMaxSamples, equals([2500, 3000]));
+ });
+ });
+
group('Place', () {
test('none', () {
var place = Place.none();
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index fcc4650..ed88f0c 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -66,10 +66,13 @@
///
/// This approach has several drawbacks:
///
-/// - The AST is always complete and correct, and that's rarely the case for
-/// real completion requests. Usually the tree is incomplete and often has a
-/// completely different structure because of the way recovery works. We
-/// currently have no way of measuring completions under realistic conditions.
+/// - The options for creating an "in-progress" file are limited. In the default
+/// 'overlay' mode, the AST is always complete and correct, rarely the case
+/// for real completion requests. The other 'overlay' modes generate
+/// incomplete ASTs with error recovery nodes, but neither of these quite
+/// properly emulate the act of editing the middle of a file, perhaps the
+/// middle of an expression, or the middle of an argument list. We currently
+/// have no way of measuring completions under realistic conditions.
///
/// - We can't measure completions for several keywords because the presence of
/// the keyword in the AST causes it to not be suggested.
@@ -77,7 +80,7 @@
/// - The time it takes to compute the suggestions doesn't include the time
/// required to finish analyzing the file if the analysis hasn't been
/// completed before suggestions are requested. While the times are accurate
-/// (within the accuracy of the `Stopwatch` class) they are the minimum
+/// (within the accuracy of the [Stopwatch] class) they are the minimum
/// possible time. This doesn't give us a measure of how completion will
/// perform in production, but does give us an optimistic approximation.
///
@@ -193,7 +196,7 @@
..addFlag(CompletionMetricsOptions.PRINT_SHADOWED_COMPLETION_DETAILS,
defaultsTo: false,
help: 'Print detailed information every time a completion request '
- 'produces a suggestions whose name matches the expected suggestion '
+ 'produces a suggestion whose name matches the expected suggestion '
'but that is referencing a different element',
negatable: false)
..addFlag(CompletionMetricsOptions.PRINT_SLOWEST_RESULTS,
@@ -332,6 +335,11 @@
final ArithmeticMeanComputer meanCompletionMS =
ArithmeticMeanComputer('ms per completion');
+ /// A percentile computer for the ms per completion request, using 2.000
+ /// seconds as the max value to use in percentile calculations.
+ final PercentileComputer percentileCompletionMS =
+ PercentileComputer('ms per completion', valueLimit: 2000);
+
final DistributionComputer distributionCompletionMS = DistributionComputer();
final MeanReciprocalRankComputer mrrComputer =
@@ -398,6 +406,8 @@
.fromJson(map['completionElementKindCounter'] as Map<String, dynamic>);
metrics.meanCompletionMS
.fromJson(map['meanCompletionMS'] as Map<String, dynamic>);
+ metrics.percentileCompletionMS
+ .fromJson(map['percentileMS'] as Map<String, dynamic>);
metrics.distributionCompletionMS
.fromJson(map['distributionCompletionMS'] as Map<String, dynamic>);
metrics.mrrComputer.fromJson(map['mrrComputer'] as Map<String, dynamic>);
@@ -453,6 +463,7 @@
completionKindCounter.addData(metrics.completionKindCounter);
completionElementKindCounter.addData(metrics.completionElementKindCounter);
meanCompletionMS.addData(metrics.meanCompletionMS);
+ percentileCompletionMS.addData(metrics.percentileCompletionMS);
distributionCompletionMS.addData(metrics.distributionCompletionMS);
mrrComputer.addData(metrics.mrrComputer);
successfulMrrComputer.addData(metrics.successfulMrrComputer);
@@ -545,6 +556,7 @@
'completionKindCounter': completionKindCounter.toJson(),
'completionElementKindCounter': completionElementKindCounter.toJson(),
'meanCompletionMS': meanCompletionMS.toJson(),
+ 'percentileCompletionMS': percentileCompletionMS.toJson(),
'distributionCompletionMS': distributionCompletionMS.toJson(),
'mrrComputer': mrrComputer.toJson(),
'successfulMrrComputer': successfulMrrComputer.toJson(),
@@ -617,6 +629,7 @@
/// Record this elapsed ms count for the average ms count.
void _recordTime(CompletionResult result) {
meanCompletionMS.addValue(result.elapsedMS);
+ percentileCompletionMS.addValue(result.elapsedMS);
distributionCompletionMS.addValue(result.elapsedMS);
}
@@ -815,7 +828,7 @@
void printComparisonOfCompletionCounts() {
String toString(int count, int totalCount) {
- return '$count (${printPercentage(count / totalCount, 2)})';
+ return '$count (${(count / totalCount).asPercentage(2)})';
}
var counters = targetMetrics.map((metrics) => metrics.completionCounter);
@@ -1093,25 +1106,45 @@
}
void printOtherMetrics(CompletionMetrics metrics) {
- List<String> toRow(ArithmeticMeanComputer computer) {
- var min = computer.min;
- var mean = computer.mean.toStringAsFixed(6);
- var max = computer.max;
- return [computer.name, '$min, $mean, $max'];
+ List<String> meanComputingRow(ArithmeticMeanComputer computer) {
+ return [
+ computer.name,
+ computer.min!.toStringAsFixed(3),
+ computer.mean.toStringAsFixed(3),
+ computer.max!.toStringAsFixed(3),
+ ];
}
var table = [
- toRow(metrics.meanCompletionMS),
- toRow(metrics.charsBeforeTop),
- toRow(metrics.charsBeforeTopFive),
- toRow(metrics.insertionLengthTheoretical),
+ ['', 'min', 'mean', 'max'],
+ meanComputingRow(metrics.meanCompletionMS),
+ meanComputingRow(metrics.charsBeforeTop),
+ meanComputingRow(metrics.charsBeforeTopFive),
+ meanComputingRow(metrics.insertionLengthTheoretical),
];
rightJustifyColumns(table, range(1, table[0].length));
printHeading(2, 'Other metrics');
printTable(table);
+ var percentileTable = [
+ ['', 'p50', 'p90', 'p95', 'count > 2s', 'max'],
+ [
+ metrics.percentileCompletionMS.name,
+ metrics.percentileCompletionMS.median.toString(),
+ metrics.percentileCompletionMS.p90.toString(),
+ metrics.percentileCompletionMS.p95.toString(),
+ metrics.percentileCompletionMS.aboveValueMaxCount.toString(),
+ metrics.percentileCompletionMS.maxValue.toString(),
+ ],
+ ];
+ rightJustifyColumns(percentileTable, range(1, percentileTable[1].length));
+
+ printHeading(3, 'Percentile metrics');
+ printTable(percentileTable);
+
var distribution = metrics.distributionCompletionMS.displayString();
+ printHeading(3, 'Completion ms distribution');
print('${metrics.name}: $distribution');
print('');
}
@@ -2083,6 +2116,12 @@
}
}
+extension on num {
+ String asPercentage([int fractionDigits = 1]) =>
+ '${(this * 100).toStringAsFixed(fractionDigits)}%'
+ .padLeft(4 + fractionDigits);
+}
+
extension AvailableSuggestionsExtension on protocol.AvailableSuggestion {
// TODO(jwren) I am not sure if we want CompletionSuggestionKind.INVOCATION in
// call cases here, to iterate I need to figure out why this algorithm is
diff --git a/pkg/analysis_server/tool/code_completion/metrics_util.dart b/pkg/analysis_server/tool/code_completion/metrics_util.dart
index 673f1bb..8899842 100644
--- a/pkg/analysis_server/tool/code_completion/metrics_util.dart
+++ b/pkg/analysis_server/tool/code_completion/metrics_util.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math;
+import 'dart:typed_data';
import 'package:analysis_server/src/status/pages.dart';
@@ -295,6 +296,136 @@
}
}
+/// A computer for calculating percentile-based metrics on a data set.
+///
+/// Specifically this tracks p50 (the median), p90, and p95.
+///
+/// See https://en.wikipedia.org/wiki/Percentile.
+class PercentileComputer {
+ final String name;
+
+ /// The value limit allowed by this computer.
+ ///
+ /// The computer can calculate percentile values for all data that range
+ /// between 0 and [valueLimit], exclusive.
+ int valueLimit;
+
+ /// An array of counts; the value at each index _i_ is the number of
+ /// occurrences of value _i_.
+ ///
+ /// Any values larger than [valueLimit] are not counted here.
+ Uint32List _counts;
+
+ /// The number of values which are less than [valueLimit].
+ int valueCount = 0;
+
+ /// The number of values greater than [valueLimit].
+ int aboveValueMaxCount = 0;
+
+ List<int> aboveValueMaxSamples = [];
+
+ int maxValue = 0;
+
+ PercentileComputer(this.name, {required this.valueLimit})
+ : _counts = Uint32List(valueLimit);
+
+ /// Calculates the median (p50) value.
+ int get median => kthPercentile(50);
+
+ /// Calculates the p90 value; the value at the 90th percentile of the data.
+ int get p90 => kthPercentile(90);
+
+ /// Calculates the p95 value; the value at the 95th percentile of the data.
+ int get p95 => kthPercentile(95);
+
+ /// Add the data from the given [computer] to this computer.
+ void addData(PercentileComputer computer) {
+ if (computer.valueLimit != valueLimit) {
+ throw UnsupportedError(
+ 'Cannot combine two PercentileComputers with different valueLimit '
+ 'values');
+ }
+ for (var i = 0; i < _counts.length; i++) {
+ _counts[i] += computer._counts[i];
+ }
+ valueCount += computer.valueCount;
+ aboveValueMaxCount += computer.aboveValueMaxCount;
+ for (var val in computer.aboveValueMaxSamples) {
+ if (aboveValueMaxSamples.length < 10) {
+ aboveValueMaxSamples.add(val);
+ }
+ }
+ maxValue = math.max(maxValue, computer.maxValue);
+ }
+
+ void addValue(int val) {
+ if (val > valueLimit) {
+ aboveValueMaxCount++;
+ if (aboveValueMaxSamples.length < 10) {
+ aboveValueMaxSamples.add(val);
+ }
+ } else {
+ _counts[val]++;
+ valueCount++;
+ }
+ maxValue = math.max(maxValue, val);
+ }
+
+ void clear() {
+ _counts = Uint32List(0);
+ valueCount = 0;
+ aboveValueMaxCount = 0;
+ aboveValueMaxSamples = [];
+ maxValue = 0;
+ }
+
+ /// Set the state of this computer to the state recorded in the decoded JSON
+ /// [map].
+ void fromJson(Map<String, dynamic> map) {
+ valueLimit = map['valueLimit'] as int;
+ _counts = Uint32List.fromList((map['counts'] as List<dynamic>).cast<int>());
+ valueCount = map['valueCount'] as int;
+ aboveValueMaxCount = map['aboveValueMaxCount'] as int;
+ aboveValueMaxSamples =
+ (map['aboveValueMaxSamples'] as List<dynamic>).cast<int>();
+ maxValue = map['maxValue'] as int;
+ }
+
+ /// Calculates the value at the _k_th percentile of the data.
+ int kthPercentile(int percentile) {
+ if (valueCount == 0) {
+ return 0;
+ }
+ // Linear walk through the data takes O([maxValue]) time. If this is too
+ // slow, a binary search can be implemented.
+ var targetIndex = valueCount * percentile / 100;
+ // The number of values represented by walking the counts.
+ var accumulation = 0;
+ for (var i = 0; i < _counts.length; i++) {
+ accumulation += _counts[i];
+ if (accumulation > targetIndex) {
+ // We've now accounted for [targetIndex] values, which includes the
+ // median value.
+ return i;
+ }
+ }
+ // The median value is in the very highest expected possible value.
+ return valueLimit;
+ }
+
+ /// Return a map used to represent this computer in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'counts': _counts,
+ 'valueLimit': valueLimit,
+ 'valueCount': valueCount,
+ 'aboveValueMaxCount': aboveValueMaxCount,
+ 'aboveValueMaxSamples': aboveValueMaxSamples,
+ 'maxValue': maxValue,
+ };
+ }
+}
+
/// An immutable class to represent the placement in some list, for example '2nd
/// place out of 5'.
class Place {
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 5ff3292..85f3551 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 4.0.0 (Not yet released - breaking changes)
+## 4.0.0
* Removed deprecated `UriKind` and `Source.uriKind`.
* Removed deprecated `LibraryElement.hasExtUri`.
* Removed deprecated `LibraryElement.hasLoadLibraryFunction`.
diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml
index e0b366d..fdeb71c 100644
--- a/pkg/analyzer/pubspec.yaml
+++ b/pkg/analyzer/pubspec.yaml
@@ -1,5 +1,5 @@
name: analyzer
-version: 4.0.0-dev
+version: 4.0.0
description: This package provides a library that performs static analysis of Dart code.
homepage: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer
@@ -7,7 +7,7 @@
sdk: '>=2.14.0 <3.0.0'
dependencies:
- _fe_analyzer_shared: ^38.0.0
+ _fe_analyzer_shared: ^39.0.0
collection: ^1.15.0
convert: ^3.0.0
crypto: ^3.0.0
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 2a303b8..a2c2d4e 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -36,6 +36,7 @@
import 'package:vm/incremental_compiler.dart' show IncrementalCompiler;
import 'package:vm/kernel_front_end.dart';
+import 'src/binary_protocol.dart';
import 'src/javascript_bundle.dart';
import 'src/strong_components.dart';
@@ -114,6 +115,10 @@
' option',
defaultsTo: 'org-dartlang-root',
hide: true)
+ ..addOption('binary-protocol-address',
+ hide: true,
+ help: 'The server will establish TCP connection to this address, and'
+ ' will exchange binary requests and responses with the client.')
..addFlag('enable-http-uris',
defaultsTo: false, hide: true, help: 'Enables support for http uris.')
..addFlag('verbose', help: 'Enables verbose output from the compiler.')
@@ -1421,6 +1426,12 @@
}
}
+ final binaryProtocolAddressStr = options['binary-protocol-address'];
+ if (binaryProtocolAddressStr is String) {
+ runBinaryProtocol(binaryProtocolAddressStr);
+ return 0;
+ }
+
compiler ??= FrontendCompiler(output,
printerFactory: binaryPrinterFactory,
unsafePackageSerialization: options["unsafe-package-serialization"],
diff --git a/pkg/frontend_server/lib/src/binary_protocol.dart b/pkg/frontend_server/lib/src/binary_protocol.dart
new file mode 100644
index 0000000..25fc45d
--- /dev/null
+++ b/pkg/frontend_server/lib/src/binary_protocol.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.15
+import 'dart:async';
+import 'dart:io' as io;
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
+import 'package:front_end/src/api_prototype/compiler_options.dart' as fe;
+import 'package:front_end/src/api_prototype/file_system.dart' as fe;
+import 'package:front_end/src/api_prototype/kernel_generator.dart' as fe;
+import 'package:front_end/src/fasta/kernel/utils.dart' as fe;
+import 'package:kernel/ast.dart' as fe;
+import 'package:kernel/target/targets.dart' as fe;
+import 'package:vm/kernel_front_end.dart' as vm;
+import 'package:vm/target/vm.dart' as vm;
+
+Future<void> runBinaryProtocol(String addressStr) async {
+ final parsedAddress = _ParsedAddress.parse(addressStr);
+ final socket = await io.Socket.connect(
+ parsedAddress.host,
+ parsedAddress.port,
+ );
+
+ _Client(
+ RequestChannel(socket),
+ () {
+ socket.destroy();
+ },
+ );
+}
+
+class _Client {
+ final RequestChannel _channel;
+ final void Function() _stopHandler;
+ final Map<Uri, Uint8List> _dills = {};
+
+ _Client(this._channel, this._stopHandler) {
+ _channel.add('dill.put', _dillPut);
+ _channel.add('dill.remove', _dillRemove);
+ _channel.add('exit', _exit);
+ _channel.add('kernelForModule', _kernelForModule);
+ _channel.add('kernelForProgram', _kernelForProgram);
+ _channel.add('stop', _stop);
+ }
+
+ Future<void> _dillPut(Object? argumentObject) async {
+ final arguments = argumentObject.argumentAsMap;
+ final uriStr = arguments.required<String>('uri');
+ final bytes = arguments.required<Uint8List>('bytes');
+
+ final uri = Uri.parse(uriStr);
+ _dills[uri] = bytes;
+ }
+
+ Future<void> _dillRemove(Object? argumentObject) async {
+ final arguments = argumentObject.argumentAsMap;
+ final uriStr = arguments.required<String>('uri');
+
+ final uri = Uri.parse(uriStr);
+ _dills.remove(uri);
+ }
+
+ Future<void> _exit(Object? argument) async {
+ io.exit(0);
+ }
+
+ fe.CompilerOptions _getCompilerOptions(Map<Object?, Object?> arguments) {
+ final sdkSummaryUriStr = arguments.required<String>('sdkSummary');
+
+ final compilerOptions = fe.CompilerOptions()
+ ..environmentDefines = {}
+ ..fileSystem = _FileSystem(_channel, _dills)
+ ..sdkSummary = Uri.parse(sdkSummaryUriStr)
+ ..target = vm.VmTarget(fe.TargetFlags(enableNullSafety: true));
+
+ final additionalDills = arguments['additionalDills'].asListOf<String>();
+ if (additionalDills != null) {
+ compilerOptions.additionalDills.addAll(
+ additionalDills.map(Uri.parse),
+ );
+ }
+
+ return compilerOptions;
+ }
+
+ Future<Object?> _kernelForModule(Object? argumentObject) async {
+ final arguments = argumentObject.argumentAsMap;
+ final packagesFileUriStr = arguments.required<String>('packagesFileUri');
+
+ final compilerOptions = _getCompilerOptions(arguments)
+ ..packagesFileUri = Uri.parse(packagesFileUriStr);
+
+ final uriStrList = arguments['uris'].asListOf<String>();
+ if (uriStrList == null) {
+ throw ArgumentError('Missing field: uris');
+ }
+
+ final compilationResults = await fe.kernelForModule(
+ uriStrList.map(Uri.parse).toList(),
+ compilerOptions,
+ );
+
+ return _serializeComponentWithoutPlatform(
+ compilationResults.component!,
+ );
+ }
+
+ Future<Object?> _kernelForProgram(Object? argumentObject) async {
+ final arguments = argumentObject.argumentAsMap;
+
+ final compilerOptions = _getCompilerOptions(arguments);
+
+ final packagesFileUriStr = arguments.optional<String>('packagesFileUri');
+ if (packagesFileUriStr != null) {
+ compilerOptions.packagesFileUri = Uri.parse(packagesFileUriStr);
+ }
+
+ final uriStr = arguments.required<String>('uri');
+
+ final compilationResults = await vm.compileToKernel(
+ Uri.parse(uriStr),
+ compilerOptions,
+ environmentDefines: {},
+ );
+
+ return _serializeComponentWithoutPlatform(
+ compilationResults.component!,
+ );
+ }
+
+ Future<void> _stop(Object? argument) async {
+ _stopHandler();
+ }
+
+ static Uint8List _serializeComponentWithoutPlatform(fe.Component component) {
+ return fe.serializeComponent(
+ component,
+ filter: (library) {
+ return !library.importUri.isScheme('dart');
+ },
+ includeSources: false,
+ );
+ }
+}
+
+class _FileSystem implements fe.FileSystem {
+ final RequestChannel _channel;
+ final Map<Uri, Uint8List> _dills;
+
+ _FileSystem(this._channel, this._dills);
+
+ @override
+ fe.FileSystemEntity entityForUri(Uri uri) {
+ return _FileSystemEntity(this, uri);
+ }
+}
+
+class _FileSystemEntity implements fe.FileSystemEntity {
+ final _FileSystem _fileSystem;
+
+ @override
+ final Uri uri;
+
+ _FileSystemEntity(this._fileSystem, this.uri);
+
+ RequestChannel get _channel => _fileSystem._channel;
+
+ String get _uriStr => uri.toString();
+
+ @override
+ Future<bool> exists() async {
+ if (_fileSystem._dills.containsKey(uri)) {
+ return true;
+ }
+ return _channel.sendRequest<bool>('file.exists', _uriStr);
+ }
+
+ @override
+ Future<bool> existsAsyncIfPossible() => exists();
+
+ @override
+ Future<List<int>> readAsBytes() async {
+ final storedBytes = _fileSystem._dills[uri];
+ if (storedBytes != null) {
+ return storedBytes;
+ }
+
+ return _channel.sendRequest<Uint8List>('file.readAsBytes', _uriStr);
+ }
+
+ @override
+ Future<List<int>> readAsBytesAsyncIfPossible() => readAsBytes();
+
+ @override
+ Future<String> readAsString() async {
+ return _channel.sendRequest<String>('file.readAsString', _uriStr);
+ }
+}
+
+class _ParsedAddress {
+ final String host;
+ final int port;
+
+ factory _ParsedAddress.parse(String str) {
+ final colonOffset = str.lastIndexOf(':');
+ if (colonOffset == -1) {
+ throw FormatException("Expected ':' in: $str");
+ }
+
+ return _ParsedAddress._(
+ str.substring(0, colonOffset),
+ int.parse(str.substring(colonOffset + 1)),
+ );
+ }
+
+ _ParsedAddress._(this.host, this.port);
+}
+
+extension on Object? {
+ Map<Object?, Object?> get argumentAsMap {
+ final self = this;
+ if (self is Map<Object?, Object?>) {
+ return self;
+ }
+ throw ArgumentError('The argument must be a map.');
+ }
+}
+
+extension on Map<Object?, Object?> {
+ T? optional<T extends Object>(String name) {
+ final value = this[name];
+ if (value == null) {
+ return null;
+ }
+ if (value is T) {
+ return value;
+ }
+ throw ArgumentError('Must be null or $T: $name');
+ }
+
+ T required<T>(String name) {
+ final value = this[name];
+ if (value is T) {
+ return value;
+ }
+ throw ArgumentError('Must be $T: $name');
+ }
+}
+
+extension on Object? {
+ List<T>? asListOf<T>() {
+ final self = this;
+ if (self is List<Object?>) {
+ final result = <T>[];
+ for (final element in self) {
+ if (element is T) {
+ result.add(element);
+ } else {
+ return null;
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+}
diff --git a/pkg/frontend_server/test/frontend_server_test.dart b/pkg/frontend_server/test/frontend_server_test.dart
index d34b067..46457d0 100644
--- a/pkg/frontend_server/test/frontend_server_test.dart
+++ b/pkg/frontend_server/test/frontend_server_test.dart
@@ -3,10 +3,12 @@
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
+import 'dart:typed_data';
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
import 'package:front_end/src/api_unstable/vm.dart';
-import 'package:kernel/binary/ast_to_binary.dart';
import 'package:kernel/ast.dart' show Component;
+import 'package:kernel/binary/ast_to_binary.dart';
import 'package:kernel/kernel.dart' show loadComponentFromBinary;
import 'package:kernel/verifier.dart' show verifyComponent;
import 'package:mockito/mockito.dart';
@@ -16,15 +18,6 @@
import '../lib/frontend_server.dart';
-class _MockedCompiler extends Mock implements CompilerInterface {}
-
-class _MockedIncrementalCompiler extends Mock implements IncrementalCompiler {}
-
-class _MockedBinaryPrinterFactory extends Mock implements BinaryPrinterFactory {
-}
-
-class _MockedBinaryPrinter extends Mock implements BinaryPrinter {}
-
void main() async {
group('basic', () {
final CompilerInterface compiler = _MockedCompiler();
@@ -1798,6 +1791,214 @@
});
});
+ group('binary protocol', () {
+ var fileContentMap = <Uri, String>{};
+
+ setUp(() {
+ fileContentMap = {};
+ });
+
+ void addFileCallbacks(RequestChannel requestChannel) {
+ requestChannel.add('file.exists', (uriStr) async {
+ final uri = Uri.parse(uriStr as String);
+ return fileContentMap.containsKey(uri);
+ });
+ requestChannel.add('file.readAsBytes', (uriStr) async {
+ final uri = Uri.parse(uriStr as String);
+ final content = fileContentMap[uri];
+ return content != null ? utf8.encode(content) : Uint8List(0);
+ });
+ requestChannel.add('file.readAsStringSync', (uriStr) async {
+ final uri = Uri.parse(uriStr as String);
+ return fileContentMap[uri] ?? '';
+ });
+ }
+
+ Future<ServerSocket> loopbackServerSocket() async {
+ try {
+ return await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
+ } on SocketException catch (_) {
+ return await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ }
+ }
+
+ Uri registerKernelBlob(Uint8List bytes) {
+ bytes = Uint8List.fromList(bytes);
+ return (Isolate.current as dynamic).createUriForKernelBlob(bytes);
+ }
+
+ Future<void> runWithServer(
+ Future<void> Function(RequestChannel) f,
+ ) async {
+ final testFinished = Completer<void>();
+ final serverSocket = await loopbackServerSocket();
+
+ serverSocket.listen((socket) async {
+ final requestChannel = RequestChannel(socket);
+
+ try {
+ await f(requestChannel);
+ } finally {
+ requestChannel.sendRequest('stop', {});
+ socket.destroy();
+ serverSocket.close();
+ testFinished.complete();
+ }
+ });
+
+ final host = serverSocket.address.address;
+ final addressStr = '$host:${serverSocket.port}';
+ expect(await starter(['--binary-protocol-address=${addressStr}']), 0);
+
+ await testFinished.future;
+ }
+
+ group('dill.put', () {
+ test('not Map argument', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('dill.put', 42);
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('no field: uri', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('dill.put', {});
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('no field: bytes', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('dill.put', {
+ 'uri': 'vm:dill',
+ });
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('OK', () async {
+ await runWithServer((requestChannel) async {
+ await requestChannel.sendRequest<Uint8List>('dill.put', {
+ 'uri': 'vm:dill',
+ 'bytes': Uint8List(256),
+ });
+ });
+ });
+ });
+
+ group('dill.remove', () {
+ test('not Map argument', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('dill.remove', 42);
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('no field: uri', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('dill.remove', {});
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('OK', () async {
+ await runWithServer((requestChannel) async {
+ await requestChannel.sendRequest<Uint8List>('dill.remove', {
+ 'uri': 'vm:dill',
+ });
+ });
+ });
+ });
+
+ group('kernelForProgram', () {
+ test('not Map argument', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>(
+ 'kernelForProgram',
+ 42,
+ );
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('no field: sdkSummary', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>(
+ 'kernelForProgram',
+ {},
+ );
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('no field: uri', () async {
+ await runWithServer((requestChannel) async {
+ try {
+ await requestChannel.sendRequest<Uint8List>('kernelForProgram', {
+ 'sdkSummary': 'dill:vm',
+ });
+ fail('Expected RemoteException');
+ } on RemoteException {}
+ });
+ });
+
+ test('compiles', () async {
+ await runWithServer((requestChannel) async {
+ addFileCallbacks(requestChannel);
+
+ await requestChannel.sendRequest<void>('dill.put', {
+ 'uri': 'dill:vm',
+ 'bytes': File(
+ path.join(
+ path.dirname(path.dirname(Platform.resolvedExecutable)),
+ 'lib',
+ '_internal',
+ 'vm_platform_strong.dill',
+ ),
+ ).readAsBytesSync(),
+ });
+
+ fileContentMap[Uri.parse('file:///home/test/lib/test.dart')] = r'''
+import 'dart:isolate';
+void main(List<String> arguments, SendPort sendPort) {
+ sendPort.send(42);
+}
+''';
+
+ final kernelBytes = await requestChannel.sendRequest<Uint8List>(
+ 'kernelForProgram',
+ {
+ 'sdkSummary': 'dill:vm',
+ 'uri': 'file:///home/test/lib/test.dart',
+ },
+ );
+
+ expect(kernelBytes, hasLength(greaterThan(200)));
+ final kernelUri = registerKernelBlob(kernelBytes);
+
+ final receivePort = ReceivePort();
+ await Isolate.spawnUri(kernelUri, [], receivePort.sendPort);
+ expect(await receivePort.first, 42);
+ });
+ });
+ });
+ });
+
test('compile to JavaScript', () async {
var file = File('${tempDir.path}/foo.dart')..createSync();
file.writeAsStringSync("main() {\n}\n");
@@ -2897,30 +3098,15 @@
}
}
-class Result {
- String status;
- List<String> sources;
-
- Result(this.status, this.sources);
-
- void expectNoErrors({String filename}) {
- var result = CompilationResult.parse(status);
- expect(result.errorsCount, equals(0));
- if (filename != null) {
- expect(result.filename, equals(filename));
- }
- }
-}
-
class OutputParser {
- OutputParser(this._receivedResults);
bool expectSources = true;
-
StreamController<Result> _receivedResults;
- List<String> _receivedSources;
+ List<String> _receivedSources;
String _boundaryKey;
+
bool _readingSources;
+ OutputParser(this._receivedResults);
void listener(String s) {
if (_boundaryKey == null) {
@@ -2959,3 +3145,27 @@
}
}
}
+
+class Result {
+ String status;
+ List<String> sources;
+
+ Result(this.status, this.sources);
+
+ void expectNoErrors({String filename}) {
+ var result = CompilationResult.parse(status);
+ expect(result.errorsCount, equals(0));
+ if (filename != null) {
+ expect(result.filename, equals(filename));
+ }
+ }
+}
+
+class _MockedBinaryPrinter extends Mock implements BinaryPrinter {}
+
+class _MockedBinaryPrinterFactory extends Mock implements BinaryPrinterFactory {
+}
+
+class _MockedCompiler extends Mock implements CompilerInterface {}
+
+class _MockedIncrementalCompiler extends Mock implements IncrementalCompiler {}
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index fde11b60..3bdaac0 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -3691,7 +3691,7 @@
instruction()->deopt_id(),
instruction()->source());
} else {
- __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+ __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
compiler->EmitCallsiteMetadata(
instruction()->source(), instruction()->deopt_id(),
UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 7506894..88f0e9a 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -3247,7 +3247,7 @@
instruction()->deopt_id(),
instruction()->source());
} else {
- __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+ __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
compiler->EmitCallsiteMetadata(
instruction()->source(), instruction()->deopt_id(),
UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index 4869e16..06243b2 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -2898,7 +2898,7 @@
instruction(), /*num_slow_path_args=*/0);
compiler->pending_deoptimization_env_ = env;
- __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+ __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
compiler->EmitCallsiteMetadata(
instruction()->source(), instruction()->deopt_id(),
UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_riscv.cc b/runtime/vm/compiler/backend/il_riscv.cc
index a029815..b24fdb4 100644
--- a/runtime/vm/compiler/backend/il_riscv.cc
+++ b/runtime/vm/compiler/backend/il_riscv.cc
@@ -3494,7 +3494,7 @@
instruction()->deopt_id(),
instruction()->source());
} else {
- __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+ __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
compiler->EmitCallsiteMetadata(
instruction()->source(), instruction()->deopt_id(),
UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 023874d..ae2531a 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -3325,7 +3325,7 @@
instruction()->deopt_id(),
instruction()->source());
} else {
- __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+ __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
compiler->EmitCallsiteMetadata(
instruction()->source(), instruction()->deopt_id(),
UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 199ae30..33ef038 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -1037,7 +1037,8 @@
void StubCodeCompiler::GenerateStackOverflowSharedWithoutFPURegsStub(
Assembler* assembler) {
GenerateSharedStub(
- assembler, /*save_fpu_registers=*/false, &kStackOverflowRuntimeEntry,
+ assembler, /*save_fpu_registers=*/false,
+ &kInterruptOrStackOverflowRuntimeEntry,
target::Thread::stack_overflow_shared_without_fpu_regs_stub_offset(),
/*allow_return=*/true);
}
@@ -1045,7 +1046,8 @@
void StubCodeCompiler::GenerateStackOverflowSharedWithFPURegsStub(
Assembler* assembler) {
GenerateSharedStub(
- assembler, /*save_fpu_registers=*/true, &kStackOverflowRuntimeEntry,
+ assembler, /*save_fpu_registers=*/true,
+ &kInterruptOrStackOverflowRuntimeEntry,
target::Thread::stack_overflow_shared_with_fpu_regs_stub_offset(),
/*allow_return=*/true);
}
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 2fb6faf..a249423 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -2853,7 +2853,7 @@
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
-DEFINE_RUNTIME_ENTRY(StackOverflow, 0) {
+DEFINE_RUNTIME_ENTRY(InterruptOrStackOverflow, 0) {
#if defined(USING_SIMULATOR)
uword stack_pos = Simulator::Current()->get_sp();
// If simulator was never called it may return 0 as a value of SPREG.
diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h
index 5731d55..857784b 100644
--- a/runtime/vm/runtime_entry_list.h
+++ b/runtime/vm/runtime_entry_list.h
@@ -51,7 +51,7 @@
V(ArgumentErrorUnboxedInt64) \
V(IntegerDivisionByZeroException) \
V(ReThrow) \
- V(StackOverflow) \
+ V(InterruptOrStackOverflow) \
V(Throw) \
V(DeoptimizeMaterialize) \
V(RewindPostDeopt) \
diff --git a/tools/VERSION b/tools/VERSION
index d5f67ab..c45d641 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 18
PATCH 0
-PRERELEASE 19
+PRERELEASE 20
PRERELEASE_PATCH 0
\ No newline at end of file