Version 2.14.0-107.0.dev
Merge commit '33aa207efd4f51bfa9bf0c355c62a8099a221cf3' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 2f5efdc..ac72d20 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -253,6 +253,18 @@
"languageVersion": "2.3"
},
{
+ "name": "devtools_server",
+ "rootUri": "../third_party/devtools/devtools_server",
+ "packageUri": "lib/",
+ "languageVersion": "2.6"
+ },
+ {
+ "name": "devtools_shared",
+ "rootUri": "../third_party/devtools/devtools_shared",
+ "packageUri": "lib/",
+ "languageVersion": "2.3"
+ },
+ {
"name": "diagnostic",
"rootUri": "../pkg/diagnostic",
"packageUri": "lib/",
diff --git a/.packages b/.packages
index 7cbfb89..1631e03 100644
--- a/.packages
+++ b/.packages
@@ -21,6 +21,7 @@
benchmark_harness:third_party/pkg/benchmark_harness/lib
boolean_selector:third_party/pkg/boolean_selector/lib
build_integration:pkg/build_integration/lib
+browser_launcher:third_party/pkg/browser_launcher/lib
charcode:third_party/pkg/charcode/lib
cli_util:third_party/pkg/cli_util/lib
collection:third_party/pkg/collection/lib
@@ -37,6 +38,7 @@
dartdoc:third_party/pkg/dartdoc/lib
dds:pkg/dds/lib
dev_compiler:pkg/dev_compiler/lib
+devtools_shared:third_party/devtools/devtools_shared/lib
diagnostic:pkg/diagnostic/lib
expect:pkg/expect/lib
ffi:third_party/pkg/ffi/lib
diff --git a/DEPS b/DEPS
index d17ac02..1505b2a 100644
--- a/DEPS
+++ b/DEPS
@@ -80,6 +80,7 @@
"boringssl_gen_rev": "7322fc15cc065d8d2957fccce6b62a509dc4d641",
"boringssl_rev" : "1607f54fed72c6589d560254626909a64124f091",
"browser-compat-data_tag": "v1.0.22",
+ "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"charcode_rev": "bcd8a12c315b7a83390e4865ad847ecd9344cba2",
"chrome_rev" : "19997",
"cli_util_rev" : "8c504de5deb08fe32ecf51f9662bb37d8c708e57",
@@ -105,7 +106,6 @@
"dart_style_rev": "f17c23e0eea9a870601c19d904e2a9c1a7c81470",
"chromedriver_tag": "83.0.4103.39",
- "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"dartdoc_rev" : "505f163f7cb48e917503e4a23fbff1227e08b263",
"devtools_rev" : "12ad5341ae0a275042c84a4e7be9a6c98db65612",
"jsshell_tag": "version:88.0",
@@ -320,6 +320,9 @@
Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' +
"@" + Var("browser-compat-data_tag"),
+ Var("dart_root") + "/third_party/pkg/browser_launcher":
+ Var("dart_git") + "browser_launcher.git" + "@" + Var("browser_launcher_rev"),
+
Var("dart_root") + "/third_party/tcmalloc/gperftools":
Var('chromium_git') + '/external/github.com/gperftools/gperftools.git' +
"@" + Var("gperftools_revision"),
@@ -336,9 +339,6 @@
Var("dart_root") + "/third_party/pkg/boolean_selector":
Var("dart_git") + "boolean_selector.git" +
"@" + Var("boolean_selector_rev"),
- Var("dart_root") + "/third_party/pkg/browser_launcher":
- Var("dart_git") + "browser_launcher.git" +
- "@" + Var("browser_launcher_rev"),
Var("dart_root") + "/third_party/pkg/charcode":
Var("dart_git") + "charcode.git" + "@" + Var("charcode_rev"),
Var("dart_root") + "/third_party/pkg/cli_util":
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index 1722c990..6b5ed50 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -398,8 +398,7 @@
### ambiguous_extension_member_access
-_A member named '{0}' is defined in extensions {1}, and neither is more
-specific._
+_A member named '{0}' is defined in extensions {1}, and none are more specific._
#### Description
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index b72fbba..5e581c5 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -40,7 +40,8 @@
args = args
.where(
(element) => !(element.contains('--observe') ||
- element.contains('--enable-vm-service')),
+ element.contains('--enable-vm-service') ||
+ element.contains('--devtools')),
)
.toList();
}
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index db32bfe..d996c3c 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -158,6 +158,10 @@
hide: !verbose,
negatable: false,
help: 'Enables tracing of library and script loading.',
+ )
+ ..addFlag(
+ 'debug-dds',
+ hide: true,
);
addExperimentalFlags(argParser, verbose);
}
@@ -179,13 +183,18 @@
String launchDdsArg = argResults['launch-dds'];
String ddsHost = '';
String ddsPort = '';
+
+ // TODO(bkonyi): allow for users to choose not to launch DevTools
+ // See https://github.com/dart-lang/sdk/issues/45867.
+ const bool launchDevTools = true;
bool launchDds = false;
if (launchDdsArg != null) {
launchDds = true;
- final ddsUrl = launchDdsArg.split(':');
+ final ddsUrl = launchDdsArg.split('\\:');
ddsHost = ddsUrl[0];
ddsPort = ddsUrl[1];
}
+ final bool debugDds = argResults['debug-dds'];
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
@@ -198,7 +207,12 @@
if (launchDds) {
debugSession = _DebuggingSession();
if (!await debugSession.start(
- ddsHost, ddsPort, disableServiceAuthCodes)) {
+ ddsHost,
+ ddsPort,
+ disableServiceAuthCodes,
+ launchDevTools,
+ debugDds,
+ )) {
return errorExitCode;
}
}
@@ -242,10 +256,19 @@
class _DebuggingSession {
Future<bool> start(
- String host, String port, bool disableServiceAuthCodes) async {
- final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ bool debugDds,
+ ) async {
+ final sdkDir = dirname(sdk.dart);
+ final fullSdk = sdkDir.endsWith('bin');
+ final ddsSnapshot = fullSdk
? sdk.ddsSnapshot
- : absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
+ : absolute(sdkDir, 'gen', 'dds.dart.snapshot');
+ final devToolsBinaries =
+ fullSdk ? sdk.devToolsBinaries : absolute(sdkDir, 'devtools');
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
return false;
}
@@ -256,30 +279,51 @@
serviceInfo = await Service.getInfo();
}
final process = await Process.start(
- sdk.dart,
- [
- if (dirname(sdk.dart).endsWith('bin'))
- sdk.ddsSnapshot
- else
- absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
- serviceInfo.serverUri.toString(),
- host,
- port,
- disableServiceAuthCodes.toString(),
- ],
- mode: ProcessStartMode.detachedWithStdio);
+ sdk.dart,
+ [
+ if (debugDds) '--enable-vm-service=0',
+ ddsSnapshot,
+ serviceInfo.serverUri.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ debugDds.toString(),
+ ],
+ mode: ProcessStartMode.detachedWithStdio,
+ );
final completer = Completer<void>();
- StreamSubscription sub;
- sub = process.stderr.transform(utf8.decoder).listen((event) {
- if (event == 'DDS started') {
- sub.cancel();
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at:';
+ if (debugDds) {
+ StreamSubscription stdoutSub;
+ stdoutSub = process.stdout.transform(utf8.decoder).listen((event) {
+ if (event.startsWith(devToolsMessagePrefix)) {
+ final ddsDebuggingUri = event.split(' ').last;
+ print(
+ 'A DevTools debugger for DDS is available at: $ddsDebuggingUri',
+ );
+ stdoutSub.cancel();
+ }
+ });
+ }
+ StreamSubscription stderrSub;
+ stderrSub = process.stderr.transform(utf8.decoder).listen((event) {
+ final result = json.decode(event) as Map<String, dynamic>;
+ final state = result['state'];
+ if (state == 'started') {
+ if (result.containsKey('devToolsUri')) {
+ final devToolsUri = result['devToolsUri'];
+ print('$devToolsMessagePrefix $devToolsUri');
+ }
+ stderrSub.cancel();
completer.complete();
- } else if (event.contains('Failed to start DDS')) {
- sub.cancel();
- completer.completeError(event.replaceAll(
- 'Failed to start DDS',
+ } else {
+ stderrSub.cancel();
+ completer.completeError(
'Could not start Observatory HTTP server',
- ));
+ );
}
});
try {
diff --git a/pkg/dartdev/lib/src/sdk.dart b/pkg/dartdev/lib/src/sdk.dart
index 3e8a12a..426f810 100644
--- a/pkg/dartdev/lib/src/sdk.dart
+++ b/pkg/dartdev/lib/src/sdk.dart
@@ -68,6 +68,13 @@
'dds.dart.snapshot',
);
+ String get devToolsBinaries => path.absolute(
+ sdkPath,
+ 'bin',
+ 'resources',
+ 'devtools',
+ );
+
String get pubSnapshot => path.absolute(
sdkPath,
'bin',
diff --git a/pkg/dartdev/test/commands/run_test.dart b/pkg/dartdev/test/commands/run_test.dart
index b8cc5e8..58a4d56 100644
--- a/pkg/dartdev/test/commands/run_test.dart
+++ b/pkg/dartdev/test/commands/run_test.dart
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'dart:async';
+import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
@@ -300,4 +302,61 @@
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
});
+
+ group('DevTools', () {
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at: http://127.0.0.1:';
+
+ test('spawn simple', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout, contains(devToolsMessagePrefix));
+ });
+
+ test('implicit spawn', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ '--enable-vm-service',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout, contains(devToolsMessagePrefix));
+ });
+
+ test(
+ 'spawn via SIGQUIT',
+ () async {
+ p = project(
+ mainSrc:
+ 'void main() { print("ready"); int i = 0; while(true) { i++; } }',
+ );
+ Process process = await p.start([
+ p.relativeFilePath,
+ ]);
+
+ final readyCompleter = Completer<void>();
+ final completer = Completer<void>();
+
+ StreamSubscription sub;
+ sub = process.stdout.transform(utf8.decoder).listen((event) async {
+ if (event.contains('ready')) {
+ readyCompleter.complete();
+ } else if (event.contains(devToolsMessagePrefix)) {
+ await sub.cancel();
+ completer.complete();
+ }
+ });
+ // Wait for process to start.
+ await readyCompleter.future;
+ process.kill(ProcessSignal.sigquit);
+ await completer.future;
+ process.kill();
+ },
+ // No support for SIGQUIT on Windows.
+ skip: Platform.isWindows,
+ );
+ });
}
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 3db7549..b39156e 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,4 +1,5 @@
-# 1.7.7-dev
+# 1.8.0-dev
+- Add support for launching DevTools from DDS.
- Fixed issue where two clients subscribing to the same stream in close succession
could result in DDS sending multiple `streamListen` requests to the VM service.
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index 9937d26..45673f5 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -4,6 +4,7 @@
// @dart=2.10
+import 'dart:convert';
import 'dart:io';
import 'package:dds/dds.dart';
@@ -16,6 +17,9 @@
/// - DDS bind address
/// - DDS port
/// - Disable service authentication codes
+/// - Start DevTools
+/// - DevTools build directory
+/// - Enable logging
Future<void> main(List<String> args) async {
if (args.isEmpty) return;
@@ -37,16 +41,37 @@
port: int.parse(args[2]),
);
final disableServiceAuthCodes = args[3] == 'true';
+
+ final startDevTools = args[4] == 'true';
+ Uri devToolsBuildDirectory;
+ if (args[5].isNotEmpty) {
+ devToolsBuildDirectory = Uri.file(args[5]);
+ }
+ final logRequests = args[6] == 'true';
try {
// TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
// See https://github.com/dart-lang/sdk/issues/43192.
- await DartDevelopmentService.startDartDevelopmentService(
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
serviceUri: serviceUri,
enableAuthCodes: !disableServiceAuthCodes,
+ devToolsConfiguration: startDevTools
+ ? DevToolsConfiguration(
+ enable: startDevTools,
+ customBuildDirectoryPath: devToolsBuildDirectory,
+ )
+ : null,
+ logRequests: logRequests,
);
- stderr.write('DDS started');
- } catch (e) {
- stderr.writeln('Failed to start DDS:\n$e');
+ stderr.write(json.encode({
+ 'state': 'started',
+ if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(),
+ }));
+ } catch (e, st) {
+ stderr.write(json.encode({
+ 'state': 'error',
+ 'error': '$e',
+ 'stacktrace': '$st',
+ }));
}
}
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index f7c7a05..f0e913b 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -44,6 +44,8 @@
Uri serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
+ DevToolsConfiguration devToolsConfiguration = const DevToolsConfiguration(),
+ bool logRequests = false,
}) async {
if (remoteVmServiceUri == null) {
throw ArgumentError.notNull('remoteVmServiceUri');
@@ -80,6 +82,8 @@
serviceUri,
enableAuthCodes,
ipv6,
+ devToolsConfiguration,
+ logRequests,
);
await service.startService();
return service;
@@ -125,6 +129,11 @@
/// Returns `null` if the service is not running.
Uri get wsUri;
+ /// The HTTP [Uri] of the hosted DevTools instance.
+ ///
+ /// Returns `null` if DevTools is not running.
+ Uri get devToolsUri;
+
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
/// requests.
bool get isRunning;
@@ -168,3 +177,13 @@
final int errorCode;
final String message;
}
+
+class DevToolsConfiguration {
+ const DevToolsConfiguration({
+ this.enable = false,
+ this.customBuildDirectoryPath,
+ });
+
+ final bool enable;
+ final Uri customBuildDirectoryPath;
+}
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index f81bd25..9ceb162 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -21,27 +21,25 @@
/// Representation of a single DDS client which manages the connection and
/// DDS request intercepting / forwarding.
class DartDevelopmentServiceClient {
- factory DartDevelopmentServiceClient.fromWebSocket(
+ DartDevelopmentServiceClient.fromWebSocket(
DartDevelopmentService dds,
WebSocketChannel ws,
json_rpc.Peer vmServicePeer,
- ) =>
- DartDevelopmentServiceClient._(
- dds,
- ws,
- vmServicePeer,
- );
+ ) : this._(
+ dds,
+ ws,
+ vmServicePeer,
+ );
- factory DartDevelopmentServiceClient.fromSSEConnection(
+ DartDevelopmentServiceClient.fromSSEConnection(
DartDevelopmentService dds,
SseConnection sse,
json_rpc.Peer vmServicePeer,
- ) =>
- DartDevelopmentServiceClient._(
- dds,
- sse,
- vmServicePeer,
- );
+ ) : this._(
+ dds,
+ sse,
+ vmServicePeer,
+ );
DartDevelopmentServiceClient._(
this.dds,
diff --git a/pkg/dds/lib/src/constants.dart b/pkg/dds/lib/src/constants.dart
index 2466390..b69ae7c 100644
--- a/pkg/dds/lib/src/constants.dart
+++ b/pkg/dds/lib/src/constants.dart
@@ -16,6 +16,10 @@
};
}
+// Give connections time to reestablish before considering them closed.
+// Required to reestablish connections killed by UberProxy.
+const sseKeepAlive = Duration(seconds: 30);
+
abstract class PauseTypeMasks {
static const pauseOnStartMask = 1 << 0;
static const pauseOnReloadMask = 1 << 1;
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index db355fb..c28523d 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -24,6 +24,8 @@
import 'binary_compatible_peer.dart';
import 'client.dart';
import 'client_manager.dart';
+import 'constants.dart';
+import 'devtools/devtools_handler.dart';
import 'expression_evaluator.dart';
import 'isolate_manager.dart';
import 'stream_manager.dart';
@@ -51,7 +53,13 @@
class DartDevelopmentServiceImpl implements DartDevelopmentService {
DartDevelopmentServiceImpl(
- this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
+ this._remoteVmServiceUri,
+ this._uri,
+ this._authCodesEnabled,
+ this._ipv6,
+ this._devToolsConfiguration,
+ this.shouldLogRequests,
+ ) {
_clientManager = ClientManager(this);
_expressionEvaluator = ExpressionEvaluator(this);
_isolateManager = IsolateManager(this);
@@ -113,20 +121,26 @@
(_ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4)
.host;
final port = uri?.port ?? 0;
-
+ var pipeline = const Pipeline();
+ if (shouldLogRequests) {
+ pipeline = pipeline.addMiddleware(
+ logRequests(
+ logger: (String message, bool isError) {
+ print('Log: $message');
+ },
+ ),
+ );
+ }
+ pipeline = pipeline.addMiddleware(_authCodeMiddleware);
+ final handler = pipeline.addHandler(_handlers().handler);
// Start the DDS server.
- _server = await io.serve(
- const Pipeline()
- .addMiddleware(_authCodeMiddleware)
- .addHandler(_handlers().handler),
- host,
- port);
+ _server = await io.serve(handler, host, port);
final tmpUri = Uri(
scheme: 'http',
host: host,
port: _server.port,
- path: '$_authCode/',
+ path: '$authCode/',
);
// Notify the VM service that this client is DDS and that it should close
@@ -157,7 +171,7 @@
return;
}
_shuttingDown = true;
- // Don't accept anymore HTTP requests.
+ // Don't accept any more HTTP requests.
await _server?.close();
// Close connections to clients.
@@ -197,7 +211,7 @@
return forbidden;
}
final authToken = pathSegments[0];
- if (authToken != _authCode) {
+ if (authToken != authCode) {
return forbidden;
}
// Creates a new request with the authentication code stripped from
@@ -233,18 +247,12 @@
});
Handler _sseHandler() {
- // Give connections time to reestablish before considering them closed.
- // Required to reestablish connections killed by UberProxy.
- const keepAlive = Duration(seconds: 30);
- final handler = authCodesEnabled
- ? SseHandler(
- Uri.parse('/$_authCode/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- )
- : SseHandler(
- Uri.parse('/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- );
+ final handler = SseHandler(
+ authCodesEnabled
+ ? Uri.parse('/$authCode/$_kSseHandlerPath')
+ : Uri.parse('/$_kSseHandlerPath'),
+ keepAlive: sseKeepAlive,
+ );
handler.connections.rest.listen((sseConnection) {
final client = DartDevelopmentServiceClient.fromSSEConnection(
@@ -259,10 +267,18 @@
}
Handler _httpHandler() {
- // DDS doesn't support any HTTP requests itself, so we just forward all of
- // them to the VM service.
- final cascade = Cascade().add(proxyHandler(remoteVmServiceUri));
- return cascade.handler;
+ if (_devToolsConfiguration != null && _devToolsConfiguration.enable) {
+ // Install the DevTools handlers and forward any unhandled HTTP requests to
+ // the VM service.
+ final buildDir =
+ _devToolsConfiguration.customBuildDirectoryPath?.toFilePath();
+ return devtoolsHandler(
+ dds: this,
+ buildDir: buildDir,
+ notFoundHandler: proxyHandler(remoteVmServiceUri),
+ );
+ }
+ return proxyHandler(remoteVmServiceUri);
}
List<String> _cleanupPathSegments(Uri uri) {
@@ -296,34 +312,63 @@
return uri.replace(scheme: 'sse', pathSegments: pathSegments);
}
+ Uri _toDevTools(Uri uri) {
+ // The DevTools URI is a bit strange as the query parameters appear after
+ // the fragment. There's no nice way to encode the query parameters
+ // properly, so we create another Uri just to grab the formatted query.
+ // The result will need to have '/?' prepended when being used as the
+ // fragment to get the correct format.
+ final query = Uri(
+ queryParameters: {
+ 'uri': wsUri.toString(),
+ },
+ ).query;
+ return Uri(
+ scheme: 'http',
+ host: uri.host,
+ port: uri.port,
+ pathSegments: [
+ ...uri.pathSegments.where(
+ (e) => e.isNotEmpty,
+ ),
+ 'devtools',
+ '',
+ ],
+ fragment: '/?$query',
+ );
+ }
+
String getNamespace(DartDevelopmentServiceClient client) =>
clientManager.clients.keyOf(client);
- @override
bool get authCodesEnabled => _authCodesEnabled;
final bool _authCodesEnabled;
+ String get authCode => _authCode;
String _authCode;
- @override
+ final bool shouldLogRequests;
+
Uri get remoteVmServiceUri => _remoteVmServiceUri;
- @override
Uri get remoteVmServiceWsUri => _toWebSocket(_remoteVmServiceUri);
Uri _remoteVmServiceUri;
- @override
Uri get uri => _uri;
+ Uri _uri;
- @override
Uri get sseUri => _toSse(_uri);
Uri get wsUri => _toWebSocket(_uri);
- Uri _uri;
+
+ Uri get devToolsUri =>
+ _devToolsConfiguration.enable ? _toDevTools(_uri) : null;
final bool _ipv6;
bool get isRunning => _uri != null;
+ final DevToolsConfiguration _devToolsConfiguration;
+
Future<void> get done => _done.future;
Completer _done = Completer<void>();
bool _shuttingDown = false;
diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart
new file mode 100644
index 0000000..5f8670e
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_client.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:json_rpc_2/src/server.dart' as json_rpc;
+import 'package:sse/src/server/sse_handler.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import 'server_api.dart';
+
+class LoggingMiddlewareSink<S> implements StreamSink<S> {
+ LoggingMiddlewareSink(this.sink);
+
+ @override
+ void add(S event) {
+ print('DevTools SSE response: $event');
+ sink.add(event);
+ }
+
+ @override
+ void addError(Object error, [StackTrace stackTrace]) {
+ print('DevTools SSE error response: $error');
+ sink.addError(error);
+ }
+
+ @override
+ Future addStream(Stream<S> stream) {
+ return sink.addStream(stream);
+ }
+
+ @override
+ Future close() => sink.close();
+
+ @override
+ Future get done => sink.done;
+
+ final StreamSink sink;
+}
+
+/// Represents a DevTools client connection to the DevTools server API.
+class DevToolsClient {
+ DevToolsClient.fromSSEConnection(
+ SseConnection sse,
+ bool loggingEnabled,
+ ) {
+ Stream<String> stream = sse.stream;
+ StreamSink sink = sse.sink;
+
+ if (loggingEnabled) {
+ stream = stream.map<String>((String e) {
+ print('DevTools SSE request: $e');
+ return e;
+ });
+ sink = LoggingMiddlewareSink(sink);
+ }
+
+ _server = json_rpc.Server(
+ StreamChannel(stream, sink),
+ strictProtocolChecks: false,
+ );
+ _registerJsonRpcMethods();
+ _server.listen();
+ }
+
+ void _registerJsonRpcMethods() {
+ _server.registerMethod('connected', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('currentPage', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('disconnected', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('getPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = ServerApi.devToolsPreferences.properties[key];
+ return value;
+ });
+
+ _server.registerMethod('setPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = parameters['value'].value;
+ ServerApi.devToolsPreferences.properties[key] = value;
+ });
+ }
+
+ json_rpc.Server _server;
+}
diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/devtools_handler.dart
new file mode 100644
index 0000000..a0a4bf1
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_handler.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:dds/src/constants.dart';
+import 'package:meta/meta.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:sse/server/sse_handler.dart';
+
+import '../dds_impl.dart';
+import 'devtools_client.dart';
+import 'server_api.dart';
+
+/// Returns a [Handler] which handles serving DevTools and the DevTools server
+/// API under $DDS_URI/devtools/.
+///
+/// [buildDir] is the path to the pre-compiled DevTools instance to be served.
+///
+/// [notFoundHandler] is a [Handler] to which requests that could not be handled
+/// by the DevTools handler are forwarded (e.g., a proxy to the VM service).
+FutureOr<Handler> devtoolsHandler({
+ @required DartDevelopmentServiceImpl dds,
+ @required String buildDir,
+ @required Handler notFoundHandler,
+}) {
+ // Serves the web assets for DevTools.
+ final devtoolsAssetHandler = createStaticHandler(
+ buildDir,
+ defaultDocument: 'index.html',
+ );
+
+ // Support DevTools client-server interface via SSE.
+ // Note: the handler path needs to match the full *original* path, not the
+ // current request URL (we remove '/devtools' in the initial router but we
+ // need to include it here).
+ const devToolsSseHandlerPath = '/devtools/api/sse';
+ final devToolsApiHandler = SseHandler(
+ dds.authCodesEnabled
+ ? Uri.parse('/${dds.authCode}$devToolsSseHandlerPath')
+ : Uri.parse(devToolsSseHandlerPath),
+ keepAlive: sseKeepAlive,
+ );
+
+ devToolsApiHandler.connections.rest.listen(
+ (sseConnection) => DevToolsClient.fromSSEConnection(
+ sseConnection,
+ dds.shouldLogRequests,
+ ),
+ );
+
+ final devtoolsHandler = (Request request) {
+ // If the request isn't of the form api/<method> assume it's a request for
+ // DevTools assets.
+ if (request.url.pathSegments.length < 2 ||
+ request.url.pathSegments.first != 'api') {
+ return devtoolsAssetHandler(request);
+ }
+ final method = request.url.pathSegments[1];
+ if (method == 'ping') {
+ // Note: we have an 'OK' body response, otherwise the response has an
+ // incorrect status code (204 instead of 200).
+ return Response.ok('OK');
+ }
+ if (method == 'sse') {
+ return devToolsApiHandler.handler(request);
+ }
+ if (!ServerApi.canHandle(request)) {
+ return Response.notFound('$method is not a valid API');
+ }
+ return ServerApi.handle(request);
+ };
+
+ return (request) {
+ final pathSegments = request.url.pathSegments;
+ if (pathSegments.isEmpty || pathSegments.first != 'devtools') {
+ return notFoundHandler(request);
+ }
+ // Forward all requests to /devtools/* to the DevTools handler.
+ request = request.change(path: 'devtools');
+ return devtoolsHandler(request);
+ };
+}
diff --git a/pkg/dds/lib/src/devtools/file_system.dart b/pkg/dds/lib/src/devtools/file_system.dart
new file mode 100644
index 0000000..9a05de7
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/file_system.dart
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
+import 'usage.dart';
+
+class LocalFileSystem {
+ static String _userHomeDir() {
+ final String envKey =
+ Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
+ final String value = Platform.environment[envKey];
+ return value == null ? '.' : value;
+ }
+
+ /// Returns the path to the DevTools storage directory.
+ static String devToolsDir() {
+ return path.join(_userHomeDir(), '.flutter-devtools');
+ }
+
+ /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools file
+ /// exists in the user's home directory.
+ static void maybeMoveLegacyDevToolsStore() {
+ final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName));
+ if (file.existsSync()) {
+ ensureDevToolsDirectory();
+ file.copySync(path.join(devToolsDir(), DevToolsUsage.storeName));
+ file.deleteSync();
+ }
+ }
+
+ /// Creates the ~/.flutter-devtools directory if it does not already exist.
+ static void ensureDevToolsDirectory() {
+ Directory('${LocalFileSystem.devToolsDir()}').createSync();
+ }
+
+ /// Returns a DevTools file from the given path.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static File devToolsFileFromPath(String pathFromDevToolsDir) {
+ if (pathFromDevToolsDir.contains('..')) {
+ // The passed in path should not be able to walk up the directory tree
+ // outside of the ~/.flutter-devtools/ directory.
+ return null;
+ }
+ ensureDevToolsDirectory();
+ final file = File(path.join(devToolsDir(), pathFromDevToolsDir));
+ if (!file.existsSync()) {
+ return null;
+ }
+ return file;
+ }
+
+ /// Returns a DevTools file from the given path as encoded json.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static String devToolsFileAsJson(String pathFromDevToolsDir) {
+ final file = devToolsFileFromPath(pathFromDevToolsDir);
+ if (file == null) return null;
+
+ final fileName = path.basename(file.path);
+ if (!fileName.endsWith('.json')) return null;
+
+ final content = file.readAsStringSync();
+ final json = jsonDecode(content);
+ json['lastModifiedTime'] = file.lastModifiedSync().toString();
+ return jsonEncode(json);
+ }
+
+ /// Whether the flutter store file exists.
+ static bool flutterStoreExists() {
+ final flutterStore = File('${_userHomeDir()}/.flutter');
+ return flutterStore.existsSync();
+ }
+}
diff --git a/pkg/dds/lib/src/devtools/server_api.dart b/pkg/dds/lib/src/devtools/server_api.dart
new file mode 100644
index 0000000..b866f44
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/server_api.dart
@@ -0,0 +1,230 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:shelf/shelf.dart' as shelf;
+
+import 'file_system.dart';
+import 'usage.dart';
+
+/// The DevTools server API.
+///
+/// This defines endpoints that serve all requests that come in over api/.
+class ServerApi {
+ static const errorNoActiveSurvey = 'ERROR: setActiveSurvey not called.';
+
+ /// Determines whether or not [request] is an API call.
+ static bool canHandle(shelf.Request request) {
+ return request.url.path.startsWith(apiPrefix);
+ }
+
+ /// Handles all requests.
+ ///
+ /// To override an API call, pass in a subclass of [ServerApi].
+ static FutureOr<shelf.Response> handle(
+ shelf.Request request, [
+ ServerApi api,
+ ]) {
+ api ??= ServerApi();
+ switch (request.url.path) {
+ // ----- Flutter Tool GA store. -----
+ case apiGetFlutterGAEnabled:
+ // Is Analytics collection enabled?
+ return api.getCompleted(
+ request,
+ json.encode(FlutterUsage.doesStoreExist ? _usage.enabled : null),
+ );
+ case apiGetFlutterGAClientId:
+ // Flutter Tool GA clientId - ONLY get Flutter's clientId if enabled is
+ // true.
+ return (FlutterUsage.doesStoreExist)
+ ? api.getCompleted(
+ request,
+ json.encode(_usage.enabled ? _usage.clientId : null),
+ )
+ : api.getCompleted(
+ request,
+ json.encode(null),
+ );
+
+ // ----- DevTools GA store. -----
+
+ case apiResetDevTools:
+ _devToolsUsage.reset();
+ return api.getCompleted(request, json.encode(true));
+ case apiGetDevToolsFirstRun:
+ // Has DevTools been run first time? To bring up welcome screen.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.isFirstRun),
+ );
+ case apiGetDevToolsEnabled:
+ // Is DevTools Analytics collection enabled?
+ return api.getCompleted(request, json.encode(_devToolsUsage.enabled));
+ case apiSetDevToolsEnabled:
+ // Enable or disable DevTools analytics collection.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(devToolsEnabledPropertyName)) {
+ _devToolsUsage.enabled =
+ json.decode(queryParams[devToolsEnabledPropertyName]);
+ }
+ return api.setCompleted(request, json.encode(_devToolsUsage.enabled));
+
+ // ----- DevTools survey store. -----
+
+ case apiSetActiveSurvey:
+ // Assume failure.
+ bool result = false;
+
+ // Set the active survey used to store subsequent apiGetSurveyActionTaken,
+ // apiSetSurveyActionTaken, apiGetSurveyShownCount, and
+ // apiIncrementSurveyShownCount calls.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.keys.length == 1 &&
+ queryParams.containsKey(activeSurveyName)) {
+ final String theSurveyName = queryParams[activeSurveyName];
+
+ // Set the current activeSurvey.
+ _devToolsUsage.activeSurvey = theSurveyName;
+ result = true;
+ }
+
+ return api.getCompleted(request, json.encode(result));
+ case apiGetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyActionTaken');
+ }
+ // SurveyActionTaken has the survey been acted upon (taken or dismissed)
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ // TODO(terry): remove the query param logic for this request.
+ // setSurveyActionTaken should only be called with the value of true, so
+ // we can remove the extra complexity.
+ case apiSetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiSetSurveyActionTaken');
+ }
+ // Set the SurveyActionTaken.
+ // Has the survey been taken or dismissed..
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(surveyActionTakenPropertyName)) {
+ _devToolsUsage.surveyActionTaken =
+ json.decode(queryParams[surveyActionTakenPropertyName]);
+ }
+ return api.setCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ case apiGetSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyShownCount');
+ }
+ // SurveyShownCount how many times have we asked to take survey.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiIncrementSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiIncrementSurveyShownCount');
+ }
+ // Increment the SurveyShownCount, we've asked about the survey.
+ _devToolsUsage.incrementSurveyShownCount();
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiGetBaseAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(baseAppSizeFilePropertyName)) {
+ final filePath = queryParams[baseAppSizeFilePropertyName];
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for base app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$baseAppSizeFilePropertyName');
+ case apiGetTestAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(testAppSizeFilePropertyName)) {
+ final filePath = queryParams[testAppSizeFilePropertyName];
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for test app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$testAppSizeFilePropertyName');
+ default:
+ return api.notImplemented(request);
+ }
+ }
+
+ // Accessing Flutter usage file e.g., ~/.flutter.
+ // NOTE: Only access the file if it exists otherwise Flutter Tool hasn't yet
+ // been run.
+ static final FlutterUsage _usage =
+ FlutterUsage.doesStoreExist ? FlutterUsage() : null;
+
+ // Accessing DevTools usage file e.g., ~/.devtools
+ static final DevToolsUsage _devToolsUsage = DevToolsUsage();
+
+ static DevToolsUsage get devToolsPreferences => _devToolsUsage;
+
+ /// Logs a page view in the DevTools server.
+ ///
+ /// In the open-source version of DevTools, Google Analytics handles this
+ /// without any need to involve the server.
+ FutureOr<shelf.Response> logScreenView(shelf.Request request) =>
+ notImplemented(request);
+
+ /// Return the value of the property.
+ FutureOr<shelf.Response> getCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// Return the value of the property after the property value has been set.
+ FutureOr<shelf.Response> setCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// A [shelf.Response] for API calls that encountered a request problem e.g.,
+ /// setActiveSurvey not called.
+ ///
+ /// This is a 400 Bad Request response.
+ FutureOr<shelf.Response> badRequest([String logError]) {
+ if (logError != null) print(logError);
+ return shelf.Response(HttpStatus.badRequest);
+ }
+
+ /// A [shelf.Response] for API calls that have not been implemented in this
+ /// server.
+ ///
+ /// This is a no-op 204 No Content response because returning 404 Not Found
+ /// creates unnecessary noise in the console.
+ FutureOr<shelf.Response> notImplemented(shelf.Request request) =>
+ shelf.Response(HttpStatus.noContent);
+}
diff --git a/pkg/dds/lib/src/devtools/usage.dart b/pkg/dds/lib/src/devtools/usage.dart
new file mode 100644
index 0000000..afa35d7
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/usage.dart
@@ -0,0 +1,236 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:usage/usage_io.dart';
+
+import 'file_system.dart';
+
+/// Access the file '~/.flutter'.
+class FlutterUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ FlutterUsage({
+ String settingsName = 'flutter',
+ String versionOverride,
+ String configDirOverride,
+ }) {
+ _analytics = AnalyticsIO('', settingsName, '');
+ }
+
+ Analytics _analytics;
+
+ /// Does the .flutter store exist?
+ static bool get doesStoreExist {
+ return LocalFileSystem.flutterStoreExists();
+ }
+
+ bool get isFirstRun => _analytics.firstRun;
+
+ bool get enabled => _analytics.enabled;
+
+ set enabled(bool value) => _analytics.enabled = value;
+
+ String get clientId => _analytics.clientId;
+}
+
+// Access the DevTools on disk store (~/.devtools/.devtools).
+class DevToolsUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ DevToolsUsage({
+ String versionOverride,
+ String configDirOverride,
+ }) {
+ LocalFileSystem.maybeMoveLegacyDevToolsStore();
+ properties = IOPersistentProperties(
+ storeName,
+ documentDirPath: LocalFileSystem.devToolsDir(),
+ );
+ }
+
+ static const storeName = '.devtools';
+
+ /// The activeSurvey is the property name of a top-level property
+ /// existing or created in the file ~/.devtools
+ /// If the property doesn't exist it is created with default survey values:
+ ///
+ /// properties[activeSurvey]['surveyActionTaken'] = false;
+ /// properties[activeSurvey]['surveyShownCount'] = 0;
+ ///
+ /// It is a requirement that the API apiSetActiveSurvey must be called before
+ /// calling any survey method on DevToolsUsage (addSurvey, rewriteActiveSurvey,
+ /// surveyShownCount, incrementSurveyShownCount, or surveyActionTaken).
+ String _activeSurvey;
+
+ IOPersistentProperties properties;
+
+ static const _surveyActionTaken = 'surveyActionTaken';
+ static const _surveyShownCount = 'surveyShownCount';
+
+ void reset() {
+ properties.remove('firstRun');
+ properties['enabled'] = false;
+ }
+
+ bool get isFirstRun {
+ properties['firstRun'] = properties['firstRun'] == null;
+ return properties['firstRun'];
+ }
+
+ bool get enabled {
+ if (properties['enabled'] == null) {
+ properties['enabled'] = false;
+ }
+
+ return properties['enabled'];
+ }
+
+ set enabled(bool value) {
+ properties['enabled'] = value;
+ return properties['enabled'];
+ }
+
+ bool surveyNameExists(String surveyName) => properties[surveyName] != null;
+
+ void _addSurvey(String surveyName) {
+ assert(activeSurvey != null);
+ assert(activeSurvey == surveyName);
+ rewriteActiveSurvey(false, 0);
+ }
+
+ String get activeSurvey => _activeSurvey;
+
+ set activeSurvey(String surveyName) {
+ assert(surveyName != null);
+ _activeSurvey = surveyName;
+
+ if (!surveyNameExists(activeSurvey)) {
+ // Create the survey if property is non-existent in ~/.devtools
+ _addSurvey(activeSurvey);
+ }
+ }
+
+ /// Need to rewrite the entire survey structure for property to be persisted.
+ void rewriteActiveSurvey(bool actionTaken, int shownCount) {
+ assert(activeSurvey != null);
+ properties[activeSurvey] = {
+ _surveyActionTaken: actionTaken,
+ _surveyShownCount: shownCount,
+ };
+ }
+
+ int get surveyShownCount {
+ assert(activeSurvey != null);
+ final prop = properties[activeSurvey];
+ if (prop[_surveyShownCount] == null) {
+ rewriteActiveSurvey(prop[_surveyActionTaken], 0);
+ }
+ return properties[activeSurvey][_surveyShownCount];
+ }
+
+ void incrementSurveyShownCount() {
+ assert(activeSurvey != null);
+ surveyShownCount; // Ensure surveyShownCount has been initialized.
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(prop[_surveyActionTaken], prop[_surveyShownCount] + 1);
+ }
+
+ bool get surveyActionTaken {
+ assert(activeSurvey != null);
+ return properties[activeSurvey][_surveyActionTaken] == true;
+ }
+
+ set surveyActionTaken(bool value) {
+ assert(activeSurvey != null);
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(value, prop[_surveyShownCount]);
+ }
+}
+
+abstract class PersistentProperties {
+ PersistentProperties(this.name);
+
+ final String name;
+
+ dynamic operator [](String key);
+
+ void operator []=(String key, dynamic value);
+
+ /// Re-read settings from the backing store.
+ ///
+ /// May be a no-op on some platforms.
+ void syncSettings();
+}
+
+const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' ');
+
+class IOPersistentProperties extends PersistentProperties {
+ IOPersistentProperties(
+ String name, {
+ String documentDirPath,
+ }) : super(name) {
+ final String fileName = name.replaceAll(' ', '_');
+ documentDirPath ??= LocalFileSystem.devToolsDir();
+ _file = File(path.join(documentDirPath, fileName));
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) {
+ _file = file;
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ File _file;
+
+ Map _map;
+
+ @override
+ dynamic operator [](String key) => _map[key];
+
+ @override
+ void operator []=(String key, dynamic value) {
+ if (value == null && !_map.containsKey(key)) return;
+ if (_map[key] == value) return;
+
+ if (value == null) {
+ _map.remove(key);
+ } else {
+ _map[key] = value;
+ }
+
+ try {
+ _file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n');
+ } catch (_) {}
+ }
+
+ @override
+ void syncSettings() {
+ try {
+ String contents = _file.readAsStringSync();
+ if (contents.isEmpty) contents = '{}';
+ _map = jsonDecode(contents);
+ } catch (_) {
+ _map = {};
+ }
+ }
+
+ void remove(String propertyName) {
+ _map.remove(propertyName);
+ }
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 221e3df..a69236c 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 1.7.6
+version: 1.8.0-dev
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -12,18 +12,21 @@
dependencies:
async: ^2.4.1
+ devtools_shared: ^2.0.0
json_rpc_2: ^2.2.0
meta: ^1.1.8
+ path: ^1.8.0
pedantic: ^1.7.0
shelf: ^1.0.0
shelf_proxy: ^1.0.0
+ shelf_static: ^1.0.0-dev
shelf_web_socket: ^1.0.0
sse: ^3.7.0
stream_channel: ^2.0.0
+ usage: ^4.0.0
vm_service: ^6.0.1-nullsafety.0
web_socket_channel: ^2.0.0
dev_dependencies:
- shelf_static: ^1.0.0
test: ^1.0.0
webdriver: ^3.0.0
diff --git a/pkg/dds/test/devtools_observatory_connection_test.dart b/pkg/dds/test/devtools_observatory_connection_test.dart
new file mode 100644
index 0000000..3dcc6cc
--- /dev/null
+++ b/pkg/dds/test/devtools_observatory_connection_test.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, 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.10
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:dds/dds.dart';
+
+import 'package:test/test.dart';
+import 'common/test_helper.dart';
+
+// Regression test for https://github.com/dart-lang/sdk/issues/45933.
+
+void main() {
+ Process process;
+ DartDevelopmentService dds;
+
+ setUp(() async {
+ // We don't care what's actually running in the target process for this
+ // test, so we're just using an existing one that invokes `debugger()` so
+ // we know it won't exit before we can connect.
+ process = await spawnDartProcess(
+ 'get_stream_history_script.dart',
+ pauseOnStart: false,
+ );
+ });
+
+ tearDown(() async {
+ await dds?.shutdown();
+ process?.kill();
+ dds = null;
+ process = null;
+ });
+
+ defineTest({bool authCodesEnabled}) {
+ test(
+ 'Ensure Observatory and DevTools assets are available with '
+ '${authCodesEnabled ? '' : 'no'} auth codes', () async {
+ dds = await DartDevelopmentService.startDartDevelopmentService(
+ remoteVmServiceUri,
+ devToolsConfiguration: DevToolsConfiguration(
+ enable: true,
+ customBuildDirectoryPath: Platform.script.resolve(
+ '../../../third_party/devtools/web',
+ ),
+ ),
+ );
+ expect(dds.isRunning, true);
+
+ final client = HttpClient();
+
+ // Check that Observatory assets are accessible.
+ final observatoryRequest = await client.getUrl(dds.uri);
+ final observatoryResponse = await observatoryRequest.close();
+ expect(observatoryResponse.statusCode, 200);
+ final observatoryContent =
+ await observatoryResponse.transform(utf8.decoder).join();
+ expect(observatoryContent, startsWith('<!DOCTYPE html>'));
+
+ // Check that DevTools assets are accessible.
+ final devtoolsRequest = await client.getUrl(dds.devToolsUri);
+ final devtoolsResponse = await devtoolsRequest.close();
+ expect(devtoolsResponse.statusCode, 200);
+ final devtoolsContent =
+ await devtoolsResponse.transform(utf8.decoder).join();
+ expect(devtoolsContent, startsWith('<!DOCTYPE html>'));
+ });
+ }
+
+ defineTest(authCodesEnabled: true);
+ defineTest(authCodesEnabled: false);
+}
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 5f0da4d..47f3893 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-
-## 6.3.0-dev
+## 7.0.0
+- *breaking bug fix*: Fixed issue where response parsing could fail for `Context`.
- Add support for `setBreakpointState` RPC and updated `Breakpoint` class to include
`enabled` property.
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 50fe029..a42219a 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -3318,7 +3318,7 @@
/// The enclosing context for this context.
@optional
- Context? parent;
+ ContextRef? parent;
/// The variables in this context object.
List<ContextElement>? variables;
@@ -3334,7 +3334,8 @@
Context._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
length = json['length'] ?? -1;
- parent = createServiceObject(json['parent'], const ['Context']) as Context?;
+ parent = createServiceObject(json['parent'], const ['ContextRef'])
+ as ContextRef?;
variables = List<ContextElement>.from(
createServiceObject(json['variables'], const ['ContextElement'])
as List? ??
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index dfe2e30..a7db2e8 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
A library to communicate with a service implementing the Dart VM
service protocol.
-version: 6.3.0-dev
+version: 7.0.0
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index ee35109..95c7166 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -553,13 +553,12 @@
vm_service_server_port = 0;
}
- // We do not want to wait for DDS to advertise availability of VM service in the
- // following scenarios:
- // - When the VM service is disabled (can be started at a later time via SIGQUIT).
- // - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is enabled.
- bool wait_for_dds_to_advertise_service =
- !Options::disable_dart_dev() && Options::enable_vm_service();
-
+ // We do not want to wait for DDS to advertise availability of VM service in
+ // the following scenarios:
+ // - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is
+ // enabled.
+ // TODO(bkonyi): do we want to tie DevTools / DDS to the CLI in the long run?
+ bool wait_for_dds_to_advertise_service = !Options::disable_dart_dev();
// Load embedder specific bits and return.
if (!VmService::Setup(
Options::disable_dart_dev() ? Options::vm_service_server_ip()
diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc
index b002ab8..55fef46 100644
--- a/runtime/bin/main_options.cc
+++ b/runtime/bin/main_options.cc
@@ -585,7 +585,7 @@
run_command = true;
}
if (!Options::disable_dart_dev() && enable_vm_service_ && run_command) {
- const char* dds_format_str = "--launch-dds=%s:%d";
+ const char* dds_format_str = "--launch-dds=%s\\:%d";
size_t size =
snprintf(nullptr, 0, dds_format_str, vm_service_server_ip(),
vm_service_server_port());
@@ -605,6 +605,7 @@
first_option = false;
}
}
+
// Verify consistency of arguments.
// snapshot_depfile is an alias for depfile. Passing them both is an error.
diff --git a/runtime/tests/vm/dart/isolates/concurrency_stress_sanity_test.dart b/runtime/tests/vm/dart/isolates/concurrency_stress_sanity_test.dart
index 1e2860b..6334701 100644
--- a/runtime/tests/vm/dart/isolates/concurrency_stress_sanity_test.dart
+++ b/runtime/tests/vm/dart/isolates/concurrency_stress_sanity_test.dart
@@ -20,7 +20,7 @@
// fuzzing test).
main() async {
if (!Platform.isLinux) return;
- if (!Platform.executable.contains("ReleaseX64/dart")) return;
+ if (!Platform.executable.endsWith("ReleaseX64/dart")) return;
final dartExecutable = Platform.executable;
await withTempDir((String tempDir) async {
diff --git a/runtime/tests/vm/dart_2/isolates/concurrency_stress_sanity_test.dart b/runtime/tests/vm/dart_2/isolates/concurrency_stress_sanity_test.dart
index a68084f..3af1a18 100644
--- a/runtime/tests/vm/dart_2/isolates/concurrency_stress_sanity_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/concurrency_stress_sanity_test.dart
@@ -20,7 +20,7 @@
// fuzzing test).
main() async {
if (!Platform.isLinux) return;
- if (!Platform.executable.contains("ReleaseX64/dart")) return;
+ if (!Platform.executable.endsWith("ReleaseX64/dart")) return;
final dartExecutable = Platform.executable;
await withTempDir((String tempDir) async {
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index bf1dc6a..86f3c78 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -2048,6 +2048,20 @@
SmiTag(result);
}
+void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) {
+#if defined(DEBUG)
+ Comment("Check that object in register has cid %" Pd "", cid);
+ Label matches;
+ LoadClassIdMayBeSmi(scratch, src);
+ CompareImmediate(scratch, cid);
+ BranchIf(EQUAL, &matches, Assembler::kNearJump);
+ Breakpoint();
+ Bind(&matches);
+#endif
+}
+
void Assembler::BailoutIfInvalidBranchOffset(int32_t offset) {
if (!CanEncodeBranchDistance(offset)) {
ASSERT(!use_far_branches());
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index e03aa97..6822e73 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -918,6 +918,9 @@
void CompareClassId(Register object, intptr_t class_id, Register scratch);
void LoadClassIdMayBeSmi(Register result, Register object);
void LoadTaggedClassIdMayBeSmi(Register result, Register object);
+ void EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) override;
intptr_t FindImmediate(int32_t imm);
bool CanLoadFromObjectPool(const Object& object) const;
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index a1f7c7f..7ef550b 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -1325,6 +1325,20 @@
}
}
+void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) {
+#if defined(DEBUG)
+ Comment("Check that object in register has cid %" Pd "", cid);
+ Label matches;
+ LoadClassIdMayBeSmi(scratch, src);
+ CompareImmediate(scratch, cid);
+ BranchIf(EQUAL, &matches, Assembler::kNearJump);
+ Breakpoint();
+ Bind(&matches);
+#endif
+}
+
// Frame entry and exit.
void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) {
// Reserve space for arguments and align frame before entering
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 4dcf55a..ef935b4 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1887,6 +1887,9 @@
// Note: input and output registers must be different.
void LoadClassIdMayBeSmi(Register result, Register object);
void LoadTaggedClassIdMayBeSmi(Register result, Register object);
+ void EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) override;
// Reserve specifies how much space to reserve for the Dart stack.
void SetupDartSP(intptr_t reserve = 4096);
diff --git a/runtime/vm/compiler/assembler/assembler_base.h b/runtime/vm/compiler/assembler/assembler_base.h
index 383a067..5b0c4b0 100644
--- a/runtime/vm/compiler/assembler/assembler_base.h
+++ b/runtime/vm/compiler/assembler/assembler_base.h
@@ -655,6 +655,10 @@
}
#endif // defined(DART_COMPRESSED_POINTERS)
+ virtual void EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) = 0;
+
intptr_t InsertAlignedRelocation(BSS::Relocation reloc);
void Unimplemented(const char* message);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc
index 4850162..2d0c3fb 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.cc
+++ b/runtime/vm/compiler/assembler/assembler_ia32.cc
@@ -2825,6 +2825,20 @@
}
}
+void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) {
+#if defined(DEBUG)
+ Comment("Check that object in register has cid %" Pd "", cid);
+ Label matches;
+ LoadClassIdMayBeSmi(scratch, src);
+ CompareImmediate(scratch, cid);
+ BranchIf(EQUAL, &matches, Assembler::kNearJump);
+ Breakpoint();
+ Bind(&matches);
+#endif
+}
+
Address Assembler::ElementAddressForIntIndex(bool is_external,
intptr_t cid,
intptr_t index_scale,
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index 5447918..2c75037 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -829,6 +829,9 @@
void LoadClassIdMayBeSmi(Register result, Register object);
void LoadTaggedClassIdMayBeSmi(Register result, Register object);
+ void EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) override;
void SmiUntagOrCheckClass(Register object,
intptr_t class_id,
diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc
index 7ab5c8e..72d30fd 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.cc
+++ b/runtime/vm/compiler/assembler/assembler_x64.cc
@@ -2405,6 +2405,20 @@
}
}
+void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) {
+#if defined(DEBUG)
+ Comment("Check that object in register has cid %" Pd "", cid);
+ Label matches;
+ LoadClassIdMayBeSmi(scratch, src);
+ CompareImmediate(scratch, cid);
+ BranchIf(EQUAL, &matches, Assembler::kNearJump);
+ Breakpoint();
+ Bind(&matches);
+#endif
+}
+
Address Assembler::VMTagAddress() {
return Address(THR, target::Thread::vm_tag_offset());
}
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 5acae47..4bc8495 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -883,6 +883,10 @@
void LoadClassIdMayBeSmi(Register result, Register object);
void LoadTaggedClassIdMayBeSmi(Register result, Register object);
+ void EnsureHasClassIdInDEBUG(intptr_t cid,
+ Register src,
+ Register scratch) override;
+
// CheckClassIs fused with optimistic SmiUntag.
// Value in the register object is untagged optimistically.
void SmiUntagOrCheckClass(Register object, intptr_t class_id, Label* smi);
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 226e449..4e02ded 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -993,10 +993,12 @@
LocationSummary* AllocateClosureInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
- const intptr_t kNumInputs = 0;
+ const intptr_t kNumInputs = inputs_.length();
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
+ locs->set_in(kFunctionPos,
+ Location::RegisterLocation(AllocateClosureABI::kFunctionReg));
locs->set_out(0, Location::RegisterLocation(AllocateClosureABI::kResultReg));
return locs;
}
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 917d7df..ce21bc8 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -6207,21 +6207,31 @@
DISALLOW_COPY_AND_ASSIGN(AllocateObjectInstr);
};
-// Allocates and null initializes a closure object. The closure function, when
-// non-null, is used to determine the precise type of the resulting closure
-// and to inline the closure function when applicable.
-class AllocateClosureInstr : public TemplateAllocation<0> {
+// Allocates and null initializes a closure object, given the closure function
+// as a value.
+class AllocateClosureInstr : public TemplateAllocation<1> {
public:
+ enum Inputs { kFunctionPos = 0 };
AllocateClosureInstr(const InstructionSource& source,
- const Function& closure_function,
+ Value* closure_function,
intptr_t deopt_id)
- : TemplateAllocation(source, deopt_id),
- closure_function_(closure_function) {}
+ : TemplateAllocation(source, deopt_id) {
+ SetInputAt(kFunctionPos, closure_function);
+ }
DECLARE_INSTRUCTION(AllocateClosure)
virtual CompileType ComputeType() const;
- const Function& closure_function() const { return closure_function_; }
+ Value* closure_function() const { return inputs_[kFunctionPos]; }
+
+ const Function& known_function() const {
+ Value* const value = closure_function();
+ if (value->BindsToConstant()) {
+ ASSERT(value->BoundConstant().IsFunction());
+ return Function::Cast(value->BoundConstant());
+ }
+ return Object::null_function();
+ }
virtual bool HasUnknownSideEffects() const { return false; }
@@ -6230,11 +6240,7 @@
compiler::target::Closure::InstanceSize());
}
- PRINT_OPERANDS_TO_SUPPORT
-
private:
- const Function& closure_function_;
-
DISALLOW_COPY_AND_ASSIGN(AllocateClosureInstr);
};
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index c816dac..2dcf90c 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -672,14 +672,6 @@
AllocationInstr::PrintOperandsTo(f);
}
-void AllocateClosureInstr::PrintOperandsTo(BaseTextBuffer* f) const {
- f->Printf("function=%s", closure_function().ToCString());
- if (InputCount() > 0 || Identity().IsNotAliased()) {
- f->AddString(", ");
- }
- TemplateAllocation::PrintOperandsTo(f);
-}
-
void MaterializeObjectInstr::PrintOperandsTo(BaseTextBuffer* f) const {
f->Printf("%s", String::Handle(cls_.ScrubbedName()).ToCString());
for (intptr_t i = 0; i < InputCount(); i++) {
diff --git a/runtime/vm/compiler/backend/il_test.cc b/runtime/vm/compiler/backend/il_test.cc
index 3a0286c..3f70e07 100644
--- a/runtime/vm/compiler/backend/il_test.cc
+++ b/runtime/vm/compiler/backend/il_test.cc
@@ -178,13 +178,13 @@
std::vector<const char*> expected_stores_jit;
std::vector<const char*> expected_stores_aot;
- expected_stores_jit.insert(expected_stores_jit.end(),
- {"value", "Context.parent", "Context.parent",
- "value", "Closure.function_type_arguments",
- "Closure.function", "Closure.context"});
- expected_stores_aot.insert(expected_stores_aot.end(),
- {"value", "Closure.function_type_arguments",
- "Closure.function", "Closure.context"});
+ expected_stores_jit.insert(
+ expected_stores_jit.end(),
+ {"value", "Context.parent", "Context.parent", "value",
+ "Closure.function_type_arguments", "Closure.context"});
+ expected_stores_aot.insert(
+ expected_stores_aot.end(),
+ {"value", "Closure.function_type_arguments", "Closure.context"});
RunInitializingStoresTest(root_library, "f4", CompilerPass::kJIT,
expected_stores_jit);
diff --git a/runtime/vm/compiler/backend/inliner.cc b/runtime/vm/compiler/backend/inliner.cc
index c727f66..62172d2 100644
--- a/runtime/vm/compiler/backend/inliner.cc
+++ b/runtime/vm/compiler/backend/inliner.cc
@@ -1513,7 +1513,7 @@
Definition* receiver =
call->Receiver()->definition()->OriginalDefinition();
if (const auto* alloc = receiver->AsAllocateClosure()) {
- target = alloc->closure_function().ptr();
+ target = alloc->known_function().ptr();
} else if (ConstantInstr* constant = receiver->AsConstant()) {
if (constant->value().IsClosure()) {
target = Closure::Cast(constant->value()).function();
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index bf2afc0..7b27daf 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -2087,6 +2087,10 @@
// explicitly initializes each non-null closure field in the flow
// graph with StoreInstanceField instructions post-allocation.
Definition* forward_def = graph_->constant_null();
+ // Forward values passed as AllocateClosureInstr inputs.
+ if (slot->IsIdentical(Slot::Closure_function())) {
+ forward_def = alloc->closure_function()->definition();
+ }
gen->Add(place_id);
if (out_values == nullptr) out_values = CreateBlockOutValues();
(*out_values)[place_id] = forward_def;
@@ -3727,8 +3731,10 @@
}
}
if (auto alloc_closure = alloc->AsAllocateClosure()) {
- // Any closure slots that are non-null are explicitly initialized
- // post-allocation using StoreInstanceField instructions.
+ // Add slots for any instruction inputs. Any closure slots not listed below
+ // that are non-null are explicitly initialized post-allocation using
+ // StoreInstanceField instructions.
+ AddSlot(slots, Slot::Closure_function());
}
if (alloc->IsCreateArray()) {
AddSlot(
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 72a427f..554ea11 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -1491,7 +1491,7 @@
}
CompileType AllocateClosureInstr::ComputeType() const {
- const auto& func = closure_function();
+ const auto& func = known_function();
if (!func.IsNull()) {
const auto& sig = FunctionType::ZoneHandle(func.signature());
return CompileType(CompileType::kNonNullable, kClosureCid, &sig);
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
index 8274032..acfc656 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
@@ -889,10 +889,10 @@
return Fragment(allocate);
}
-Fragment BaseFlowGraphBuilder::AllocateClosure(const Function& closure_function,
- TokenPosition position) {
- auto* allocate = new (Z) AllocateClosureInstr(
- InstructionSource(position), closure_function, GetNextDeoptId());
+Fragment BaseFlowGraphBuilder::AllocateClosure(TokenPosition position) {
+ auto const function = Pop();
+ auto* allocate = new (Z) AllocateClosureInstr(InstructionSource(position),
+ function, GetNextDeoptId());
Push(allocate);
return Fragment(allocate);
}
@@ -1006,7 +1006,8 @@
code += LoadLocal(pointer);
code += StoreNativeField(*context_slots[0]);
- code += AllocateClosure(target);
+ code += Constant(target);
+ code += AllocateClosure();
LocalVariable* closure = MakeTemporary();
code += LoadLocal(closure);
@@ -1014,11 +1015,6 @@
code += StoreNativeField(Slot::Closure_context(),
StoreInstanceFieldInstr::Kind::kInitializing);
- code += LoadLocal(closure);
- code += Constant(target);
- code += StoreNativeField(Slot::Closure_function(),
- StoreInstanceFieldInstr::Kind::kInitializing);
-
// Drop address and context.
code += DropTempsPreserveTop(2);
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
index 483750a..e3948fc 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
@@ -341,10 +341,8 @@
Fragment AssertBool(TokenPosition position);
Fragment BooleanNegate();
Fragment AllocateContext(const ZoneGrowableArray<const Slot*>& scope);
- // closure_function can be null if not statically known (i.e., copying from
- // another closure object).
- Fragment AllocateClosure(const Function& closure_function,
- TokenPosition position = TokenPosition::kNoSource);
+ // Top of the stack should be the closure function.
+ Fragment AllocateClosure(TokenPosition position = TokenPosition::kNoSource);
Fragment CreateArray();
Fragment AllocateTypedData(TokenPosition position, classid_t class_id);
Fragment InstantiateType(const AbstractType& type);
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 36e5460..1027098 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -4374,8 +4374,11 @@
Fragment instructions = BuildExpression();
LocalVariable* original_closure = MakeTemporary();
- // The closure function isn't known at compile time.
- instructions += flow_graph_builder_->AllocateClosure(Object::null_function());
+ // Load the target function and allocate the closure.
+ instructions += LoadLocal(original_closure);
+ instructions +=
+ flow_graph_builder_->LoadNativeField(Slot::Closure_function());
+ instructions += flow_graph_builder_->AllocateClosure();
LocalVariable* new_closure = MakeTemporary();
intptr_t num_type_args = ReadListLength();
@@ -4406,14 +4409,6 @@
StoreInstanceFieldInstr::Kind::kInitializing);
instructions += DropTemporary(&type_args_vec);
- // Copy over the target function.
- instructions += LoadLocal(new_closure);
- instructions += LoadLocal(original_closure);
- instructions +=
- flow_graph_builder_->LoadNativeField(Slot::Closure_function());
- instructions += flow_graph_builder_->StoreNativeField(
- Slot::Closure_function(), StoreInstanceFieldInstr::Kind::kInitializing);
-
// Copy over the instantiator type arguments.
instructions += LoadLocal(new_closure);
instructions += LoadLocal(original_closure);
@@ -5568,7 +5563,9 @@
function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kEnd);
- Fragment instructions = flow_graph_builder_->AllocateClosure(function);
+ Fragment instructions;
+ instructions += Constant(function);
+ instructions += flow_graph_builder_->AllocateClosure();
LocalVariable* closure = MakeTemporary();
// The function signature can have uninstantiated class type parameters.
@@ -5598,12 +5595,7 @@
StoreInstanceFieldInstr::Kind::kInitializing);
}
- // Store the function and the context in the closure.
- instructions += LoadLocal(closure);
- instructions += Constant(function);
- instructions += flow_graph_builder_->StoreNativeField(
- Slot::Closure_function(), StoreInstanceFieldInstr::Kind::kInitializing);
-
+ // Store the context in the closure.
instructions += LoadLocal(closure);
instructions += LoadLocal(parsed_function()->current_context_var());
instructions += flow_graph_builder_->StoreNativeField(
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 89105dd..df727ff 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -209,7 +209,6 @@
Fragment AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count);
- Fragment AllocateObject(const Class& klass, const Function& closure_function);
Fragment AllocateContext(const ZoneGrowableArray<const Slot*>& context_slots);
Fragment LoadNativeField(const Slot& field);
Fragment StoreLocal(TokenPosition position, LocalVariable* variable);
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 064f260..88c10b3 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -1584,7 +1584,8 @@
Fragment FlowGraphBuilder::BuildImplicitClosureCreation(
const Function& target) {
Fragment fragment;
- fragment += AllocateClosure(target);
+ fragment += Constant(target);
+ fragment += AllocateClosure();
LocalVariable* closure = MakeTemporary();
// The function signature can have uninstantiated class type parameters.
@@ -1605,12 +1606,7 @@
fragment += AllocateContext(implicit_closure_scope->context_slots());
LocalVariable* context = MakeTemporary();
- // Store the function and the context in the closure.
- fragment += LoadLocal(closure);
- fragment += Constant(target);
- fragment += StoreNativeField(Slot::Closure_function(),
- StoreInstanceFieldInstr::Kind::kInitializing);
-
+ // Store the context in the closure.
fragment += LoadLocal(closure);
fragment += LoadLocal(context);
fragment += StoreNativeField(Slot::Closure_context(),
@@ -1659,7 +1655,7 @@
return true;
}
if (auto const alloc = definition->AsAllocateClosure()) {
- return !alloc->closure_function().IsNull();
+ return !alloc->known_function().IsNull();
}
return definition->IsLoadLocal();
}
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 5af39cf..4dbf99c 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -756,6 +756,8 @@
#endif // !defined(TARGET_ARCH_IA32)
// Called for inline allocation of closure.
+// Input (preserved):
+// AllocateClosureABI::kFunctionReg: closure function.
// Output:
// AllocateClosureABI::kResultReg: new allocated Closure object.
// Clobbered:
@@ -763,6 +765,8 @@
void StubCodeCompiler::GenerateAllocateClosureStub(Assembler* assembler) {
const intptr_t instance_size =
target::RoundedAllocationSize(target::Closure::InstanceSize());
+ __ EnsureHasClassIdInDEBUG(kFunctionCid, AllocateClosureABI::kFunctionReg,
+ AllocateClosureABI::kScratchReg);
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
Label slow_case;
__ Comment("Inline allocation of uninitialized closure");
@@ -777,8 +781,10 @@
AllocateClosureABI::kScratchReg);
__ Comment("Inline initialization of allocated closure");
- // Put null in the scratch register for initializing boxed fields.
+ // Put null in the scratch register for initializing most boxed fields.
// We initialize the fields in offset order below.
+ // Since the TryAllocateObject above did not go to the slow path, we're
+ // guaranteed an object in new space here, and thus no barriers are needed.
__ LoadObject(AllocateClosureABI::kScratchReg, NullObject());
__ StoreToSlotNoBarrier(AllocateClosureABI::kScratchReg,
AllocateClosureABI::kResultReg,
@@ -789,7 +795,7 @@
__ StoreToSlotNoBarrier(AllocateClosureABI::kScratchReg,
AllocateClosureABI::kResultReg,
Slot::Closure_delayed_type_arguments());
- __ StoreToSlotNoBarrier(AllocateClosureABI::kScratchReg,
+ __ StoreToSlotNoBarrier(AllocateClosureABI::kFunctionReg,
AllocateClosureABI::kResultReg,
Slot::Closure_function());
__ StoreToSlotNoBarrier(AllocateClosureABI::kScratchReg,
@@ -808,7 +814,9 @@
__ Comment("Closure allocation via runtime");
__ EnterStubFrame();
__ PushObject(NullObject()); // Space on the stack for the return value.
- __ CallRuntime(kAllocateClosureRuntimeEntry, 0);
+ __ PushRegister(AllocateClosureABI::kFunctionReg);
+ __ CallRuntime(kAllocateClosureRuntimeEntry, 1);
+ __ PopRegister(AllocateClosureABI::kFunctionReg);
__ PopRegister(AllocateClosureABI::kResultReg);
ASSERT(target::WillAllocateNewOrRememberedObject(instance_size));
EnsureIsNewOrRemembered(assembler, /*preserve_registers=*/false);
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index e757ee3..ea32cf7 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -236,7 +236,8 @@
__ ldr(R3, Address(R0, R4), NE);
// Push type arguments & extracted method.
- __ PushList(1 << R3 | 1 << R1);
+ __ Push(R3);
+ __ Push(R1);
// Allocate context.
{
@@ -267,32 +268,41 @@
__ StoreIntoObject(R0, FieldAddress(R0, target::Context::variable_offset(0)),
R1);
+ // Pop function before pushing context.
+ __ Pop(AllocateClosureABI::kFunctionReg);
+
// Push context.
__ Push(R0);
- // Allocate closure.
+ // Allocate closure. After this point, we only use the registers in
+ // AllocateClosureABI.
__ LoadObject(CODE_REG, closure_allocation_stub);
- __ ldr(R1, FieldAddress(CODE_REG, target::Code::entry_point_offset(
- CodeEntryKind::kUnchecked)));
- __ blx(R1);
+ __ ldr(AllocateClosureABI::kScratchReg,
+ FieldAddress(CODE_REG, target::Code::entry_point_offset()));
+ __ blx(AllocateClosureABI::kScratchReg);
// Populate closure object.
- __ Pop(R1); // Pop context.
- __ StoreIntoObject(R0, FieldAddress(R0, target::Closure::context_offset()),
- R1);
- __ PopList(1 << R3 | 1 << R1); // Pop type arguments & extracted method.
+ __ Pop(AllocateClosureABI::kScratchReg); // Pop context.
+ __ StoreIntoObject(AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::context_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ Pop(AllocateClosureABI::kScratchReg); // Pop type arguments.
__ StoreIntoObjectNoBarrier(
- R0, FieldAddress(R0, target::Closure::function_offset()), R1);
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::instantiator_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ LoadObject(AllocateClosureABI::kScratchReg, EmptyTypeArguments());
__ StoreIntoObjectNoBarrier(
- R0,
- FieldAddress(R0, target::Closure::instantiator_type_arguments_offset()),
- R3);
- __ LoadObject(R1, EmptyTypeArguments());
- __ StoreIntoObjectNoBarrier(
- R0, FieldAddress(R0, target::Closure::delayed_type_arguments_offset()),
- R1);
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::delayed_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
__ LeaveStubFrame();
+ // No-op if the two are the same.
+ __ MoveRegister(R0, AllocateClosureABI::kResultReg);
__ Ret();
}
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 78627e8..ef37b4e 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -473,7 +473,8 @@
__ Bind(&no_type_args);
// Push type arguments & extracted method.
- __ PushPair(R3, R1);
+ __ Push(R3);
+ __ Push(R1);
// Allocate context.
{
@@ -504,32 +505,41 @@
__ StoreIntoObject(R0, FieldAddress(R0, target::Context::variable_offset(0)),
R1);
+ // Pop function before pushing context.
+ __ Pop(AllocateClosureABI::kFunctionReg);
+
// Push context.
__ Push(R0);
- // Allocate closure.
+ // Allocate closure. After this point, we only use the registers in
+ // AllocateClosureABI.
__ LoadObject(CODE_REG, closure_allocation_stub);
- __ ldr(R1, FieldAddress(CODE_REG, target::Code::entry_point_offset(
- CodeEntryKind::kUnchecked)));
- __ blr(R1);
+ __ ldr(AllocateClosureABI::kScratchReg,
+ FieldAddress(CODE_REG, target::Code::entry_point_offset()));
+ __ blr(AllocateClosureABI::kScratchReg);
// Populate closure object.
- __ Pop(R1); // Pop context.
- __ StoreIntoObject(R0, FieldAddress(R0, target::Closure::context_offset()),
- R1);
- __ PopPair(R3, R1); // Pop type arguments & extracted method.
+ __ Pop(AllocateClosureABI::kScratchReg); // Pop context.
+ __ StoreIntoObject(AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::context_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ Pop(AllocateClosureABI::kScratchReg); // Pop type arguments.
__ StoreIntoObjectNoBarrier(
- R0, FieldAddress(R0, target::Closure::function_offset()), R1);
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::instantiator_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ LoadObject(AllocateClosureABI::kScratchReg, EmptyTypeArguments());
__ StoreIntoObjectNoBarrier(
- R0,
- FieldAddress(R0, target::Closure::instantiator_type_arguments_offset()),
- R3);
- __ LoadObject(R1, EmptyTypeArguments());
- __ StoreIntoObjectNoBarrier(
- R0, FieldAddress(R0, target::Closure::delayed_type_arguments_offset()),
- R1);
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::delayed_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
__ LeaveStubFrame();
+ // No-op if the two are the same.
+ __ MoveRegister(R0, AllocateClosureABI::kResultReg);
__ Ret();
}
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 2dc6cd6..9aa8e8c 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -448,32 +448,39 @@
__ StoreIntoObject(
RAX, FieldAddress(RAX, target::Context::variable_offset(0)), RSI);
+ // Pop function before pushing context.
+ __ popq(AllocateClosureABI::kFunctionReg);
+
// Push context.
__ pushq(RAX);
- // Allocate closure.
+ // Allocate closure. After this point, we only use the registers in
+ // AllocateClosureABI.
__ LoadObject(CODE_REG, closure_allocation_stub);
- __ call(FieldAddress(
- CODE_REG, target::Code::entry_point_offset(CodeEntryKind::kUnchecked)));
+ __ call(FieldAddress(CODE_REG, target::Code::entry_point_offset()));
// Populate closure object.
- __ popq(RCX); // Pop context.
- __ StoreIntoObject(RAX, FieldAddress(RAX, target::Closure::context_offset()),
- RCX);
- __ popq(RCX); // Pop extracted method.
+ __ popq(AllocateClosureABI::kScratchReg); // Pop context.
+ __ StoreIntoObject(AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::context_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ popq(AllocateClosureABI::kScratchReg); // Pop type argument vector.
__ StoreIntoObjectNoBarrier(
- RAX, FieldAddress(RAX, target::Closure::function_offset()), RCX);
- __ popq(RCX); // Pop type argument vector.
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::instantiator_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
+ __ LoadObject(AllocateClosureABI::kScratchReg, EmptyTypeArguments());
__ StoreIntoObjectNoBarrier(
- RAX,
- FieldAddress(RAX, target::Closure::instantiator_type_arguments_offset()),
- RCX);
- __ LoadObject(RCX, EmptyTypeArguments());
- __ StoreIntoObjectNoBarrier(
- RAX, FieldAddress(RAX, target::Closure::delayed_type_arguments_offset()),
- RCX);
+ AllocateClosureABI::kResultReg,
+ FieldAddress(AllocateClosureABI::kResultReg,
+ target::Closure::delayed_type_arguments_offset()),
+ AllocateClosureABI::kScratchReg);
__ LeaveStubFrame();
+ // No-op if the two are the same.
+ __ MoveRegister(RAX, AllocateClosureABI::kResultReg);
__ Ret();
}
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index b0a6159..f638186 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -454,6 +454,7 @@
// ABI for AllocateClosureStub.
struct AllocateClosureABI {
static const Register kResultReg = R0;
+ static const Register kFunctionReg = R1;
static const Register kScratchReg = R4;
};
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index ec77884..d82871e 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -295,6 +295,7 @@
// ABI for AllocateClosureStub.
struct AllocateClosureABI {
static const Register kResultReg = R0;
+ static const Register kFunctionReg = R1;
static const Register kScratchReg = R4;
};
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index 5272b47..3d36faf 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -205,6 +205,7 @@
// ABI for AllocateClosureStub.
struct AllocateClosureABI {
static const Register kResultReg = EAX;
+ static const Register kFunctionReg = EBX;
static const Register kScratchReg = EDX;
};
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index 67ef651..469b90b 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -266,6 +266,7 @@
// ABI for AllocateClosureStub.
struct AllocateClosureABI {
static const Register kResultReg = RAX;
+ static const Register kFunctionReg = RBX;
static const Register kScratchReg = R13;
};
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 1565559..2b33298 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -595,13 +595,15 @@
UNREACHABLE();
}
-// Allocate a new closure and initialize its fields to null.
+// Allocate a new closure and initializes its function field with the argument
+// and all other fields to null.
// Return value: newly allocated closure.
-DEFINE_RUNTIME_ENTRY(AllocateClosure, 0) {
+DEFINE_RUNTIME_ENTRY(AllocateClosure, 1) {
+ const auto& function = Function::CheckedHandle(zone, arguments.ArgAt(0));
const Closure& closure = Closure::Handle(
zone, Closure::New(
Object::null_type_arguments(), Object::null_type_arguments(),
- Object::null_type_arguments(), Object::null_function(),
+ Object::null_type_arguments(), function,
Context::Handle(Context::null()), SpaceForRuntimeAllocation()));
arguments.SetReturn(closure);
}
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 99bdab7..3000c42 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1832,7 +1832,7 @@
int length;
// The enclosing context for this context.
- Context parent [optional];
+ @Context parent [optional];
// The variables in this context object.
ContextElement[] variables;
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index 15a1abf..1390092 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -252,6 +252,18 @@
},
]
+# This rule copies the pre-built DevTools application to
+# bin/resources/devtools/
+copy_tree_specs += [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":create_common_sdk" ]
+ source = "../third_party/devtools/web"
+ dest = "$root_out_dir/dart-sdk/bin/resources/devtools"
+ ignore_patterns = "{}"
+ },
+]
+
# This loop generates rules to copy libraries to lib/
foreach(library, _full_sdk_libraries) {
copy_tree_specs += [
@@ -811,6 +823,7 @@
":copy_libraries_dart",
":copy_libraries_specification",
":copy_license",
+ ":copy_prebuilt_devtools",
":copy_readme",
":copy_vm_dill_files",
":write_dartdoc_options",
diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart
index d1f8a5d..833e5a2 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -43,6 +43,7 @@
// HTTP server.
Server? server;
Future<Server>? serverFuture;
+_DebuggingSession? ddsInstance;
Server _lazyServerBoot() {
var localServer = server;
@@ -58,6 +59,90 @@
return localServer;
}
+/// Responsible for launching a DevTools instance when the service is started
+/// via SIGQUIT.
+class _DebuggingSession {
+ Future<bool> start(
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ ) async {
+ final dartPath = Uri.parse(Platform.resolvedExecutable);
+ final dartDir = [
+ '', // Include leading '/'
+ ...dartPath.pathSegments.sublist(
+ 0,
+ dartPath.pathSegments.length - 1,
+ ),
+ ].join('/');
+
+ final fullSdk = dartDir.endsWith('bin');
+
+ final ddsSnapshot = [
+ dartDir,
+ fullSdk ? 'snapshots' : 'gen',
+ 'dds.dart.snapshot',
+ ].join('/');
+
+ final devToolsBinaries = [
+ dartDir,
+ if (fullSdk) 'resources',
+ 'devtools',
+ ].join('/');
+
+ const enableLogging = false;
+ _process = await Process.start(
+ dartPath.toString(),
+ [
+ ddsSnapshot,
+ server!.serverAddress!.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ enableLogging.toString(),
+ ],
+ mode: ProcessStartMode.detachedWithStdio,
+ );
+ final completer = Completer<void>();
+ late StreamSubscription stderrSub;
+ stderrSub = _process!.stderr.transform(utf8.decoder).listen((event) {
+ final result = json.decode(event) as Map<String, dynamic>;
+ final state = result['state'];
+ if (state == 'started') {
+ if (result.containsKey('devToolsUri')) {
+ // NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message
+ // is changed to ensure consistency.
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at:';
+ final devToolsUri = result['devToolsUri'];
+ print('$devToolsMessagePrefix $devToolsUri');
+ }
+ stderrSub.cancel();
+ completer.complete();
+ } else {
+ stderrSub.cancel();
+ completer.completeError(
+ 'Could not start Observatory HTTP server',
+ );
+ }
+ });
+ try {
+ await completer.future;
+ return true;
+ } catch (e) {
+ stderr.write(e);
+ return false;
+ }
+ }
+
+ void shutdown() => _process!.kill();
+
+ Process? _process;
+}
+
Future cleanupCallback() async {
// Cancel the sigquit subscription.
if (_signalSubscription != null) {
@@ -221,10 +306,6 @@
_server.acceptNewWebSocketConnections = enable;
}
-void _clearFuture(_) {
- serverFuture = null;
-}
-
_onSignal(ProcessSignal signal) {
if (serverFuture != null) {
// Still waiting.
@@ -233,9 +314,21 @@
final _server = _lazyServerBoot();
// Toggle HTTP server.
if (_server.running) {
- _server.shutdown(true).then(_clearFuture);
+ _server.shutdown(true).then((_) async {
+ ddsInstance?.shutdown();
+ await VMService().clearState();
+ serverFuture = null;
+ });
} else {
- _server.startup().then(_clearFuture);
+ _server.startup().then((_) {
+ ddsInstance = _DebuggingSession()
+ ..start(
+ _server._ip,
+ _server._port.toString(),
+ false,
+ true,
+ );
+ });
}
}
diff --git a/sdk/lib/_internal/vm/bin/vmservice_server.dart b/sdk/lib/_internal/vm/bin/vmservice_server.dart
index 69aa7f8..f5742ed 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_server.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_server.dart
@@ -26,9 +26,9 @@
socket.done.then((_) => close());
}
- disconnect() {
+ Future<void> disconnect() async {
if (socket != null) {
- socket.close();
+ await socket.close();
}
}
@@ -102,8 +102,8 @@
HttpRequestClient(this.request, VMService service)
: super(service, sendEvents: false);
- disconnect() {
- request.response.close();
+ Future<void> disconnect() async {
+ await request.response.close();
close();
}
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index a3f7e8b..d492dcf 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -411,6 +411,16 @@
replyPort.send(bytes);
}
+ Future<void> clearState() async {
+ // Create a copy of the set as a list because client.disconnect() will
+ // alter the connected clients set.
+ final clientsList = clients.toList();
+ for (final client in clientsList) {
+ await client.disconnect();
+ }
+ devfs.cleanup();
+ }
+
Future _exit() async {
isExiting = true;
@@ -423,14 +433,7 @@
// Close receive ports.
isolateControlPort.close();
scriptLoadPort.close();
-
- // Create a copy of the set as a list because client.disconnect() will
- // alter the connected clients set.
- final clientsList = clients.toList();
- for (final client in clientsList) {
- client.disconnect();
- }
- devfs.cleanup();
+ await clearState();
final cleanup = VMServiceEmbedderHooks.cleanup;
if (cleanup != null) {
await cleanup();
diff --git a/third_party/devtools/update.sh b/third_party/devtools/update.sh
index a69f6ee..077d758 100755
--- a/third_party/devtools/update.sh
+++ b/third_party/devtools/update.sh
@@ -30,12 +30,11 @@
# to serve from DDS.
mkdir cipd_package
cp -R packages/devtools/build/ cipd_package/web
-cp -r packages/devtools_server cipd_package
cp -r packages/devtools_shared cipd_package
cipd create \
-name dart/third_party/flutter/devtools \
-in cipd_package \
-install-mode copy \
- -tag revision:$1
+ -tag git_revision:$1
diff --git a/tools/VERSION b/tools/VERSION
index ec96746..4dbaebf 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 106
+PRERELEASE 107
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index bf63fe3..39d7272 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -322,6 +322,7 @@
"xcodebuild/ReleaseSIMARM64C/",
"xcodebuild/ReleaseX64/",
"xcodebuild/ReleaseX64C/",
+ "pkg/",
"samples/",
"samples_2/",
"samples-dev/",
@@ -329,6 +330,7 @@
"third_party/android_tools/sdk/platform-tools/adb",
"third_party/android_tools/ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip",
"third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip",
+ "third_party/devtools/",
"third_party/webdriver/",
"third_party/pkg/",
"third_party/pkg_tested/",
diff --git a/tools/generate_package_config.dart b/tools/generate_package_config.dart
index 8d4ed5b..f8791cd 100644
--- a/tools/generate_package_config.dart
+++ b/tools/generate_package_config.dart
@@ -57,6 +57,8 @@
packageDirectory(
'runtime/observatory_2/tests/service_2/observatory_test_package_2'),
packageDirectory('sdk/lib/_internal/sdk_library_metadata'),
+ packageDirectory('third_party/devtools/devtools_server'),
+ packageDirectory('third_party/devtools/devtools_shared'),
packageDirectory('third_party/pkg/protobuf/protobuf'),
packageDirectory('tools/package_deps'),
];
diff --git a/utils/dartdev/BUILD.gn b/utils/dartdev/BUILD.gn
index 7f33d99..3ce3bc3 100644
--- a/utils/dartdev/BUILD.gn
+++ b/utils/dartdev/BUILD.gn
@@ -2,12 +2,14 @@
# 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("../../build/dart/copy_tree.gni")
import("../application_snapshot.gni")
group("dartdev") {
public_deps = [
":copy_dartdev_kernel",
":copy_dartdev_snapshot",
+ ":copy_prebuilt_devtools",
]
}
@@ -39,3 +41,15 @@
deps = [ "../dds:dds" ]
output = "$root_gen_dir/dartdev.dart.snapshot"
}
+
+copy_trees("copy_prebuilt_devtools") {
+ sources = [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":dartdev" ]
+ source = "../../third_party/devtools/web"
+ dest = "$root_out_dir/devtools"
+ ignore_patterns = "{}"
+ },
+ ]
+}