blob: 7e2d115266db41d313b2228b68f7640b08211fbc [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:async/async.dart';
import 'package:dap/dap.dart';
import 'package:vm_service/vm_service.dart' as vm;
import '../protocol_stream.dart';
import 'dart.dart';
import 'mixins.dart';
/// A DAP Debug Adapter for attaching to already-running Dart and Flutter applications.
class DdsHostedAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
DartAttachRequestArguments>
with PidTracker, VmServiceInfoFileUtils, PackageConfigUtils, TestAdapter {
Uri? ddsUri;
@override
final parseLaunchArgs = DartLaunchRequestArguments.fromJson;
@override
final parseAttachArgs = DartAttachRequestArguments.fromJson;
DdsHostedAdapter()
: super(
// TODO(helin24): Make channel optional for base adapter class.
ByteStreamServerChannel(
Stream.empty(),
NullStreamSink(),
(message) {},
),
ipv6: true,
enableDds: false,
);
/// Whether the VM Service closing should be used as a signal to terminate the
/// debug session.
///
/// True here because we no longer need this adapter once the VM service has closed.
@override
bool get terminateOnVmServiceClose => true;
@override
Future<void> debuggerConnected(vm.VM vmInfo) async {}
/// Called by [disconnectRequest] to request that we forcefully shut down the
/// app being run (or in the case of an attach, disconnect).
@override
Future<void> disconnectImpl() async {
await handleDetach();
}
/// Called by [launchRequest] to request that we actually start the app to be
/// run/debugged.
///
/// For debugging, this should start paused, connect to the VM Service, set
/// breakpoints, and resume.
@override
Future<void> launchImpl() async {
sendConsoleOutput(
'Launch is not supported for the attach only adapter',
);
handleSessionTerminate();
}
/// Called by [attachRequest] to request that we actually connect to the app
/// to be debugged.
@override
Future<void> attachImpl() async {
final args = this.args as DartAttachRequestArguments;
final vmServiceUri = args.vmServiceUri;
if (vmServiceUri == null) {
sendConsoleOutput(
'To attach, provide vmServiceUri',
);
handleSessionTerminate();
return;
}
if (vmServiceUri != ddsUri.toString()) {
sendConsoleOutput(
'To use the attach-only adapter, VM service URI must match DDS URI',
);
handleSessionTerminate();
}
// TODO(helin24): In this method, we only need to verify that the DDS URI
// matches the VM service URI. The DDS URI isn't really needed because this
// adapter is running in the same process. We need to refactor so that we
// call DDS/VM service methods directly instead of using the websocket.
unawaited(connectDebugger(ddsUri!));
}
/// Called by [terminateRequest] to request that we gracefully shut down the
/// app being run (or in the case of an attach, disconnect).
@override
Future<void> terminateImpl() async {
await handleDetach();
terminatePids(ProcessSignal.sigterm);
}
void handleMessage(String message, void Function(Response) responseWriter) {
final potentialException =
DebugAdapterException('Message does not conform to DAP spec: $message');
try {
final Map<String, Object?> json = jsonDecode(message);
final type = json['type'] as String;
if (type == 'request') {
handleIncomingRequest(Request.fromJson(json), responseWriter);
// TODO(helin24): Handle event and response?
} else {
throw potentialException;
}
} catch (e) {
throw potentialException;
}
}
}