Skip to content

Commit 0591fa8

Browse files
author
Anna Gringauze
authored
Add columns to breakpoints (#1560)
* Add columns to breakpoints * Added tests and updated version * Remove debug printing * Removed adjusting JS locations from 0-based to 1-based and back * Add debug information on failure to set breakpoints * Update tests * Update skiplist tests
1 parent 17a84a2 commit 0591fa8

File tree

11 files changed

+142
-44
lines changed

11 files changed

+142
-44
lines changed

dwds/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 13.1.1-dev
2+
- Add column information to breakpoints to allow precise breakpoint placement.
3+
14
## 13.1.0
25
- Update _fe_analyzer_shared to version ^38.0.0.
36

dwds/lib/src/debugging/debugger.dart

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,9 @@ class Debugger extends Domain {
228228
int line, {
229229
int column,
230230
}) async {
231+
column ??= 0;
231232
checkIsolate('addBreakpoint', isolateId);
232-
final breakpoint = await _breakpoints.add(scriptId, line);
233+
final breakpoint = await _breakpoints.add(scriptId, line, column);
233234
_notifyBreakpoint(breakpoint);
234235
return breakpoint;
235236
}
@@ -249,7 +250,9 @@ class Debugger extends Domain {
249250
for (var breakpoint in previousBreakpoints) {
250251
var scriptRef = await _updatedScriptRefFor(breakpoint);
251252
var updatedLocation = await _locations.locationForDart(
252-
DartUri(scriptRef.uri, _root), _lineNumberFor(breakpoint));
253+
DartUri(scriptRef.uri, _root),
254+
_lineNumberFor(breakpoint),
255+
_columnNumberFor(breakpoint));
253256
var updatedBreakpoint = _breakpoints._dartBreakpoint(
254257
scriptRef, updatedLocation, breakpoint.id);
255258
_breakpoints._note(
@@ -263,7 +266,8 @@ class Debugger extends Domain {
263266
await addBreakpoint(
264267
inspector.isolate.id,
265268
(await _updatedScriptRefFor(breakpoint)).id,
266-
_lineNumberFor(breakpoint));
269+
_lineNumberFor(breakpoint),
270+
column: _columnNumberFor(breakpoint));
267271
}
268272
}
269273

@@ -324,10 +328,11 @@ class Debugger extends Domain {
324328
var frame = e.params['callFrames'][0];
325329
var location = frame['location'];
326330
var scriptId = location['scriptId'] as String;
327-
var lineNumber = location['lineNumber'] as int;
331+
var line = location['lineNumber'] as int;
332+
var column = location['columnNumber'] as int;
328333

329334
var url = _urlForScriptId(scriptId);
330-
return _locations.locationForJs(url, lineNumber + 1);
335+
return _locations.locationForJs(url, line, column);
331336
}
332337

333338
/// The variables visible in a frame in Dart protocol [BoundVariable] form.
@@ -459,9 +464,8 @@ class Debugger extends Domain {
459464
bool populateVariables = true,
460465
}) async {
461466
var location = frame.location;
462-
// Chrome is 0 based. Account for this.
463-
var line = location.lineNumber + 1;
464-
var column = location.columnNumber + 1;
467+
var line = location.lineNumber;
468+
var column = location.columnNumber;
465469

466470
var url = _urlForScriptId(location.scriptId);
467471
if (url == null) {
@@ -723,14 +727,20 @@ bool isNativeJsObject(InstanceRef instanceRef) {
723727

724728
/// Returns the Dart line number for the provided breakpoint.
725729
int _lineNumberFor(Breakpoint breakpoint) =>
726-
int.parse(breakpoint.id.split('#').last);
730+
int.parse(breakpoint.id.split('#').last.split(':').first);
731+
732+
/// Returns the Dart column number for the provided breakpoint.
733+
int _columnNumberFor(Breakpoint breakpoint) =>
734+
int.parse(breakpoint.id.split('#').last.split(':').last);
727735

728736
/// Returns the breakpoint ID for the provided Dart script ID and Dart line
729737
/// number.
730-
String breakpointIdFor(String scriptId, int line) => 'bp/$scriptId#$line';
738+
String breakpointIdFor(String scriptId, int line, int column) =>
739+
'bp/$scriptId#$line:$column';
731740

732741
/// Keeps track of the Dart and JS breakpoint Ids that correspond.
733742
class _Breakpoints extends Domain {
743+
final logger = Logger('Breakpoints');
734744
final _dartIdByJsId = <String, String>{};
735745
final _jsIdByDartId = <String, String>{};
736746

@@ -752,13 +762,16 @@ class _Breakpoints extends Domain {
752762
}) : super(provider);
753763

754764
Future<Breakpoint> _createBreakpoint(
755-
String id, String scriptId, int line) async {
765+
String id, String scriptId, int line, int column) async {
756766
var dartScript = inspector.scriptWithId(scriptId);
757767
var dartUri = DartUri(dartScript.uri, root);
758-
var location = await locations.locationForDart(dartUri, line);
759-
768+
var location = await locations.locationForDart(dartUri, line, column);
760769
// TODO: Handle cases where a breakpoint can't be set exactly at that line.
761770
if (location == null) {
771+
logger
772+
.fine('Failed to set breakpoint at ${dartScript.uri}:$line:$column: '
773+
'Dart location not found for scriptId: $scriptId, '
774+
'server path: ${dartUri.serverPath}, root:$root');
762775
throw RPCError(
763776
'addBreakpoint',
764777
102,
@@ -779,10 +792,10 @@ class _Breakpoints extends Domain {
779792

780793
/// Adds a breakpoint at [scriptId] and [line] or returns an existing one if
781794
/// present.
782-
Future<Breakpoint> add(String scriptId, int line) async {
783-
final id = breakpointIdFor(scriptId, line);
795+
Future<Breakpoint> add(String scriptId, int line, int column) async {
796+
final id = breakpointIdFor(scriptId, line, column);
784797
return _bpByDartId.putIfAbsent(
785-
id, () => _createBreakpoint(id, scriptId, line));
798+
id, () => _createBreakpoint(id, scriptId, line, column));
786799
}
787800

788801
/// Create a Dart breakpoint at [location] in [dartScript] with [id].
@@ -792,17 +805,19 @@ class _Breakpoints extends Domain {
792805
id: id,
793806
breakpointNumber: int.parse(createId()),
794807
resolved: true,
795-
location: SourceLocation(script: dartScript, tokenPos: location.tokenPos),
808+
location: SourceLocation(
809+
script: dartScript,
810+
tokenPos: location.tokenPos,
811+
line: location.dartLocation.line,
812+
column: location.dartLocation.column,
813+
),
796814
enabled: true,
797815
)..id = id;
798816
return breakpoint;
799817
}
800818

801819
/// Calls the Chrome protocol setBreakpoint and returns the remote ID.
802820
Future<String> _setJsBreakpoint(Location location) async {
803-
// Location is 0 based according to:
804-
// https://chromedevtools.github.io/devtools-protocol/tot/Debugger#type-Location
805-
806821
// The module can be loaded from a nested path and contain an ETAG suffix.
807822
var urlRegex = '.*${location.jsLocation.module}.*';
808823
// Prevent `Aww, snap!` errors when setting multiple breakpoints
@@ -811,7 +826,8 @@ class _Breakpoints extends Domain {
811826
var response = await remoteDebugger
812827
.sendCommand('Debugger.setBreakpointByUrl', params: {
813828
'urlRegex': urlRegex,
814-
'lineNumber': location.jsLocation.line - 1,
829+
'lineNumber': location.jsLocation.line,
830+
'columnNumber': location.jsLocation.column,
815831
});
816832
return response.result['breakpointId'] as String;
817833
});

dwds/lib/src/debugging/location.dart

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Location {
4040
var dartColumn = entry.sourceColumn;
4141
var jsLine = lineEntry.line;
4242
var jsColumn = entry.column;
43+
4344
// lineEntry data is 0 based according to:
4445
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
4546
return Location._(
@@ -71,21 +72,18 @@ class DartLocation {
7172
@override
7273
String toString() => '[${uri.serverPath}:$line:$column]';
7374

74-
static DartLocation fromZeroBased(DartUri uri, int line, int column) =>
75+
factory DartLocation.fromZeroBased(DartUri uri, int line, int column) =>
7576
DartLocation._(uri, line + 1, column + 1);
76-
77-
static DartLocation fromOneBased(DartUri uri, int line, int column) =>
78-
DartLocation._(uri, line, column);
7977
}
8078

8179
/// Location information for a JS source.
8280
class JsLocation {
8381
final String module;
8482

85-
/// 1 based row offset within the JS source code.
83+
/// 0 based row offset within the JS source code.
8684
final int line;
8785

88-
/// 1 based column offset within the JS source code.
86+
/// 0 based column offset within the JS source code.
8987
final int column;
9088

9189
JsLocation._(
@@ -97,10 +95,9 @@ class JsLocation {
9795
@override
9896
String toString() => '[$module:$line:$column]';
9997

100-
static JsLocation fromZeroBased(String module, int line, int column) =>
101-
JsLocation._(module, line + 1, column + 1);
102-
103-
static JsLocation fromOneBased(String module, int line, int column) =>
98+
// JS Location is 0 based according to:
99+
// https://chromedevtools.github.io/devtools-protocol/tot/Debugger#type-Location
100+
factory JsLocation.fromZeroBased(String module, int line, int column) =>
104101
JsLocation._(module, line, column);
105102
}
106103

@@ -155,17 +152,21 @@ class Locations {
155152
/// Find the [Location] for the given Dart source position.
156153
///
157154
/// The [line] number is 1-based.
158-
Future<Location> locationForDart(DartUri uri, int line) async =>
155+
Future<Location> locationForDart(DartUri uri, int line, int column) async =>
159156
(await locationsForDart(uri.serverPath)).firstWhere(
160-
(location) => location.dartLocation.line == line,
157+
(location) =>
158+
location.dartLocation.line == line &&
159+
location.dartLocation.column >= column,
161160
orElse: () => null);
162161

163162
/// Find the [Location] for the given JS source position.
164163
///
165-
/// The [line] number is 1-based.
166-
Future<Location> locationForJs(String url, int line) async =>
164+
/// The [line] number is 0-based.
165+
Future<Location> locationForJs(String url, int line, int column) async =>
167166
(await locationsForUrl(url)).firstWhere(
168-
(location) => location.jsLocation.line == line,
167+
(location) =>
168+
location.jsLocation.line == line &&
169+
location.jsLocation.column >= column,
169170
orElse: () => null);
170171

171172
/// Returns the tokenPosTable for the provided Dart script path as defined

dwds/lib/src/debugging/skip_list.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ class SkipLists {
3131
var startColumn = 0;
3232
for (var location in locations) {
3333
// Account for 1 based.
34-
var endLine = location.jsLocation.line - 1;
35-
var endColumn = location.jsLocation.column - 1;
34+
var endLine = location.jsLocation.line;
35+
var endColumn = location.jsLocation.column;
3636
// Stop before the known location.
3737
endColumn -= 1;
3838
if (endColumn < 0) {
@@ -42,8 +42,8 @@ class SkipLists {
4242
if (endLine > startLine || endColumn > startColumn) {
4343
ranges.add(
4444
_rangeFor(scriptId, startLine, startColumn, endLine, endColumn));
45-
startLine = location.jsLocation.line - 1;
46-
startColumn = location.jsLocation.column;
45+
startLine = location.jsLocation.line;
46+
startColumn = location.jsLocation.column + 1;
4747
}
4848
}
4949
ranges.add(_rangeFor(scriptId, startLine, startColumn, maxValue, maxValue));

dwds/lib/src/services/expression_evaluator.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ class ExpressionEvaluator {
159159
}
160160

161161
var functionName = jsFrame.functionName;
162-
var jsLine = jsFrame.location.lineNumber + 1;
162+
var jsLine = jsFrame.location.lineNumber;
163163
var jsScriptId = jsFrame.location.scriptId;
164+
var jsColumn = jsFrame.location.columnNumber;
164165
var jsScope = await _collectLocalJsScope(jsFrame);
165166

166167
// Find corresponding dart location and scope.
@@ -171,7 +172,7 @@ class ExpressionEvaluator {
171172
// cases. Invent location matching strategy for those cases.
172173
// [issue 890](https://github.com/dart-lang/webdev/issues/890)
173174
var url = _urlForScriptId(jsScriptId);
174-
var locationMap = await _locations.locationForJs(url, jsLine);
175+
var locationMap = await _locations.locationForJs(url, jsLine, jsColumn);
175176
if (locationMap == null) {
176177
return _createError(
177178
ErrorKind.internal,

dwds/lib/src/version.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dwds/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: dwds
22
# Every time this changes you need to run `dart run build_runner build`.
3-
version: 13.1.0
3+
version: 13.1.1-dev
44
homepage: https://github.com/dart-lang/webdev/tree/master/dwds
55
description: >-
66
A service that proxies between the Chrome debug protocol and the Dart VM

dwds/test/build_daemon_breakpoint_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,28 @@ void main() {
7474
await service.removeBreakpoint(isolate.id, bp.id);
7575
});
7676

77+
test('set breakpoint inside a JavaScript line succeeds on', () async {
78+
var line = await context.findBreakpointLine(
79+
'printNestedObjectMultiLine', isolate.id, mainScript);
80+
var column = 0;
81+
var bp = await service.addBreakpointWithScriptUri(
82+
isolate.id, mainScript.uri, line,
83+
column: column);
84+
await stream.firstWhere(
85+
(Event event) => event.kind == EventKind.kPauseBreakpoint);
86+
87+
expect(bp, isNotNull);
88+
expect(
89+
bp.location,
90+
isA<SourceLocation>()
91+
.having((loc) => loc.script, 'script', equals(mainScript))
92+
.having((loc) => loc.line, 'line', equals(line))
93+
.having((loc) => loc.column, 'column', greaterThan(column)));
94+
95+
// Remove breakpoint so it doesn't impact other tests.
96+
await service.removeBreakpoint(isolate.id, bp.id);
97+
});
98+
7799
test('set breakpoint again', () async {
78100
var line = await context.findBreakpointLine(
79101
'printLocal', isolate.id, mainScript);

dwds/test/frontend_server_breakpoint_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,29 @@ void main() {
104104
// Remove breakpoint so it doesn't impact other tests.
105105
await service.removeBreakpoint(isolate.id, bp.id);
106106
});
107+
108+
test('set breakpoint inside a JavaScript line succeeds', () async {
109+
var line = await context.findBreakpointLine(
110+
'printNestedObjectMultiLine', isolate.id, mainScript);
111+
var column = 0;
112+
var bp = await service.addBreakpointWithScriptUri(
113+
isolate.id, mainScript.uri, line,
114+
column: column);
115+
116+
await stream.firstWhere(
117+
(Event event) => event.kind == EventKind.kPauseBreakpoint);
118+
119+
expect(bp, isNotNull);
120+
expect(
121+
bp.location,
122+
isA<SourceLocation>()
123+
.having((loc) => loc.script, 'script', equals(mainScript))
124+
.having((loc) => loc.line, 'line', equals(line))
125+
.having((loc) => loc.column, 'column', greaterThan(column)));
126+
127+
// Remove breakpoint so it doesn't impact other tests.
128+
await service.removeBreakpoint(isolate.id, bp.id);
129+
});
107130
});
108131
});
109132
}

fixtures/_testPackage/web/main.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ void main() {
4040
printFromTestPackage();
4141
printCallExtension();
4242
printLoopVariable();
43+
printNestedObjectsMultiLine();
4344
});
4445

4546
document.body.appendText(concatenate('Program', ' is running!'));
@@ -99,10 +100,25 @@ Future<void> printDeferred() async {
99100
d.deferredPrintLocal();
100101
}
101102

103+
void printNestedObjectsMultiLine() {
104+
print(// Breakpoint: printEnclosingFunctionMultiLine
105+
EnclosingMainClass(// Breakpoint: printEnclosingObjectMultiLine
106+
MainClass(0) // Breakpoint: printNestedObjectMultiLine
107+
));
108+
}
109+
102110
class MainClass {
103111
final int _field;
104112
MainClass(this._field);
105113

106114
@override
107115
String toString() => '$_field';
108116
}
117+
118+
class EnclosingMainClass {
119+
final MainClass _field;
120+
EnclosingMainClass(this._field);
121+
122+
@override
123+
String toString() => '$_field';
124+
}

0 commit comments

Comments
 (0)