Version 2.14.0-245.0.dev
Merge commit '8ff5176a77f4684b55b16f5bbf3bbd5ac717d513' into 'dev'
diff --git a/pkg/dartdev/test/commands/run_test.dart b/pkg/dartdev/test/commands/run_test.dart
index 4cd3517..45eb00c 100644
--- a/pkg/dartdev/test/commands/run_test.dart
+++ b/pkg/dartdev/test/commands/run_test.dart
@@ -237,6 +237,28 @@
);
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
+
+ // Again, with IPv6.
+ result = p.runSync([
+ 'run',
+ '--observe=8181/::1',
+ '--pause-isolates-on-start',
+ // This should negate the above flag.
+ '--no-pause-isolates-on-start',
+ '--no-pause-isolates-on-exit',
+ '--no-pause-isolates-on-unhandled-exceptions',
+ '-Dfoo=bar',
+ '--define=bar=foo',
+ p.relativeFilePath,
+ ]);
+
+ expect(
+ result.stdout,
+ matches(
+ r'Observatory listening on http:\/\/\[::1\]:8181\/[a-zA-Z0-9_-]+=\/\n.*'),
+ );
+ expect(result.stderr, isEmpty);
+ expect(result.exitCode, 0);
});
test('fails when provided verbose VM flags', () async {
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index b34c090..2ae2f5a 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -53,6 +53,7 @@
remoteVmServiceUri,
serviceUri: serviceUri,
enableAuthCodes: !disableServiceAuthCodes,
+ ipv6: address.type == InternetAddressType.IPv6,
devToolsConfiguration: startDevTools && devToolsBuildDirectory != null
? DevToolsConfiguration(
enable: startDevTools,
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index f101d7b..8730cb1 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -6,8 +6,10 @@
import 'dart:io';
import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm;
+import '../../../dds.dart';
import '../base_debug_adapter.dart';
import '../exceptions.dart';
import '../isolate_manager.dart';
@@ -97,13 +99,32 @@
/// yet been made.
vm.VmServiceInterface? vmService;
+ /// The DDS instance that was started and that [vmService] is connected to.
+ ///
+ /// `null` if the session is running in noDebug mode of the connection has not
+ /// yet been made.
+ DartDevelopmentService? _dds;
+
+ /// Whether to enable DDS for launched applications.
+ final bool enableDds;
+
+ /// Whether to enable authentication codes for the VM Service/DDS.
+ final bool enableAuthCodes;
+
+ /// A logger for printing diagnostic information.
+ final Logger? logger;
+
/// Whether the current debug session is an attach request (as opposed to a
/// launch request). Not available until after launchRequest or attachRequest
/// have been called.
late final bool isAttach;
- DartDebugAdapter(ByteStreamServerChannel channel, Logger? logger)
- : super(channel, logger) {
+ DartDebugAdapter(
+ ByteStreamServerChannel channel, {
+ this.enableDds = true,
+ this.enableAuthCodes = true,
+ this.logger,
+ }) : super(channel) {
_isolateManager = IsolateManager(this);
_converter = ProtocolConverter(this);
}
@@ -166,10 +187,21 @@
/// The caller should handle any other normalisation (such as adding /ws to
/// the end if required).
Future<void> connectDebugger(Uri uri) async {
- // The VM Service library always expects the WebSockets URI so fix the
- // scheme (http -> ws, https -> wss).
- final isSecure = uri.isScheme('https') || uri.isScheme('wss');
- uri = uri.replace(scheme: isSecure ? 'wss' : 'ws');
+ // Start up a DDS instance for this VM.
+ if (enableDds) {
+ // TODO(dantup): Do we need to worry about there already being one connected
+ // if this URL came from another service that may have started one?
+ logger?.call('Starting a DDS instance for $uri');
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
+ uri,
+ // TODO(dantup): Allow this to be disabled?
+ enableAuthCodes: true,
+ );
+ _dds = dds;
+ uri = dds.wsUri!;
+ } else {
+ uri = _cleanVmServiceUri(uri);
+ }
logger?.call('Connecting to debugger at $uri');
sendOutput('console', 'Connecting to VM Service at $uri\n');
@@ -311,6 +343,7 @@
void Function() sendResponse,
) async {
await disconnectImpl();
+ await shutdown();
sendResponse();
}
@@ -597,6 +630,18 @@
sendResponse(SetExceptionBreakpointsResponseBody());
}
+ /// Shuts down and cleans up.
+ ///
+ /// This is called by [disconnectRequest] and [terminateRequest] but may also
+ /// be called if the client just disconnects from the server without calling
+ /// either.
+ ///
+ /// This method must tolerate being called multiple times.
+ @mustCallSuper
+ Future<void> shutdown() async {
+ await _dds?.shutdown();
+ }
+
/// Handles a request from the client for the call stack for [args.threadId].
///
/// This is usually called after we sent a [StoppedEvent] to the client
@@ -734,6 +779,7 @@
void Function() sendResponse,
) async {
await terminateImpl();
+ await shutdown();
sendResponse();
}
@@ -838,6 +884,25 @@
sendResponse(VariablesResponseBody(variables: variables));
}
+ /// Fixes up an Observatory [uri] to a WebSocket URI with a trailing /ws
+ /// for connecting when not using DDS.
+ ///
+ /// DDS does its own cleaning up of the URI.
+ Uri _cleanVmServiceUri(Uri uri) {
+ // The VM Service library always expects the WebSockets URI so fix the
+ // scheme (http -> ws, https -> wss).
+ final isSecure = uri.isScheme('https') || uri.isScheme('wss');
+ uri = uri.replace(scheme: isSecure ? 'wss' : 'ws');
+
+ if (uri.path.endsWith('/ws') || uri.path.endsWith('/ws/')) {
+ return uri;
+ }
+
+ final append = uri.path.endsWith('/') ? 'ws' : '/ws';
+ final newPath = '${uri.path}$append';
+ return uri.replace(path: newPath);
+ }
+
/// Handles evaluation of an expression that is (or begins with)
/// `threadExceptionExpression` which corresponds to the exception at the top
/// of [thread].
@@ -870,12 +935,22 @@
);
}
- void _handleDebugEvent(vm.Event event) {
- _isolateManager.handleEvent(event);
+ Future<void> _handleDebugEvent(vm.Event event) async {
+ // Delay processing any events until the debugger initialization has
+ // finished running, as events may arrive (for ex. IsolateRunnable) while
+ // it's doing is own initialization that this may interfere with.
+ await debuggerInitialized;
+
+ await _isolateManager.handleEvent(event);
}
- void _handleIsolateEvent(vm.Event event) {
- _isolateManager.handleEvent(event);
+ Future<void> _handleIsolateEvent(vm.Event event) async {
+ // Delay processing any events until the debugger initialization has
+ // finished running, as events may arrive (for ex. IsolateRunnable) while
+ // it's doing is own initialization that this may interfere with.
+ await debuggerInitialized;
+
+ await _isolateManager.handleEvent(event);
}
/// Handles a dart:developer log() event, sending output to the client.
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli.dart b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
index 007943f..6a01c8c 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
@@ -41,8 +41,17 @@
@override
final parseLaunchArgs = DartLaunchRequestArguments.fromJson;
- DartCliDebugAdapter(ByteStreamServerChannel channel, [Logger? logger])
- : super(channel, logger);
+ DartCliDebugAdapter(
+ ByteStreamServerChannel channel, {
+ bool enableDds = true,
+ bool enableAuthCodes = true,
+ Logger? logger,
+ }) : super(
+ channel,
+ enableDds: enableDds,
+ enableAuthCodes: enableAuthCodes,
+ logger: logger,
+ );
Future<void> debuggerConnected(vm.VM vmInfo) async {
if (!isAttach) {
@@ -97,16 +106,14 @@
);
}
- // TODO(dantup): Currently this just spawns the new VM and completely
- // ignores DDS. Figure out how this will ultimately work - will we just wrap
- // the call to initDebugger() with something that starts DDS?
final vmServiceInfoFile = _vmServiceInfoFile;
final vmArgs = <String>[
- '--no-serve-devtools',
if (debug) ...[
'--enable-vm-service=${args.vmServicePort ?? 0}',
'--pause_isolates_on_start=true',
+ if (!enableAuthCodes) '--disable-service-auth-codes'
],
+ '--disable-dart-dev',
if (debug && vmServiceInfoFile != null) ...[
'-DSILENT_OBSERVATORY=true',
'--write-service-info=${Uri.file(vmServiceInfoFile.path)}'
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 4c09eb8..cd2b99a 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -7,7 +7,6 @@
import 'package:meta/meta.dart';
import 'exceptions.dart';
-import 'logging.dart';
import 'protocol_common.dart';
import 'protocol_generated.dart';
import 'protocol_stream.dart';
@@ -30,9 +29,8 @@
abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
int _sequence = 1;
final ByteStreamServerChannel _channel;
- final Logger? logger;
- BaseDebugAdapter(this._channel, this.logger) {
+ BaseDebugAdapter(this._channel) {
_channel.listen(_handleIncomingMessage);
}
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 672f4a1..99ee743 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -93,11 +93,6 @@
return;
}
- // Delay processing any events until the debugger initialization has
- // finished running, as events may arrive (for ex. IsolateRunnable) while
- // it's doing is own initialization that this may interfere with.
- await _adapter.debuggerInitialized;
-
final eventKind = event.kind;
if (eventKind == vm.EventKind.kIsolateStart ||
eventKind == vm.EventKind.kIsolateRunnable) {
@@ -315,7 +310,12 @@
await _configureIsolate(isolate);
await resumeThread(thread.threadId);
} else if (eventKind == vm.EventKind.kPauseStart) {
- await resumeThread(thread.threadId);
+ // Don't resume from a PauseStart if this has already happened (see
+ // comments on [thread.hasBeenStarted]).
+ if (!thread.hasBeenStarted) {
+ thread.hasBeenStarted = true;
+ await resumeThread(thread.threadId);
+ }
} else {
// PauseExit, PauseBreakpoint, PauseInterrupted, PauseException
var reason = 'pause';
@@ -467,6 +467,17 @@
int? exceptionReference;
var paused = false;
+ /// Tracks whether an isolate has been started from its PauseStart state.
+ ///
+ /// This is used to prevent trying to resume a thread twice if a PauseStart
+ /// event arrives around the same time that are our initialization code (which
+ /// automatically resumes threads that are in the PauseStart state when we
+ /// connect).
+ ///
+ /// If we send a duplicate resume, it could trigger an unwanted resume for a
+ /// breakpoint or exception that occur early on.
+ bool hasBeenStarted = false;
+
// The most recent pauseEvent for this isolate.
vm.Event? pauseEvent;
diff --git a/pkg/dds/lib/src/dap/server.dart b/pkg/dds/lib/src/dap/server.dart
index 14de592..20f205e 100644
--- a/pkg/dds/lib/src/dap/server.dart
+++ b/pkg/dds/lib/src/dap/server.dart
@@ -18,11 +18,18 @@
static const defaultPort = 9200;
final ServerSocket _socket;
- final Logger? _logger;
+ final bool enableDds;
+ final bool enableAuthCodes;
+ final Logger? logger;
final _channels = <ByteStreamServerChannel>{};
final _adapters = <DartDebugAdapter>{};
- DapServer._(this._socket, this._logger) {
+ DapServer._(
+ this._socket, {
+ this.enableDds = true,
+ this.enableAuthCodes = true,
+ this.logger,
+ }) {
_socket.listen(_acceptConnection);
}
@@ -36,25 +43,30 @@
void _acceptConnection(Socket client) {
final address = client.remoteAddress;
- _logger?.call('Accepted connection from $address');
+ logger?.call('Accepted connection from $address');
client.done.then((_) {
- _logger?.call('Connection from $address closed');
+ logger?.call('Connection from $address closed');
});
- _createAdapter(client.transform(Uint8ListTransformer()), client, _logger);
+ _createAdapter(client.transform(Uint8ListTransformer()), client);
}
- void _createAdapter(
- Stream<List<int>> _input, StreamSink<List<int>> _output, Logger? logger) {
+ void _createAdapter(Stream<List<int>> _input, StreamSink<List<int>> _output) {
// TODO(dantup): This is hard-coded to DartCliDebugAdapter but will
// ultimately need to support having a factory passed in to support
// tests and/or being used in flutter_tools.
final channel = ByteStreamServerChannel(_input, _output, logger);
- final adapter = DartCliDebugAdapter(channel, logger);
+ final adapter = DartCliDebugAdapter(
+ channel,
+ enableDds: enableDds,
+ enableAuthCodes: enableAuthCodes,
+ logger: logger,
+ );
_channels.add(channel);
_adapters.add(adapter);
unawaited(channel.closed.then((_) {
_channels.remove(channel);
_adapters.remove(adapter);
+ adapter.shutdown();
}));
}
@@ -62,9 +74,16 @@
static Future<DapServer> create({
String host = 'localhost',
int port = 0,
+ bool enableDdds = true,
+ bool enableAuthCodes = true,
Logger? logger,
}) async {
final _socket = await ServerSocket.bind(host, port);
- return DapServer._(_socket, logger);
+ return DapServer._(
+ _socket,
+ enableDds: enableDdds,
+ enableAuthCodes: enableAuthCodes,
+ logger: logger,
+ );
}
}
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index b71a591..ded4663 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -2,9 +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 'package:collection/collection.dart';
+import 'package:dds/src/dap/protocol_generated.dart';
import 'package:test/test.dart';
-import 'package:vm_service/vm_service.dart';
import 'test_client.dart';
import 'test_support.dart';
@@ -21,7 +20,7 @@
}
''');
- var outputEvents = await dap.client.collectOutput(
+ final outputEvents = await dap.client.collectOutput(
launch: () => dap.client.launch(
testFile.path,
args: ['one', 'two'],
@@ -62,7 +61,7 @@
expect(response.threads.first.name, equals('main'));
});
- test('connects with DDS', () async {
+ test('runs with DDS', () async {
final client = dap.client;
final testFile = dap.createTestFile(r'''
void main(List<String> args) async {
@@ -72,20 +71,60 @@
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
await client.hitBreakpoint(testFile, breakpointLine);
- final response = await client.custom(
- '_getSupportedProtocols',
- null,
- );
-
- // For convenience, use the ProtocolList to deserialise the custom
- // response to check if included DDS.
- final protocolList =
- ProtocolList.parse(response.body as Map<String, Object?>?);
- final ddsProtocol = protocolList?.protocols
- ?.singleWhereOrNull((protocol) => protocol.protocolName == "DDS");
- expect(ddsProtocol, isNot(isNull));
+ expect(await client.ddsAvailable, isTrue);
});
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
+
+ test('runs with auth codes enabled', () async {
+ final testFile = dap.createTestFile(r'''
+void main(List<String> args) {}
+ ''');
+
+ final outputEvents = await dap.client.collectOutput(file: testFile);
+ expect(_hasAuthCode(outputEvents.first), isTrue);
+ });
});
+
+ testDap((dap) async {
+ group('debug mode', () {
+ test('runs without DDS', () async {
+ final client = dap.client;
+ final testFile = dap.createTestFile(r'''
+void main(List<String> args) async {
+ print('Hello!'); // BREAKPOINT
+}
+ ''');
+ final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+
+ await client.hitBreakpoint(testFile, breakpointLine);
+
+ expect(await client.ddsAvailable, isFalse);
+ });
+
+ test('runs with auth tokens disabled', () async {
+ final testFile = dap.createTestFile(r'''
+void main(List<String> args) {}
+ ''');
+
+ final outputEvents = await dap.client.collectOutput(file: testFile);
+ expect(_hasAuthCode(outputEvents.first), isFalse);
+ });
+ // These tests can be slow due to starting up the external server process.
+ }, timeout: Timeout.none);
+ }, additionalArgs: ['--no-dds', '--no-auth-codes']);
+}
+
+/// Checks for the presence of an auth token in a VM Service URI in the
+/// "Connecting to VM Service" [OutputEvent].
+bool _hasAuthCode(OutputEventBody vmConnection) {
+ // TODO(dantup): Change this to use the dart.debuggerUris custom event
+ // if implemented (whch VS Code also needs).
+ final vmServiceUriPattern = RegExp(r'Connecting to VM Service at ([^\s]+)\s');
+ final authCodePattern = RegExp(r'ws://127.0.0.1:\d+/[\w=]{5,15}/ws');
+
+ final vmServiceUri =
+ vmServiceUriPattern.firstMatch(vmConnection.output)!.group(1);
+
+ return vmServiceUri != null && authCodePattern.hasMatch(vmServiceUri);
}
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index 3912205..0bc4afe 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -5,12 +5,14 @@
import 'dart:async';
import 'dart:io';
+import 'package:collection/collection.dart';
import 'package:dds/src/dap/adapters/dart.dart';
import 'package:dds/src/dap/logging.dart';
import 'package:dds/src/dap/protocol_generated.dart';
import 'package:dds/src/dap/protocol_stream.dart';
import 'package:dds/src/dap/protocol_stream_transformers.dart';
import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart' as vm;
import 'test_server.dart';
@@ -336,16 +338,20 @@
///
/// Launch options can be customised by passing a custom [launch] function that
/// will be used instead of calling `launch(file.path)`.
- Future<StoppedEventBody> hitBreakpoint(File file, int line,
- {Future<Response> Function()? launch}) async {
+ Future<StoppedEventBody> hitBreakpoint(
+ File file,
+ int line, {
+ Future<Response> Function()? launch,
+ }) async {
final stop = expectStop('breakpoint', file: file, line: line);
await Future.wait([
initialize(),
sendRequest(
SetBreakpointsArguments(
- source: Source(path: file.path),
- breakpoints: [SourceBreakpoint(line: line)]),
+ source: Source(path: file.path),
+ breakpoints: [SourceBreakpoint(line: line)],
+ ),
),
launch?.call() ?? this.launch(file.path),
], eagerError: true);
@@ -353,6 +359,25 @@
return stop;
}
+ /// Returns whether DDS is available for the VM Service the debug adapter
+ /// is connected to.
+ Future<bool> get ddsAvailable async {
+ final response = await custom(
+ '_getSupportedProtocols',
+ null,
+ );
+
+ // For convenience, use the ProtocolList to deserialise the custom
+ // response to check if included DDS.
+ final protocolList =
+ vm.ProtocolList.parse(response.body as Map<String, Object?>?);
+
+ final ddsProtocol = protocolList?.protocols?.singleWhereOrNull(
+ (protocol) => protocol.protocolName == 'DDS',
+ );
+ return ddsProtocol != null;
+ }
+
/// Runs a script and expects to pause at an exception in [file].
Future<StoppedEventBody> hitException(
File file, [
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index 1beb3cd..3680256 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -16,10 +16,10 @@
final _random = Random();
abstract class DapTestServer {
+ List<String> get errorLogs;
String get host;
int get port;
Future<void> stop();
- List<String> get errorLogs;
}
/// An instance of a DAP server running in-process (to aid debugging).
@@ -69,17 +69,24 @@
List<String> get errorLogs => _errors;
- OutOfProcessDapTestServer._(this._process, this.host, this.port) {
+ OutOfProcessDapTestServer._(
+ this._process,
+ this.host,
+ this.port,
+ Logger? logger,
+ ) {
// The DAP server should generally not write to stdout/stderr (unless -v is
// passed), but it may do if it fails to start or crashes. If this happens,
- // ensure these are included in the test output.
- _process.stdout.transform(utf8.decoder).listen(print);
- _process.stderr.transform(utf8.decoder).listen((s) {
- _errors.add(s);
- throw s;
+ // and there's no logger, print to stdout.
+ _process.stdout.transform(utf8.decoder).listen(logger ?? print);
+ _process.stderr.transform(utf8.decoder).listen((error) {
+ logger?.call(error);
+ _errors.add(error);
+ throw error;
});
unawaited(_process.exitCode.then((code) {
final message = 'Out-of-process DAP server terminated with code $code';
+ logger?.call(message);
_errors.add(message);
if (!_isShuttingDown && code != 0) {
throw message;
@@ -94,7 +101,10 @@
await _process.exitCode;
}
- static Future<OutOfProcessDapTestServer> create() async {
+ static Future<OutOfProcessDapTestServer> create({
+ Logger? logger,
+ List<String>? additionalArgs,
+ }) async {
final ddsEntryScript =
await Isolate.resolvePackageUri(Uri.parse('package:dds/dds.dart'));
final ddsLibFolder = path.dirname(ddsEntryScript!.toFilePath());
@@ -107,11 +117,14 @@
Platform.resolvedExecutable,
[
dapServerScript,
+ 'dap',
'--host=$host',
'--port=$port',
+ ...?additionalArgs,
+ if (logger != null) '--verbose'
],
);
- return OutOfProcessDapTestServer._(_process, host, port);
+ return OutOfProcessDapTestServer._(_process, host, port, logger);
}
}
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index 124efc3..3b1d3ab 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -5,12 +5,27 @@
import 'dart:async';
import 'dart:io';
+import 'package:dds/src/dap/logging.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_server.dart';
+/// A logger to use to log all traffic (both DAP and VM) to stdout.
+///
+/// If the enviroment variable is `DAP_TEST_VERBOSE` then `print` will be used,
+/// otherwise there will be no verbose logging.
+///
+/// DAP_TEST_VERBOSE=true pub run test --chain-stack-traces test/dap/integration
+///
+///
+/// When using the out-of-process DAP, this causes `--verbose` to be passed to
+/// the server which causes it to write all traffic to `stdout` which is then
+/// picked up by [OutOfProcessDapTestServer] and passed to this logger.
+final logger =
+ Platform.environment['DAP_TEST_VERBOSE'] == 'true' ? print : null;
+
/// Whether to run the DAP server in-process with the tests, or externally in
/// another process.
///
@@ -33,8 +48,11 @@
/// A helper function to wrap all tests in a library with setup/teardown functions
/// to start a shared server for all tests in the library and an individual
/// client for each test.
-testDap(Future<void> Function(DapTestSession session) tests) {
- final session = DapTestSession();
+testDap(
+ Future<void> Function(DapTestSession session) tests, {
+ List<String>? additionalArgs,
+}) {
+ final session = DapTestSession(additionalArgs: additionalArgs);
setUpAll(session.setUpAll);
tearDownAll(session.tearDownAll);
@@ -51,6 +69,9 @@
late DapTestServer server;
late DapTestClient client;
final _testFolders = <Directory>[];
+ final List<String>? additionalArgs;
+
+ DapTestSession({this.additionalArgs});
/// Creates a file in a temporary folder to be used as an application for testing.
///
@@ -68,7 +89,7 @@
}
Future<void> setUpAll() async {
- server = await _startServer();
+ server = await _startServer(logger: logger, additionalArgs: additionalArgs);
}
Future<void> tearDown() => client.stop();
@@ -111,9 +132,15 @@
}
/// Starts a DAP server that can be shared across tests.
- Future<DapTestServer> _startServer() async {
+ Future<DapTestServer> _startServer({
+ Logger? logger,
+ List<String>? additionalArgs,
+ }) async {
return useInProcessDap
- ? await InProcessDapTestServer.create()
- : await OutOfProcessDapTestServer.create();
+ ? await InProcessDapTestServer.create(logger: logger)
+ : await OutOfProcessDapTestServer.create(
+ logger: logger,
+ additionalArgs: additionalArgs,
+ );
}
}
diff --git a/pkg/dds/tool/dap/run_server.dart b/pkg/dds/tool/dap/run_server.dart
index 69f90f1..32e08e6 100644
--- a/pkg/dds/tool/dap/run_server.dart
+++ b/pkg/dds/tool/dap/run_server.dart
@@ -2,45 +2,79 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'package:args/args.dart';
+import 'dart:io';
+
+import 'package:args/command_runner.dart';
import 'package:dds/src/dap/server.dart';
Future<void> main(List<String> arguments) async {
- final args = argParser.parse(arguments);
- if (args[argHelp]) {
- print(argParser.usage);
- return;
+ // TODO(dantup): "dap_tool" is a placeholder and will likely eventually be a
+ // "dart" command.
+ final runner = CommandRunner('dap_tool', 'Dart DAP Tool')
+ ..addCommand(DapCommand());
+
+ try {
+ await runner.run(arguments);
+ } on UsageException catch (e) {
+ print(e);
+ exit(64);
}
-
- final port = int.parse(args[argPort]);
- final host = args[argHost];
-
- await DapServer.create(
- host: host,
- port: port,
- logger: args[argVerbose] ? print : null,
- );
}
-const argHelp = 'help';
-const argHost = 'host';
-const argPort = 'port';
-const argVerbose = 'verbose';
-final argParser = ArgParser()
- ..addFlag(argHelp, hide: true)
- ..addOption(
- argHost,
- defaultsTo: 'localhost',
- help: 'The hostname/IP to bind the server to',
- )
- ..addOption(
- argPort,
- abbr: 'p',
- defaultsTo: DapServer.defaultPort.toString(),
- help: 'The port to bind the server to',
- )
- ..addFlag(
- argVerbose,
- abbr: 'v',
- help: 'Whether to print diagnostic output to stdout',
- );
+class DapCommand extends Command {
+ static const argHost = 'host';
+ static const argPort = 'port';
+ static const argDds = 'dds';
+ static const argAuthCodes = 'auth-codes';
+ static const argVerbose = 'verbose';
+
+ @override
+ final String description = 'Start a DAP debug server.';
+
+ @override
+ final String name = 'dap';
+
+ DapCommand() {
+ argParser
+ ..addOption(
+ argHost,
+ defaultsTo: 'localhost',
+ help: 'The hostname/IP to bind the server to',
+ )
+ ..addOption(
+ argPort,
+ abbr: 'p',
+ defaultsTo: DapServer.defaultPort.toString(),
+ help: 'The port to bind the server to',
+ )
+ ..addFlag(
+ argDds,
+ defaultsTo: true,
+ help: 'Whether to enable DDS for debug sessions',
+ )
+ ..addFlag(
+ argAuthCodes,
+ defaultsTo: true,
+ help: 'Whether to enable authentication codes for VM Services',
+ )
+ ..addFlag(
+ argVerbose,
+ abbr: 'v',
+ help: 'Whether to print diagnostic output to stdout',
+ );
+ }
+
+ Future<void> run() async {
+ final args = argResults!;
+ final port = int.parse(args[argPort]);
+ final host = args[argHost];
+
+ await DapServer.create(
+ host: host,
+ port: port,
+ enableDdds: args[argDds],
+ enableAuthCodes: args[argAuthCodes],
+ logger: args[argVerbose] ? print : null,
+ );
+ }
+}
diff --git a/pkg/dev_compiler/lib/src/js_ast/source_map_printer.dart b/pkg/dev_compiler/lib/src/js_ast/source_map_printer.dart
index e770e99..5862fdb 100644
--- a/pkg/dev_compiler/lib/src/js_ast/source_map_printer.dart
+++ b/pkg/dev_compiler/lib/src/js_ast/source_map_printer.dart
@@ -10,6 +10,19 @@
import 'package:source_span/source_span.dart' show SourceLocation;
import 'js_ast.dart';
+/// A source map marker signaling that there is no new source information from
+/// a node and the previous source mapping can continue through it.
+///
+/// Conceptually this works exactly the same as `foo.sourceInformation = null`
+/// but allows the AST emitter to signal there is no new information in a way
+/// that null aware assignments don't override the lack of sourcemap information
+/// later.
+class _ContinueSourceMap {
+ const _ContinueSourceMap();
+}
+
+const continueSourceMap = _ContinueSourceMap();
+
class NodeEnd {
final SourceLocation end;
NodeEnd(this.end);
@@ -68,7 +81,7 @@
@override
void enterNode(Node node) {
var srcInfo = node.sourceInformation;
- if (srcInfo == null) return;
+ if (srcInfo == null || srcInfo == continueSourceMap) return;
SourceLocation dartStart;
if (srcInfo is SourceLocation) {
@@ -100,7 +113,7 @@
if (node is! Expression) _flushPendingMarks();
var srcInfo = node.sourceInformation;
- if (srcInfo == null) return;
+ if (srcInfo == null || srcInfo == continueSourceMap) return;
SourceLocation dartEnd;
if (srcInfo is NodeSpan) {
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index b68b8c1..c384ce9 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -27,7 +27,8 @@
import '../compiler/shared_compiler.dart';
import '../js_ast/js_ast.dart' as js_ast;
import '../js_ast/js_ast.dart' show ModuleItem, js;
-import '../js_ast/source_map_printer.dart' show NodeEnd, NodeSpan, HoverComment;
+import '../js_ast/source_map_printer.dart'
+ show NodeEnd, NodeSpan, HoverComment, continueSourceMap;
import 'constants.dart';
import 'js_interop.dart';
import 'js_typerep.dart';
@@ -5765,7 +5766,9 @@
// Logical negation, `!e`, is a boolean conversion context since it is
// defined as `e ? false : true`.
- return js.call('!#', _visitTest(operand));
+ return js
+ .call('!#', _visitTest(operand))
+ .withSourceInformation(continueSourceMap) as js_ast.Expression;
}
@override
@@ -5785,12 +5788,12 @@
@override
js_ast.Expression visitConditionalExpression(ConditionalExpression node) {
- return js.call('# ? # : #', [
- _visitTest(node.condition),
- _visitExpression(node.then),
- _visitExpression(node.otherwise)
- ])
- ..sourceInformation = _nodeStart(node.condition);
+ var condition = _visitTest(node.condition);
+ var then = _visitExpression(node.then);
+ var otherwise = _visitExpression(node.otherwise);
+ return js.call('# ? # : #', [condition, then, otherwise])
+ ..sourceInformation =
+ condition.sourceInformation ?? _nodeStart(node.condition);
}
@override
diff --git a/pkg/dev_compiler/test/sourcemap/testfiles/step_through_conditional_expression.dart b/pkg/dev_compiler/test/sourcemap/testfiles/step_through_conditional_expression.dart
index fc19ec0..45a661d 100644
--- a/pkg/dev_compiler/test/sourcemap/testfiles/step_through_conditional_expression.dart
+++ b/pkg/dev_compiler/test/sourcemap/testfiles/step_through_conditional_expression.dart
@@ -8,7 +8,7 @@
void main() {
print(/*bc:1*/ foo() ? bar() : /*bc:2*/ baz());
- print(/*bc:4*/ ! /*bc:3*/ foo() ? /*bc:5*/ bar() : baz());
+ print(! /*bc:3*/ foo() ? /*bc:4*/ bar() : baz());
}
bool foo() {
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index a29766c..399d39a 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -731,9 +731,8 @@
if (func != null) {
_pass1.transformTypeParameterList(func.typeParameters, func);
- _pass1.transformVariableDeclarationList(
- func.positionalParameters, func);
- _pass1.transformVariableDeclarationList(func.namedParameters, func);
+ addUsedParameters(func.positionalParameters);
+ addUsedParameters(func.namedParameters);
func.returnType.accept(typeVisitor);
}
@@ -755,6 +754,15 @@
}
}
+ void addUsedParameters(List<VariableDeclaration> params) {
+ for (var param in params) {
+ // Do not visit initializer (default value) of a parameter as it is
+ // going to be removed during pass 2.
+ _pass1.transformExpressionList(param.annotations, param);
+ param.type.accept(typeVisitor);
+ }
+ }
+
void addUsedExtension(Extension node) {
if (_usedExtensions.add(node)) {
node.annotations = const <Expression>[];
@@ -1564,6 +1572,7 @@
// have a body even if it can never be called.
_makeUnreachableBody(node.function);
}
+ _removeDefaultValuesOfParameters(node.function);
node.function.asyncMarker = AsyncMarker.Sync;
switch (node.stubKind) {
case ProcedureStubKind.Regular:
@@ -1586,6 +1595,7 @@
Statistics.fieldInitializersDropped++;
} else if (node is Constructor) {
_makeUnreachableBody(node.function);
+ _removeDefaultValuesOfParameters(node.function);
node.initializers = const <Initializer>[];
Statistics.constructorBodiesDropped++;
} else {
@@ -1629,6 +1639,15 @@
}
}
+ void _removeDefaultValuesOfParameters(FunctionNode function) {
+ for (var p in function.positionalParameters) {
+ p.initializer = null;
+ }
+ for (var p in function.namedParameters) {
+ p.initializer = null;
+ }
+ }
+
@override
TreeNode defaultTreeNode(TreeNode node, TreeNode removalSentinel) {
return node; // Do not traverse into other nodes.
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart b/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart
new file mode 100644
index 0000000..d50e669
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart
@@ -0,0 +1,21 @@
+// 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.12
+
+enum A { a1, a2 }
+
+class B {
+ void foo({A? x = A.a1}) {}
+}
+
+class C implements B {
+ void foo({A? x}) {}
+}
+
+B obj = C();
+
+void main() {
+ obj.foo();
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart.expect
new file mode 100644
index 0000000..82c5c73
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_46461.dart.expect
@@ -0,0 +1,19 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+}
+abstract class B extends core::Object {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] abstract method foo() → void;
+}
+class C extends core::Object implements self::B {
+ synthetic constructor •() → self::C
+ : super core::Object::•()
+ ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] method foo() → void {}
+}
+[@vm.inferred-type.metadata=#lib::C?]static field self::B obj = new self::C::•();
+static method main() → void {
+ [@vm.direct-call.metadata=#lib::C.foo??] [@vm.inferred-type.metadata=!? (skip check)] [@vm.inferred-type.metadata=#lib::C?] self::obj.{self::B::foo}(){({x: self::A?}) → void};
+}
diff --git a/runtime/vm/constants_arm64.cc b/runtime/vm/constants_arm64.cc
index b30808b..c10999b 100644
--- a/runtime/vm/constants_arm64.cc
+++ b/runtime/vm/constants_arm64.cc
@@ -12,8 +12,8 @@
const char* const cpu_reg_names[kNumberOfCpuRegisters] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",
- "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21",
- "nr", "r23", "r24", "ip0", "ip1", "pp", "r28", "fp", "lr", "r31",
+ "r11", "r12", "r13", "r14", "r15", "ip0", "ip1", "r18", "r19", "r20", "r21",
+ "nr", "r23", "r24", "r25", "r26", "pp", "r28", "fp", "lr", "r31",
};
const char* const fpu_reg_names[kNumberOfFpuRegisters] = {
diff --git a/tools/VERSION b/tools/VERSION
index f2353b5..8cc8b18 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 244
+PRERELEASE 245
PRERELEASE_PATCH 0
\ No newline at end of file