[ VM / Service ] Add support for enabling/disabling breakpoints
This change adds a `setBreakpointState` RPC which allows for breakpoints
to be enabled or disabled without needing to remove and recreate
breakpoints.
Fixes https://github.com/dart-lang/sdk/issues/45336.
TEST=set_breakpoint_state_test.dart
Change-Id: I1a04e6028d4e4560fdb8d3d26420c9a05da06b4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193896
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 4064188..b5ad7d0 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-04-06T14:39:06.316531",
+ "generated": "2021-04-06T11:20:39.965073",
"generator": "tools/generate_package_config.dart",
"packages": [
{
@@ -728,6 +728,12 @@
"languageVersion": "2.12"
},
{
+ "name": "uuid",
+ "rootUri": "../third_party/pkg/uuid",
+ "packageUri": "lib/",
+ "languageVersion": "2.0"
+ },
+ {
"name": "vector_math",
"rootUri": "../third_party/pkg/vector_math",
"packageUri": "lib/",
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 2492753..5f0da4d 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 6.3.0-dev
+- Add support for `setBreakpointState` RPC and updated `Breakpoint` class to include
+ `enabled` property.
+
## 6.2.0
- Added support for `getHttpProfile` and `clearHttpProfile` `dart:io` service extensions.
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index 7122a12..c861f82 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -136,6 +136,7 @@
if (obj == "BreakpointAdded") return obj;
if (obj == "BreakpointRemoved") return obj;
if (obj == "BreakpointResolved") return obj;
+ if (obj == "BreakpointUpdated") return obj;
if (obj == "Extension") return obj;
if (obj == "GC") return obj;
if (obj == "Inspect") return obj;
@@ -293,6 +294,7 @@
assertNotNull(obj);
assertString(obj.id!);
assertInt(obj.breakpointNumber!);
+ assertBool(obj.enabled!);
assertBool(obj.resolved!);
if (obj.location is vms.SourceLocation) {
assertSourceLocation(obj.location!);
diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore
index 7e45717..cdc39af 100644
--- a/pkg/vm_service/java/.gitignore
+++ b/pkg/vm_service/java/.gitignore
@@ -4,6 +4,7 @@
src/org/dartlang/vm/service/consumer/AddBreakpointAtEntryConsumer.java
src/org/dartlang/vm/service/consumer/AddBreakpointConsumer.java
src/org/dartlang/vm/service/consumer/AddBreakpointWithScriptUriConsumer.java
+src/org/dartlang/vm/service/consumer/BreakpointConsumer.java
src/org/dartlang/vm/service/consumer/ClearCpuSamplesConsumer.java
src/org/dartlang/vm/service/consumer/CpuSamplesConsumer.java
src/org/dartlang/vm/service/consumer/EvaluateConsumer.java
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 452573e..d934516 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.44
+version=3.45
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 8b869fb..50fe029 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
HeapSnapshotObjectNoData,
HeapSnapshotObjectNullData;
-const String vmServiceVersion = '3.44.0';
+const String vmServiceVersion = '3.45.0';
/// @optional
const String optional = 'optional';
@@ -229,6 +229,7 @@
'removeBreakpoint': const ['Success'],
'requestHeapSnapshot': const ['Success'],
'resume': const ['Success'],
+ 'setBreakpointState': const ['Breakpoint'],
'setExceptionPauseMode': const ['Success'],
'setFlag': const ['Success', 'Error'],
'setLibraryDebuggable': const ['Success'],
@@ -1008,6 +1009,18 @@
Future<Success> resume(String isolateId,
{/*StepOption*/ String? step, int? frameIndex});
+ /// The `setBreakpointState` RPC allows for breakpoints to be enabled or
+ /// disabled, without requiring for the breakpoint to be completely removed.
+ ///
+ /// If `isolateId` refers to an isolate which has exited, then the `Collected`
+ /// [Sentinel] is returned.
+ ///
+ /// The returned [Breakpoint] is the updated breakpoint with its new values.
+ ///
+ /// See [Breakpoint].
+ Future<Breakpoint> setBreakpointState(
+ String isolateId, String breakpointId, bool enable);
+
/// The `setExceptionPauseMode` RPC is used to control if an isolate pauses
/// when an exception is thrown.
///
@@ -1136,7 +1149,7 @@
/// IsolateReload, ServiceExtensionAdded
/// Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
/// PauseException, PausePostRequest, Resume, BreakpointAdded,
- /// BreakpointResolved, BreakpointRemoved, Inspect, None
+ /// BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
/// GC | GC
/// Extension | Extension
/// Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -1471,6 +1484,13 @@
frameIndex: params['frameIndex'],
);
break;
+ case 'setBreakpointState':
+ response = await _serviceImplementation.setBreakpointState(
+ params!['isolateId'],
+ params['breakpointId'],
+ params['enable'],
+ );
+ break;
case 'setExceptionPauseMode':
response = await _serviceImplementation.setExceptionPauseMode(
params!['isolateId'],
@@ -1667,7 +1687,7 @@
// IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, IsolateReload, ServiceExtensionAdded
Stream<Event> get onIsolateEvent => _getEventController('Isolate').stream;
- // PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect, None
+ // PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
Stream<Event> get onDebugEvent => _getEventController('Debug').stream;
// GC
@@ -1982,6 +2002,15 @@
});
@override
+ Future<Breakpoint> setBreakpointState(
+ String isolateId, String breakpointId, bool enable) =>
+ _call('setBreakpointState', {
+ 'isolateId': isolateId,
+ 'breakpointId': breakpointId,
+ 'enable': enable
+ });
+
+ @override
Future<Success> setExceptionPauseMode(
String isolateId, /*ExceptionPauseMode*/ String mode) =>
_call('setExceptionPauseMode', {'isolateId': isolateId, 'mode': mode});
@@ -2430,6 +2459,9 @@
/// A breakpoint has been removed.
static const String kBreakpointRemoved = 'BreakpointRemoved';
+ /// A breakpoint has been updated.
+ static const String kBreakpointUpdated = 'BreakpointUpdated';
+
/// A garbage collection event.
static const String kGC = 'GC';
@@ -2810,6 +2842,9 @@
/// A number identifying this breakpoint to the user.
int? breakpointNumber;
+ /// Is this breakpoint enabled?
+ bool? enabled;
+
/// Has this breakpoint been assigned to a specific program location?
bool? resolved;
@@ -2826,6 +2861,7 @@
Breakpoint({
required this.breakpointNumber,
+ required this.enabled,
required this.resolved,
required this.location,
required String id,
@@ -2836,6 +2872,7 @@
Breakpoint._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
breakpointNumber = json['breakpointNumber'] ?? -1;
+ enabled = json['enabled'] ?? false;
resolved = json['resolved'] ?? false;
isSyntheticAsyncContinuation = json['isSyntheticAsyncContinuation'];
location = createServiceObject(json['location']!,
@@ -2851,6 +2888,7 @@
json['type'] = type;
json.addAll({
'breakpointNumber': breakpointNumber,
+ 'enabled': enabled,
'resolved': resolved,
'location': location?.toJson(),
});
@@ -2864,8 +2902,8 @@
operator ==(other) => other is Breakpoint && id == other.id;
String toString() => '[Breakpoint ' //
- 'id: ${id}, breakpointNumber: ${breakpointNumber}, resolved: ${resolved}, ' //
- 'location: ${location}]';
+ 'id: ${id}, breakpointNumber: ${breakpointNumber}, enabled: ${enabled}, ' //
+ 'resolved: ${resolved}, location: ${location}]';
}
/// `ClassRef` is a reference to a `Class`.
@@ -3685,6 +3723,7 @@
/// - BreakpointAdded
/// - BreakpointRemoved
/// - BreakpointResolved
+ /// - BreakpointUpdated
@optional
Breakpoint? breakpoint;
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 5ba220b..dfe2e30 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -3,7 +3,7 @@
A library to communicate with a service implementing the Dart VM
service protocol.
-version: 6.2.0
+version: 6.3.0-dev
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
diff --git a/pkg/vm_service/test/set_breakpoint_state_test.dart b/pkg/vm_service/test/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..7bc96ab
--- /dev/null
+++ b/pkg/vm_service/test/set_breakpoint_state_test.dart
@@ -0,0 +1,71 @@
+// 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.
+// VMOptions=--verbose_debug
+
+import 'package:vm_service/vm_service.dart';
+import 'package:test/test.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+ while (true) {
+ print('a'); // LINE_A
+ print('b'); // LINE_B
+ }
+}
+
+late Breakpoint bpt;
+
+var tests = <IsolateTest>[
+ hasPausedAtStart,
+ (VmService service, IsolateRef isolateRef) async {
+ bpt = await service.addBreakpointWithScriptUri(
+ isolateRef.id!,
+ 'set_breakpoint_state_test.dart',
+ LINE_A,
+ );
+ expect(bpt.enabled, true);
+ },
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (VmService service, IsolateRef isolateRef) async {
+ bpt = await service.setBreakpointState(
+ isolateRef.id!,
+ bpt.id!,
+ false,
+ );
+ expect(bpt.enabled, false);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (VmService service, IsolateRef isolateRef) async {
+ bpt = await service.setBreakpointState(
+ isolateRef.id!,
+ bpt.id!,
+ true,
+ );
+ expect(bpt.enabled, true);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ 'set_breakpoint_state_test.dart',
+ pause_on_start: true,
+ testeeConcurrent: testMain,
+ );
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index 1516d15..e32c00f 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2407,6 +2407,9 @@
// Either SourceLocation or UnresolvedSourceLocation.
Location? location;
+ // Is the breakpoint enabled?
+ bool? enabled;
+
// The breakpoint is in a file which is not yet loaded.
bool? latent;
@@ -2425,6 +2428,7 @@
// number never changes.
assert((number == null) || (number == newNumber));
number = newNumber;
+ enabled = map['enabled'];
resolved = map['resolved'];
var oldLocation = location;
@@ -2448,6 +2452,15 @@
assert(resolved! || location is UnresolvedSourceLocation);
}
+ Future<void> setState(bool enable) {
+ return location!.script.isolate!.invokeRpcNoUpgrade('setBreakpointState', {
+ 'breakpointId': 'breakpoints/$number',
+ 'enable': enable,
+ }).then((Map result) {
+ _update(result, false);
+ });
+ }
+
void remove() {
location!.script._removeBreakpoint(this);
}
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index b63155d..1fe66c5 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
final result = await vm.invokeRpcNoUpgrade('getVersion', {});
expect(result['type'], 'Version');
expect(result['major'], 3);
- expect(result['minor'], 44);
+ expect(result['minor'], 45);
expect(result['_privateMajor'], 0);
expect(result['_privateMinor'], 0);
},
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 3ddf48d..291688c 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -175,6 +175,7 @@
reload_sources_test: SkipByDesign # Hot reload is disabled in AOT mode.
rewind_optimized_out_test: SkipByDesign # Debugger is disabled in AOT mode.
rewind_test: SkipByDesign # Debugger is disabled in AOT mode.
+set_breakpoint_state_test: SkipByDesign # Debugger is disabled in AOT mode.
set_library_debuggable_test: SkipByDesign # Debugger is disabled in AOT mode.
sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Platform.executable.
simple_reload_test: SkipByDesign # Hot reload is disabled in AOT mode.
diff --git a/runtime/observatory/tests/service/set_breakpoint_state_test.dart b/runtime/observatory/tests/service/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..fdcee6a
--- /dev/null
+++ b/runtime/observatory/tests/service/set_breakpoint_state_test.dart
@@ -0,0 +1,65 @@
+// 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.
+// VMOptions=--verbose_debug
+
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+ while (true) {
+ print('a'); // LINE_A
+ print('b'); // LINE_B
+ }
+}
+
+late Breakpoint bpt;
+
+var tests = <IsolateTest>[
+ hasPausedAtStart,
+ (Isolate isolate) async {
+ bpt = await isolate.addBreakpointByScriptUri(
+ 'set_breakpoint_state_test.dart',
+ LINE_A,
+ );
+ expect(bpt.enabled, true);
+ },
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (Isolate isolate) async {
+ await bpt.setState(
+ false,
+ );
+ expect(bpt.enabled, false);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (Isolate isolate) async {
+ await bpt.setState(
+ true,
+ );
+ expect(bpt.enabled, true);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ pause_on_start: true,
+ testeeConcurrent: testMain,
+ );
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 1c5c9bc..743ab24 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -2418,6 +2418,9 @@
// Either SourceLocation or UnresolvedSourceLocation.
Location location;
+ // Is the breakpoint enabled?
+ bool enabled;
+
// The breakpoint is in a file which is not yet loaded.
bool latent;
@@ -2436,6 +2439,7 @@
// number never changes.
assert((number == null) || (number == newNumber));
number = newNumber;
+ enabled = map['enabled'];
resolved = map['resolved'];
var oldLocation = location;
@@ -2459,6 +2463,15 @@
assert(resolved || location is UnresolvedSourceLocation);
}
+ Future<void> setState(bool enable) {
+ return location.script.isolate.invokeRpcNoUpgrade('setBreakpointState', {
+ 'breakpointId': 'breakpoints/$number',
+ 'enable': enable,
+ }).then((Map result) {
+ _update(result, false);
+ });
+ }
+
void remove() {
location.script._removeBreakpoint(this);
}
diff --git a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
index e7a9528..bd9d9b1 100644
--- a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
var result = await vm.invokeRpcNoUpgrade('getVersion', {});
expect(result['type'], equals('Version'));
expect(result['major'], equals(3));
- expect(result['minor'], equals(44));
+ expect(result['minor'], equals(45));
expect(result['_privateMajor'], equals(0));
expect(result['_privateMinor'], equals(0));
},
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 099e686..717b43e 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -174,6 +174,7 @@
reload_sources_test: SkipByDesign # Hot reload is disabled in AOT mode.
rewind_optimized_out_test: SkipByDesign # Debugger is disabled in AOT mode.
rewind_test: SkipByDesign # Debugger is disabled in AOT mode.
+set_breakpoint_state_test: SkipByDesign # Debugger is disabled in AOT mode.
set_library_debuggable_test: SkipByDesign # Debugger is disabled in AOT mode.
sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Platform.executable.
simple_reload_test: SkipByDesign # Hot reload is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart b/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart
new file mode 100644
index 0000000..78e0fcb
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/set_breakpoint_state_test.dart
@@ -0,0 +1,65 @@
+// 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.
+// VMOptions=--verbose_debug
+
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+
+import 'service_test_common.dart';
+import 'test_helper.dart';
+
+const int LINE_A = 17;
+const int LINE_B = LINE_A + 1;
+
+testMain() {
+ while (true) {
+ print('a'); // LINE_A
+ print('b'); // LINE_B
+ }
+}
+
+Breakpoint bpt;
+
+var tests = <IsolateTest>[
+ hasPausedAtStart,
+ (Isolate isolate) async {
+ bpt = await isolate.addBreakpointByScriptUri(
+ 'set_breakpoint_state_test.dart',
+ LINE_A,
+ );
+ expect(bpt.enabled, true);
+ },
+ setBreakpointAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (Isolate isolate) async {
+ await bpt.setState(
+ false,
+ );
+ expect(bpt.enabled, false);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_B),
+ (Isolate isolate) async {
+ await bpt.setState(
+ true,
+ );
+ expect(bpt.enabled, true);
+ },
+ resumeIsolate,
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+];
+
+main([args = const <String>[]]) => runIsolateTests(
+ args,
+ tests,
+ pause_on_start: true,
+ testeeConcurrent: testMain,
+ );
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 592a7e5..cfa5e5e 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -109,7 +109,14 @@
}
bool BreakpointLocation::AnyEnabled() const {
- return breakpoints() != NULL;
+ Breakpoint* bpt = breakpoints();
+ while (bpt != nullptr) {
+ if (bpt->is_enabled()) {
+ return true;
+ }
+ bpt = bpt->next();
+ }
+ return false;
}
void BreakpointLocation::SetResolved(const Function& func,
@@ -175,6 +182,7 @@
jsobj.AddProperty("type", "Breakpoint");
jsobj.AddFixedServiceId("breakpoints/%" Pd "", id());
+ jsobj.AddProperty("enabled", enabled_);
jsobj.AddProperty("breakpointNumber", id());
if (is_synthetic_async()) {
jsobj.AddProperty("isSyntheticAsyncContinuation", is_synthetic_async());
@@ -358,7 +366,7 @@
void BreakpointLocation::AddBreakpoint(Breakpoint* bpt, Debugger* dbg) {
bpt->set_next(breakpoints());
set_breakpoints(bpt);
-
+ bpt->Enable();
dbg->group_debugger()->SyncBreakpointLocation(this);
dbg->SendBreakpointEvent(ServiceEvent::kBreakpointAdded, bpt);
}
@@ -2945,7 +2953,6 @@
// associated with the breakpoint location loc.
void GroupDebugger::SyncBreakpointLocation(BreakpointLocation* loc) {
bool any_enabled = loc->AnyEnabled();
-
SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock());
CodeBreakpoint* cbpt = code_breakpoints_;
while (cbpt != NULL) {
@@ -4283,6 +4290,21 @@
return Code::null();
}
+bool Debugger::SetBreakpointState(Breakpoint* bpt, bool enable) {
+ SafepointWriteRwLocker sl(Thread::Current(),
+ group_debugger()->breakpoint_locations_lock());
+ if (bpt->is_enabled() != enable) {
+ if (FLAG_verbose_debug) {
+ OS::PrintErr("Setting breakpoint %" Pd " to state: %s\n", bpt->id(),
+ enable ? "enabled" : "disabled");
+ }
+ enable ? bpt->Enable() : bpt->Disable();
+ group_debugger()->SyncBreakpointLocation(bpt->bpt_location());
+ return true;
+ }
+ return false;
+}
+
// Remove and delete the source breakpoint bpt and its associated
// code breakpoints.
void Debugger::RemoveBreakpoint(intptr_t bp_id) {
diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h
index bd2d0de..b9d99de 100644
--- a/runtime/vm/debugger.h
+++ b/runtime/vm/debugger.h
@@ -81,6 +81,18 @@
closure_ = closure.ptr();
}
+ void Enable() {
+ ASSERT(!enabled_);
+ enabled_ = true;
+ }
+
+ void Disable() {
+ ASSERT(enabled_);
+ enabled_ = false;
+ }
+
+ bool is_enabled() const { return enabled_; }
+
// Mark that this breakpoint is a result of a step OverAwait request.
void set_is_synthetic_async(bool is_synthetic_async) {
is_synthetic_async_ = is_synthetic_async;
@@ -105,6 +117,7 @@
InstancePtr closure_;
BreakpointLocation* bpt_location_;
bool is_synthetic_async_;
+ bool enabled_ = false;
friend class BreakpointLocation;
DISALLOW_COPY_AND_ASSIGN(Breakpoint);
@@ -713,6 +726,9 @@
intptr_t line_number,
intptr_t column_number);
+ // Returns true if the breakpoint's state changed.
+ bool SetBreakpointState(Breakpoint* bpt, bool enable);
+
void RemoveBreakpoint(intptr_t bp_id);
Breakpoint* GetBreakpointById(intptr_t id);
diff --git a/runtime/vm/debugger_api_impl_test.cc b/runtime/vm/debugger_api_impl_test.cc
index 965764b..51ba8bd 100644
--- a/runtime/vm/debugger_api_impl_test.cc
+++ b/runtime/vm/debugger_api_impl_test.cc
@@ -149,7 +149,7 @@
UNWRAP_AND_CHECK_PARAM(String, script_url, script_url_in);
Debugger* debugger = I->debugger();
- bpt = debugger->SetBreakpointAtLine(script_url, line_number);
+ bpt = debugger->SetBreakpointAtLineCol(script_url, line_number, -1);
if (bpt == NULL) {
return Api::NewError("%s: could not set breakpoint at line %" Pd
" in '%s'",
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index ee7a7ec..4c5578f 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4805,6 +4805,36 @@
return true;
}
+static const MethodParameter* set_breakpoint_state_params[] = {
+ ISOLATE_PARAMETER,
+ new IdParameter("breakpointId", true),
+ new BoolParameter("enable", true),
+ nullptr,
+};
+
+static bool SetBreakpointState(Thread* thread, JSONStream* js) {
+ Isolate* isolate = thread->isolate();
+ const char* bpt_id = js->LookupParam("breakpointId");
+ bool enable = BoolParameter::Parse(js->LookupParam("enable"), true);
+ ObjectIdRing::LookupResult lookup_result;
+ Breakpoint* bpt = LookupBreakpoint(isolate, bpt_id, &lookup_result);
+ // TODO(bkonyi): Should we return a different error for bpts which
+ // have been already removed?
+ if (bpt == nullptr) {
+ PrintInvalidParamError(js, "breakpointId");
+ return true;
+ }
+ if (isolate->debugger()->SetBreakpointState(bpt, enable)) {
+ if (Service::debug_stream.enabled()) {
+ ServiceEvent event(isolate, ServiceEvent::kBreakpointUpdated);
+ event.set_breakpoint(bpt);
+ Service::HandleEvent(&event);
+ }
+ }
+ bpt->PrintJSON(js);
+ return true;
+}
+
static const MethodParameter* get_flag_list_params[] = {
NO_ISOLATE_PARAMETER,
NULL,
@@ -5188,6 +5218,8 @@
request_heap_snapshot_params },
{ "_evaluateCompiledExpression", EvaluateCompiledExpression,
evaluate_compiled_expression_params },
+ { "setBreakpointState", SetBreakpointState,
+ set_breakpoint_state_params },
{ "setExceptionPauseMode", SetExceptionPauseMode,
set_exception_pause_mode_params },
{ "setFlag", SetFlag,
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index 032fafd..0b94d3d 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -15,7 +15,7 @@
namespace dart {
#define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 44
+#define SERVICE_PROTOCOL_MINOR_VERSION 45
class Array;
class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index e78af3e..99bdab7 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.44
+# Dart VM Service Protocol 3.45
> Please post feedback to the [observatory-discuss group][discuss-list]
-This document describes of _version 3.44_ of the Dart VM Service Protocol. This
+This document describes of _version 3.45_ of the Dart VM Service Protocol. This
protocol is used to communicate with a running Dart Virtual Machine.
To use the Service Protocol, start the VM with the *--observe* flag.
@@ -67,6 +67,7 @@
- [reloadSources](#reloadsources)
- [removeBreakpoint](#removebreakpoint)
- [resume](#resume)
+ - [setBreakpointState](#setbreakpointstate)
- [setExceptionPauseMode](#setexceptionpausemode)
- [setFlag](#setflag)
- [setLibraryDebuggable](#setlibrarydebuggable)
@@ -1305,6 +1306,24 @@
See [Success](#success), [StepOption](#StepOption).
+### setBreakpointState
+
+```
+Breakpoint setBreakpointState(string isolateId,
+ string breakpointId,
+ bool enable)
+```
+
+The _setBreakpointState_ RPC allows for breakpoints to be enabled or disabled,
+without requiring for the breakpoint to be completely removed.
+
+If _isolateId_ refers to an isolate which has exited, then the
+_Collected_ [Sentinel](#sentinel) is returned.
+
+The returned [Breakpoint](#breakpoint) is the updated breakpoint with its new
+values.
+
+See [Breakpoint](#breakpoint).
### setExceptionPauseMode
```
@@ -1457,7 +1476,7 @@
-------- | -----------
VM | VMUpdate, VMFlagUpdate
Isolate | IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, IsolateReload, ServiceExtensionAdded
-Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect, None
+Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
GC | GC
Extension | Extension
Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -1639,6 +1658,9 @@
// A number identifying this breakpoint to the user.
int breakpointNumber;
+ // Is this breakpoint enabled?
+ bool enabled;
+
// Has this breakpoint been assigned to a specific program location?
bool resolved;
@@ -2002,6 +2024,7 @@
// BreakpointAdded
// BreakpointRemoved
// BreakpointResolved
+ // BreakpointUpdated
Breakpoint breakpoint [optional];
// The list of breakpoints at which we are currently paused
@@ -2199,6 +2222,9 @@
// A breakpoint has been removed.
BreakpointRemoved,
+ // A breakpoint has been updated.
+ BreakpointUpdated,
+
// A garbage collection event.
GC,
@@ -4011,5 +4037,6 @@
3.42 | Added `limit` optional parameter to `getStack` RPC.
3.43 | Updated heap snapshot format to include identity hash codes. Added `getAllocationTraces` and `setTraceClassAllocation` RPCs, updated `CpuSample` to include `identityHashCode` and `classId` properties, updated `Class` to include `traceAllocations` property.
3.44 | Added `identityHashCode` property to `@Instance` and `Instance`.
+3.45 | Added `setBreakpointState` RPC and `BreakpointUpdated` event kind.
[discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc
index aed7093..6b97b6d 100644
--- a/runtime/vm/service_event.cc
+++ b/runtime/vm/service_event.cc
@@ -117,6 +117,8 @@
return "BreakpointResolved";
case kBreakpointRemoved:
return "BreakpointRemoved";
+ case kBreakpointUpdated:
+ return "BreakpointUpdated";
case kGC:
return "GC"; // TODO(koda): Change to GarbageCollected.
case kInspect:
@@ -166,6 +168,7 @@
case kBreakpointAdded:
case kBreakpointResolved:
case kBreakpointRemoved:
+ case kBreakpointUpdated:
case kInspect:
case kDebuggerSettingsUpdate:
return &Service::debug_stream;
diff --git a/runtime/vm/service_event.h b/runtime/vm/service_event.h
index 08979ac..80aaa8a 100644
--- a/runtime/vm/service_event.h
+++ b/runtime/vm/service_event.h
@@ -44,6 +44,7 @@
kBreakpointAdded,
kBreakpointResolved,
kBreakpointRemoved,
+ kBreakpointUpdated,
kInspect,
kDebuggerSettingsUpdate,
@@ -128,7 +129,8 @@
Breakpoint* breakpoint() const { return breakpoint_; }
void set_breakpoint(Breakpoint* bpt) {
ASSERT(kind() == kPauseBreakpoint || kind() == kBreakpointAdded ||
- kind() == kBreakpointResolved || kind() == kBreakpointRemoved);
+ kind() == kBreakpointResolved || kind() == kBreakpointRemoved ||
+ kind() == kBreakpointUpdated);
breakpoint_ = bpt;
}