Skip to content

Commit bb22b3a

Browse files
author
Anna Gringauze
authored
Make debugger skip same locations in dart during stepping. (#2043)
* Temp * Add tests and cleanup * Update changelog and disable new tests for all versions before current main * Update changelog with pull request reference * Fix test failures * Update patterns tests to skip correct sdk versions
1 parent b949981 commit bb22b3a

File tree

7 files changed

+237
-33
lines changed

7 files changed

+237
-33
lines changed

dwds/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 18.0.2-dev
22

33
- Support new DDC temp names for patterns. - [#2042](https://github.com/dart-lang/webdev/pull/2042)
4+
- Make debugger find next dart location when stepping. -[#2043](https://github.com/dart-lang/webdev/pull/2043)
45

56
## 18.0.1
67

dwds/lib/src/debugging/debugger.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Debugger extends Domain {
8383
FrameComputer? stackComputer;
8484

8585
bool _isStepping = false;
86+
DartLocation? _previousSteppingLocation;
8687

8788
void updateInspector(AppInspectorInterface appInspector) {
8889
inspector = appInspector;
@@ -143,6 +144,7 @@ class Debugger extends Domain {
143144
}
144145
} else {
145146
_isStepping = false;
147+
_previousSteppingLocation = null;
146148
result = await _remoteDebugger.resume();
147149
}
148150
handleErrorIfPresent(result);
@@ -354,7 +356,12 @@ class Debugger extends Domain {
354356
final url = urlForScriptId(scriptId);
355357
if (url == null) return null;
356358

357-
return _locations.locationForJs(url, line, column);
359+
final loc = await _locations.locationForJs(url, line, column);
360+
if (loc == null || loc.dartLocation == _previousSteppingLocation) {
361+
return null;
362+
}
363+
_previousSteppingLocation = loc.dartLocation;
364+
return loc;
358365
}
359366

360367
/// Returns script ID for the paused event.

dwds/lib/src/debugging/location.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ class DartLocation {
7474
return result == 0 ? column.compareTo(otherColumn) : result;
7575
}
7676

77+
@override
78+
int get hashCode => Object.hashAll([uri, line, column]);
79+
80+
@override
81+
bool operator ==(Object? other) {
82+
if (other is! DartLocation) {
83+
return false;
84+
}
85+
return uri.serverPath == other.uri.serverPath &&
86+
line == other.line &&
87+
column == other.column;
88+
}
89+
7790
@override
7891
String toString() => '[${uri.serverPath}:$line:$column]';
7992

dwds/test/instances/instance_inspection_common.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ class TestInspector {
119119
expect(result, isA<Instance>());
120120
return result as Instance;
121121
}
122+
123+
Future<Map<String?, Instance?>> getFrameVariables(
124+
String isolateId, Frame frame) async {
125+
final refs = <String, InstanceRef>{
126+
for (var variable in frame.vars!)
127+
variable.name!: variable.value as InstanceRef
128+
};
129+
final instances = <String, Instance>{};
130+
for (final p in refs.entries) {
131+
instances[p.key] =
132+
await service.getObject(isolateId, p.value.id!) as Instance;
133+
}
134+
return instances;
135+
}
122136
}
123137

124138
Map<String, InstanceRef> _associationsToMap(
@@ -179,7 +193,7 @@ Object? _getValue(InstanceRef instanceRef) {
179193
return instanceRef.valueAsString == 'true';
180194
case InstanceKind.kDouble:
181195
case InstanceKind.kInt:
182-
return int.parse(instanceRef.valueAsString!);
196+
return double.parse(instanceRef.valueAsString!);
183197
case InstanceKind.kString:
184198
return instanceRef.valueAsString;
185199
default:
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
@Timeout(Duration(minutes: 2))
7+
8+
import 'dart:io';
9+
10+
import 'package:pub_semver/pub_semver.dart' as semver;
11+
import 'package:test/test.dart';
12+
import 'package:test_common/logging.dart';
13+
import 'package:test_common/test_sdk_configuration.dart';
14+
import 'package:vm_service/vm_service.dart';
15+
16+
import '../fixtures/context.dart';
17+
import '../fixtures/project.dart';
18+
import 'instance_inspection_common.dart';
19+
20+
void main() async {
21+
// Enable verbose logging for debugging.
22+
final debug = false;
23+
24+
final provider = TestSdkConfigurationProvider(verbose: debug);
25+
tearDownAll(provider.dispose);
26+
27+
for (var compilationMode in CompilationMode.values) {
28+
await _runTests(
29+
provider: provider,
30+
compilationMode: compilationMode,
31+
debug: debug,
32+
);
33+
}
34+
}
35+
36+
Future<void> _runTests({
37+
required TestSdkConfigurationProvider provider,
38+
required CompilationMode compilationMode,
39+
required bool debug,
40+
}) async {
41+
final context =
42+
TestContext(TestProject.testExperimentWithSoundNullSafety, provider);
43+
final testInspector = TestInspector(context);
44+
45+
late VmServiceInterface service;
46+
late Stream<Event> stream;
47+
late String isolateId;
48+
late ScriptRef mainScript;
49+
50+
onBreakPoint(breakPointId, body) => testInspector.onBreakPoint(
51+
stream, isolateId, mainScript, breakPointId, body);
52+
53+
getInstanceRef(frame, expression) =>
54+
testInspector.getInstanceRef(isolateId, frame, expression);
55+
56+
getFields(instanceRef, {offset, count}) => testInspector
57+
.getFields(isolateId, instanceRef, offset: offset, count: count);
58+
59+
getFrameVariables(Frame frame) =>
60+
testInspector.getFrameVariables(isolateId, frame);
61+
62+
group('$compilationMode |', () {
63+
setUpAll(() async {
64+
setCurrentLogWriter(debug: debug);
65+
await context.setUp(
66+
compilationMode: compilationMode,
67+
enableExpressionEvaluation: true,
68+
verboseCompiler: debug,
69+
experiments: ['records', 'patterns'],
70+
);
71+
service = context.debugConnection.vmService;
72+
73+
final vm = await service.getVM();
74+
isolateId = vm.isolates!.first.id!;
75+
final scripts = await service.getScripts(isolateId);
76+
77+
await service.streamListen('Debug');
78+
stream = service.onEvent('Debug');
79+
80+
mainScript = scripts.scripts!
81+
.firstWhere((each) => each.uri!.contains('main.dart'));
82+
});
83+
84+
tearDownAll(() async {
85+
await context.tearDown();
86+
});
87+
88+
setUp(() => setCurrentLogWriter(debug: debug));
89+
tearDown(() => service.resume(isolateId));
90+
91+
test('pattern match case 1', () async {
92+
await onBreakPoint('testPatternCase1', (event) async {
93+
final frame = event.topFrame!;
94+
95+
expect(await getFrameVariables(frame), {
96+
'obj': matchListInstance(type: 'List<Object>'),
97+
'a': matchPrimitiveInstance(kind: InstanceKind.kString, value: 'a'),
98+
'n': matchPrimitiveInstance(kind: InstanceKind.kDouble, value: 1),
99+
});
100+
});
101+
});
102+
103+
test('pattern match case 2', () async {
104+
await onBreakPoint('testPatternCase2', (event) async {
105+
final frame = event.topFrame!;
106+
107+
expect(await getFrameVariables(frame), {
108+
'obj': matchListInstance(type: 'List<Object>'),
109+
'a': matchPrimitiveInstance(kind: InstanceKind.kString, value: 'b'),
110+
'n': matchPrimitiveInstance(kind: InstanceKind.kDouble, value: 3.14),
111+
});
112+
});
113+
});
114+
115+
test('pattern match default case', () async {
116+
await onBreakPoint('testPatternDefault', (event) async {
117+
final frame = event.topFrame!;
118+
final frameIndex = frame.index!;
119+
final instanceRef = await getInstanceRef(frameIndex, 'obj');
120+
expect(await getFields(instanceRef), [0, 1]);
121+
122+
expect(await getFrameVariables(frame), {
123+
'obj': matchListInstance(type: 'List<int>'),
124+
});
125+
});
126+
});
127+
128+
test('stepping through pattern match', () async {
129+
await onBreakPoint('callTestPattern1', (Event event) async {
130+
var previousLocation = event.topFrame!.location;
131+
for (var step in [
132+
// Make sure we step into the callee.
133+
for (var i = 0; i < 4; i++) 'Into',
134+
// Make a few steps inside the callee.
135+
for (var i = 0; i < 4; i++) 'Over',
136+
]) {
137+
await service.resume(isolateId, step: step);
138+
139+
event = await stream
140+
.firstWhere((e) => e.kind == EventKind.kPauseInterrupted);
141+
142+
if (step == 'Over') {
143+
expect(event.topFrame!.code!.name, 'testPattern');
144+
}
145+
146+
final location = event.topFrame!.location;
147+
expect(location, isNot(equals(previousLocation)));
148+
previousLocation = location;
149+
}
150+
});
151+
});
152+
}, // TODO(annagrin): Remove when dart 3.0 is stable.
153+
skip: semver.Version.parse(Platform.version.split(' ')[0]) <
154+
semver.Version.parse('3.0.0-351.0.dev'));
155+
}

dwds/test/instances/record_inspection_test.dart

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Future<void> _runTests({
6565
compilationMode: compilationMode,
6666
enableExpressionEvaluation: true,
6767
verboseCompiler: debug,
68-
experiments: ['records'],
68+
experiments: ['records', 'patterns'],
6969
);
7070
service = context.debugConnection.vmService;
7171

@@ -88,7 +88,7 @@ Future<void> _runTests({
8888
tearDown(() => service.resume(isolateId));
8989

9090
test('simple records', () async {
91-
await onBreakPoint('printSimpleLocal', (event) async {
91+
await onBreakPoint('printSimpleLocalRecord', (event) async {
9292
final frame = event.topFrame!.index!;
9393
final instanceRef = await getInstanceRef(frame, 'record');
9494

@@ -111,7 +111,7 @@ Future<void> _runTests({
111111
});
112112

113113
test('simple records, field access', () async {
114-
await onBreakPoint('printSimpleLocal', (event) async {
114+
await onBreakPoint('printSimpleLocalRecord', (event) async {
115115
final frame = event.topFrame!.index!;
116116
expect(await getInstance(frame, r'record.$1'),
117117
matchPrimitiveInstance(kind: InstanceKind.kBool, value: true));
@@ -122,7 +122,7 @@ Future<void> _runTests({
122122
});
123123

124124
test('simple records with named fields', () async {
125-
await onBreakPoint('printSimpleNamedLocal', (event) async {
125+
await onBreakPoint('printSimpleNamedLocalRecord', (event) async {
126126
final frame = event.topFrame!.index!;
127127
final instanceRef = await getInstanceRef(frame, 'record');
128128

@@ -149,7 +149,7 @@ Future<void> _runTests({
149149
});
150150

151151
test('simple records with named fields, field access', () async {
152-
await onBreakPoint('printSimpleNamedLocal', (event) async {
152+
await onBreakPoint('printSimpleNamedLocalRecord', (event) async {
153153
final frame = event.topFrame!.index!;
154154
expect(await getInstance(frame, r'record.$1'),
155155
matchPrimitiveInstance(kind: InstanceKind.kBool, value: true));
@@ -160,7 +160,7 @@ Future<void> _runTests({
160160
});
161161

162162
test('complex records fields', () async {
163-
await onBreakPoint('printComplexLocal', (event) async {
163+
await onBreakPoint('printComplexLocalRecord', (event) async {
164164
final frame = event.topFrame!.index!;
165165
final instanceRef = await getInstanceRef(frame, 'record');
166166

@@ -208,7 +208,7 @@ Future<void> _runTests({
208208
});
209209

210210
test('complex records, field access', () async {
211-
await onBreakPoint('printComplexLocal', (event) async {
211+
await onBreakPoint('printComplexLocalRecord', (event) async {
212212
final frame = event.topFrame!.index!;
213213
expect(await getInstance(frame, r'record.$1'),
214214
matchPrimitiveInstance(kind: InstanceKind.kBool, value: true));
@@ -223,7 +223,7 @@ Future<void> _runTests({
223223
});
224224

225225
test('complex records with named fields', () async {
226-
await onBreakPoint('printComplexNamedLocal', (event) async {
226+
await onBreakPoint('printComplexNamedLocalRecord', (event) async {
227227
final frame = event.topFrame!.index!;
228228
final instanceRef = await getInstanceRef(frame, 'record');
229229

@@ -272,7 +272,7 @@ Future<void> _runTests({
272272
});
273273

274274
test('complex records with named fields, field access', () async {
275-
await onBreakPoint('printComplexNamedLocal', (event) async {
275+
await onBreakPoint('printComplexNamedLocalRecord', (event) async {
276276
final frame = event.topFrame!.index!;
277277
expect(await getInstance(frame, r'record.$1'),
278278
matchPrimitiveInstance(kind: InstanceKind.kBool, value: true));
@@ -287,7 +287,7 @@ Future<void> _runTests({
287287
});
288288

289289
test('nested records', () async {
290-
await onBreakPoint('printNestedLocal', (event) async {
290+
await onBreakPoint('printNestedLocalRecord', (event) async {
291291
final frame = event.topFrame!.index!;
292292
final instanceRef = await getInstanceRef(frame, 'record');
293293

@@ -324,7 +324,7 @@ Future<void> _runTests({
324324
});
325325

326326
test('nested records, field access', () async {
327-
await onBreakPoint('printNestedLocal', (event) async {
327+
await onBreakPoint('printNestedLocalRecord', (event) async {
328328
final frame = event.topFrame!.index!;
329329
final instanceRef = await getInstanceRef(frame, r'record.$2');
330330

@@ -338,7 +338,7 @@ Future<void> _runTests({
338338
});
339339

340340
test('nested records with named fields,', () async {
341-
await onBreakPoint('printNestedNamedLocal', (event) async {
341+
await onBreakPoint('printNestedNamedLocalRecord', (event) async {
342342
final frame = event.topFrame!.index!;
343343
final instanceRef = await getInstanceRef(frame, 'record');
344344

@@ -382,7 +382,7 @@ Future<void> _runTests({
382382
});
383383

384384
test('nested records with named fields, field access', () async {
385-
await onBreakPoint('printNestedNamedLocal', (event) async {
385+
await onBreakPoint('printNestedNamedLocalRecord', (event) async {
386386
final frame = event.topFrame!.index!;
387387
final instanceRef = await getInstanceRef(frame, r'record.inner');
388388

0 commit comments

Comments
 (0)