[dds] Add support for DAP sourceRequest to download source from the VM

Change-Id: Icb639c7008d51d6ffbecb95e362cc07c81993424
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209103
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Danny Tuppeny 2021-08-09 16:48:08 +00:00 committed by commit-bot@chromium.org
parent 9e928c8587
commit 3d3a48ff9a
4 changed files with 113 additions and 0 deletions

View file

@ -21,6 +21,14 @@ import '../protocol_converter.dart';
import '../protocol_generated.dart';
import '../protocol_stream.dart';
/// The mime type to send with source responses to the client.
///
/// This is used so if the source name does not end with ".dart" the client can
/// still tell which language to use (for syntax highlighting, etc.).
///
/// https://github.com/microsoft/vscode/issues/8182#issuecomment-231151640
const dartMimeType = 'text/x-dart';
/// Maximum number of toString()s to be called when responding to variables
/// requests from the client.
///
@ -723,6 +731,44 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
await _dds?.shutdown();
}
/// [sourceRequest] is called by the client to request source code for a given
/// source.
///
/// The client may provide a whole source or just an int sourceReference (the
/// spec originally had only sourceReference but now supports whole sources).
///
/// The supplied sourceReference should correspond to a ScriptRef instance
/// that was stored to generate the sourceReference when sent to the client.
@override
Future<void> sourceRequest(
Request request,
SourceArguments args,
void Function(SourceResponseBody) sendResponse,
) async {
final storedData = _isolateManager.getStoredData(
args.source?.sourceReference ?? args.sourceReference,
);
if (storedData == null) {
throw StateError('source reference is no longer valid');
}
final thread = storedData.thread;
final data = storedData.data;
final scriptRef = data is vm.ScriptRef ? data : null;
if (scriptRef == null) {
throw StateError('source reference was not a valid script');
}
final script = await thread.getScript(scriptRef);
final scriptSource = script.source;
if (scriptSource == null) {
throw DebugAdapterException('<source not available>');
}
sendResponse(
SourceResponseBody(content: scriptSource, mimeType: dartMimeType),
);
}
/// 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

View file

@ -204,6 +204,12 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
void Function(SetExceptionBreakpointsResponseBody) sendResponse,
);
Future<void> sourceRequest(
Request request,
SourceArguments args,
void Function(SourceResponseBody) sendResponse,
);
Future<void> stackTraceRequest(
Request request,
StackTraceArguments args,
@ -312,6 +318,8 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
handle(request, threadsRequest, _voidArgs);
} else if (request.command == 'stackTrace') {
handle(request, stackTraceRequest, StackTraceArguments.fromJson);
} else if (request.command == 'source') {
handle(request, sourceRequest, SourceArguments.fromJson);
} else if (request.command == 'scopes') {
handle(request, scopesRequest, ScopesArguments.fromJson);
} else if (request.command == 'variables') {

View file

@ -129,6 +129,45 @@ void main(List<String> args) async {
final vmServiceUri = _extractVmServiceUri(outputEvents.first);
expect(vmServiceUri.path, matches(vmServiceAuthCodePathPattern));
});
test('can download source code from the VM', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
// Hit the initial breakpoint.
final stop = await dap.client.hitBreakpoint(
testFile,
breakpointLine,
launch: () => client.launch(
testFile.path,
debugSdkLibraries: true,
),
);
// Step in to go into print.
final responses = await Future.wait([
client.expectStop('step', sourceName: 'dart:core/print.dart'),
client.stepIn(stop.threadId!),
], eagerError: true);
final stopResponse = responses.first as StoppedEventBody;
// Fetch the top stack frame (which should be inside print).
final stack = await client.getValidStack(
stopResponse.threadId!,
startFrame: 0,
numFrames: 1,
);
final topFrame = stack.stackFrames.first;
// SDK sources should have a sourceReference and no path.
expect(topFrame.source!.path, isNull);
expect(topFrame.source!.sourceReference, greaterThan(0));
// Source code should contain the implementation/signature of print().
final source = await client.getValidSource(topFrame.source!);
expect(source.content, contains('void print(Object? object) {'));
});
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);

View file

@ -217,6 +217,18 @@ class DapTestClient {
_channel.sendResponse(response);
}
/// Sends a source request to the server to request source code for a [source]
/// reference that may have come from a stack frame or similar.
///
/// Returns a Future that completes when the server returns a corresponding
/// response.
Future<Response> source(Source source) => sendRequest(
SourceArguments(
source: source,
sourceReference: source.sourceReference!,
),
);
/// Sends a stackTrace request to the server to request the call stack for a
/// given thread.
///
@ -475,6 +487,14 @@ extension DapTestClientExtension on DapTestClient {
response.body as Map<String, Object?>);
}
/// Fetches source for a sourceReference and asserts it was a valid response.
Future<SourceResponseBody> getValidSource(Source source) async {
final response = await this.source(source);
expect(response.success, isTrue);
expect(response.command, equals('source'));
return SourceResponseBody.fromJson(response.body as Map<String, Object?>);
}
/// Fetches threads and asserts a valid response.
Future<ThreadsResponseBody> getValidThreads() async {
final response = await threads();