Skip to content

Commit 33c3cfc

Browse files
DanTupbkonyi
authored andcommitted
[dds] Support changing DAP debug settings after the session has started
Change-Id: I287457296408ae49950cef501780054260b57566 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205540 Reviewed-by: Ben Konyi <[email protected]>
1 parent 6b4fc26 commit 33c3cfc

File tree

4 files changed

+114
-39
lines changed

4 files changed

+114
-39
lines changed

pkg/dds/lib/src/dap/adapters/dart.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,15 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
318318
sendResponse(protocols?.toJson());
319319
break;
320320

321+
/// Used to toggle debug settings such as whether SDK/Packages are
322+
/// debuggable while the session is in progress.
323+
case 'updateDebugOptions':
324+
if (args != null) {
325+
await _updateDebugOptions(args.args);
326+
}
327+
sendResponse(null);
328+
break;
329+
321330
default:
322331
await super.customRequest(request, args, sendResponse);
323332
}
@@ -1009,7 +1018,29 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
10091018
// Notify IsolateManager if we'll be debugging so it knows whether to set
10101019
// up breakpoints etc. when isolates are registered.
10111020
final debug = !(args.noDebug ?? false);
1012-
_isolateManager.setDebugEnabled(debug);
1021+
_isolateManager.debug = debug;
1022+
_isolateManager.debugSdkLibraries = args.debugSdkLibraries ?? true;
1023+
_isolateManager.debugExternalPackageLibraries =
1024+
args.debugExternalPackageLibraries ?? true;
1025+
}
1026+
1027+
/// Updates the current debug options for the session.
1028+
///
1029+
/// Clients may not know about all debug options, so anything not included
1030+
/// in the map will not be updated by this method.
1031+
Future<void> _updateDebugOptions(Map<String, Object?> args) async {
1032+
// TODO(dantup): Document this - it's a public API we expect to be used
1033+
// by editors that can support it (although it will require custom
1034+
// code as it's there's no DAP standard for this, or the settings it
1035+
// toggles).
1036+
if (args.containsKey('debugSdkLibraries')) {
1037+
_isolateManager.debugSdkLibraries = args['debugSdkLibraries'] as bool;
1038+
}
1039+
if (args.containsKey('debugExternalPackageLibraries')) {
1040+
_isolateManager.debugExternalPackageLibraries =
1041+
args['debugExternalPackageLibraries'] as bool;
1042+
}
1043+
await _isolateManager.applyDebugOptions();
10131044
}
10141045

10151046
/// A wrapper around the same name function from package:vm_service that

pkg/dds/lib/src/dap/base_debug_adapter.dart

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
6565
RawRequestArguments? args,
6666
void Function(Object?) sendResponse,
6767
) async {
68-
final response = Response(
69-
success: false,
70-
requestSeq: request.seq,
71-
seq: _sequence++,
72-
command: request.command,
73-
message: 'Unknown command: ${request.command}',
74-
);
75-
sendResponse(response);
68+
throw DebugAdapterException('Unknown command ${request.command}');
7669
}
7770

7871
Future<void> disconnectRequest(
@@ -251,7 +244,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments> {
251244
} else if (message is Response) {
252245
_handleIncomingResponse(message);
253246
} else {
254-
throw Exception('Unknown Protocol message ${message.type}');
247+
throw DebugAdapterException('Unknown Protocol message ${message.type}');
255248
}
256249
}
257250

pkg/dds/lib/src/dap/isolate_manager.dart

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,33 @@ class IsolateManager {
2323
final Map<int, ThreadInfo> _threadsByThreadId = {};
2424
int _nextThreadNumber = 1;
2525

26-
/// Whether debugging is enabled.
26+
/// Whether debugging is enabled for this session.
2727
///
2828
/// This must be set before any isolates are spawned and controls whether
2929
/// breakpoints or exception pause modes are sent to the VM.
3030
///
31+
/// If false, requests to send breakpoints or exception pause mode will be
32+
/// dropped. Other functionality (handling pause events, resuming, etc.) will
33+
/// all still function.
34+
///
3135
/// This is used to support debug sessions that have VM Service connections
3236
/// but were run with noDebug: true (for example we may need a VM Service
3337
/// connection for a noDebug flutter app in order to support hot reload).
34-
bool _debug = false;
38+
bool debug = false;
39+
40+
/// Whether SDK libraries should be marked as debuggable.
41+
///
42+
/// Calling [sendLibraryDebuggables] is required after changing this value to
43+
/// apply changes. This allows applying both [debugSdkLibraries] and
44+
/// [debugExternalPackageLibraries] in one step.
45+
bool debugSdkLibraries = true;
46+
47+
/// Whether external package libraries should be marked as debuggable.
48+
///
49+
/// Calling [sendLibraryDebuggables] is required after changing this value to
50+
/// apply changes. This allows applying both [debugSdkLibraries] and
51+
/// [debugExternalPackageLibraries] in one step.
52+
bool debugExternalPackageLibraries = true;
3553

3654
/// Tracks breakpoints last provided by the client so they can be sent to new
3755
/// isolates that appear after initial breakpoints were sent.
@@ -72,6 +90,18 @@ class IsolateManager {
7290
/// not exited between accessing this list and trying to use the results.
7391
List<ThreadInfo> get threads => _threadsByIsolateId.values.toList();
7492

93+
/// Re-applies debug options to all isolates/libraries.
94+
///
95+
/// This is required if options like debugSdkLibraries are modified, but is a
96+
/// separate step to batch together changes to multiple options.
97+
Future<void> applyDebugOptions() async {
98+
await Future.wait(_threadsByThreadId.values.map(
99+
// debuggable libraries is the only thing currently affected by these
100+
// changable options.
101+
(isolate) => _sendLibraryDebuggables(isolate.isolate),
102+
));
103+
}
104+
75105
Future<T> getObject<T extends vm.Response>(
76106
vm.IsolateRef isolate, vm.ObjRef object) async {
77107
final res = await _adapter.vmService?.getObject(isolate.id!, object.id!);
@@ -219,19 +249,6 @@ class IsolateManager {
219249
.map((isolate) => _sendBreakpoints(isolate.isolate, uri: uri)));
220250
}
221251

222-
/// Sets whether debugging is enabled for this session.
223-
///
224-
/// If not, requests to send breakpoints or exception pause mode will be
225-
/// dropped. Other functionality (handling pause events, resuming, etc.) will
226-
/// all still function.
227-
///
228-
/// This is used to support debug sessions that have VM Service connections
229-
/// but were run with noDebug: true (for example we may need a VM Service
230-
/// connection for a noDebug flutter app in order to support hot reload).
231-
void setDebugEnabled(bool debug) {
232-
_debug = debug;
233-
}
234-
235252
/// Records exception pause mode as one of 'None', 'Unhandled' or 'All'. All
236253
/// existing isolates will be updated to reflect the new setting.
237254
Future<void> setExceptionPauseMode(String mode) async {
@@ -371,13 +388,13 @@ class IsolateManager {
371388

372389
/// Checks whether a library should be considered debuggable.
373390
///
374-
/// This usesthe settings from the launch arguments (debugSdkLibraries
375-
/// and debugExternalPackageLibraries) against the type of library given.
391+
/// Initial values are provided in the launch arguments, but may be updated
392+
/// by the `updateDebugOptions` custom request.
376393
bool _libaryIsDebuggable(vm.LibraryRef library) {
377394
if (_isSdkLibrary(library)) {
378-
return _adapter.args.debugSdkLibraries ?? false;
395+
return debugSdkLibraries;
379396
} else if (_isExternalPackageLibrary(library)) {
380-
return _adapter.args.debugExternalPackageLibraries ?? false;
397+
return debugExternalPackageLibraries;
381398
} else {
382399
return true;
383400
}
@@ -391,7 +408,7 @@ class IsolateManager {
391408
/// newly-created isolates).
392409
Future<void> _sendBreakpoints(vm.IsolateRef isolate, {String? uri}) async {
393410
final service = _adapter.vmService;
394-
if (!_debug || service == null) {
411+
if (!debug || service == null) {
395412
return;
396413
}
397414

@@ -425,7 +442,7 @@ class IsolateManager {
425442
/// Sets the exception pause mode for an individual isolate.
426443
Future<void> _sendExceptionPauseMode(vm.IsolateRef isolate) async {
427444
final service = _adapter.vmService;
428-
if (!_debug || service == null) {
445+
if (!debug || service == null) {
429446
return;
430447
}
431448

@@ -436,7 +453,7 @@ class IsolateManager {
436453
/// on the debug settings.
437454
Future<void> _sendLibraryDebuggables(vm.IsolateRef isolateRef) async {
438455
final service = _adapter.vmService;
439-
if (!_debug || service == null) {
456+
if (!debug || service == null) {
440457
return;
441458
}
442459

pkg/dds/test/dap/integration/debug_breakpoints_test.dart

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,14 @@ void main(List<String> args) async {
155155
final stepLine = lineWith(testFile, '// STEP');
156156

157157
// Hit the initial breakpoint.
158-
final stop = await client.hitBreakpoint(testFile, breakpointLine);
158+
final stop = await client.hitBreakpoint(
159+
testFile,
160+
breakpointLine,
161+
launch: () => client.launch(
162+
testFile.path,
163+
debugSdkLibraries: false,
164+
),
165+
);
159166

160167
// Step in and expect stopping on the next line (don't go into print).
161168
await Future.wait([
@@ -203,12 +210,39 @@ void main(List<String> args) async {
203210
// TODO(dantup): Support for debugExternalPackageLibraries
204211
}, skip: true);
205212

206-
test('allows changing debug settings during session', () {
207-
// TODO(dantup): !
208-
// Dart-Code's DAP has a custom method that allows an editor to change
209-
// the debug settings (debugSdkLibraries/debugExternalPackageLibraries)
210-
// during a debug session.
211-
}, skip: true);
213+
test('allows changing debug settings during session', () async {
214+
final client = dap.client;
215+
final testFile = dap.createTestFile(r'''
216+
void main(List<String> args) async {
217+
print('Hello!'); // BREAKPOINT
218+
print('Hello!'); // STEP
219+
}
220+
''');
221+
final breakpointLine = lineWith(testFile, '// BREAKPOINT');
222+
final stepLine = lineWith(testFile, '// STEP');
223+
224+
// Start with debugSdkLibraryes _enabled_ and hit the breakpoint.
225+
final stop = await client.hitBreakpoint(
226+
testFile,
227+
breakpointLine,
228+
launch: () => client.launch(
229+
testFile.path,
230+
debugSdkLibraries: true,
231+
),
232+
);
233+
234+
// Turn off debugSdkLibraries.
235+
await client.custom('updateDebugOptions', {
236+
'debugSdkLibraries': false,
237+
});
238+
239+
// Step in and expect stopping on the next line (don't go into print
240+
// because we turned off SDK debugging).
241+
await Future.wait([
242+
client.expectStop('step', file: testFile, line: stepLine),
243+
client.stepIn(stop.threadId!),
244+
], eagerError: true);
245+
});
212246
// These tests can be slow due to starting up the external server process.
213247
}, timeout: Timeout.none);
214248
}

0 commit comments

Comments
 (0)