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