Skip to content

Make debugger skip same locations in dart during stepping. #2043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dwds/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 18.0.2-dev

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

## 18.0.1

Expand Down
9 changes: 8 additions & 1 deletion dwds/lib/src/debugging/debugger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Debugger extends Domain {
FrameComputer? stackComputer;

bool _isStepping = false;
DartLocation? _previousSteppingLocation;

void updateInspector(AppInspectorInterface appInspector) {
inspector = appInspector;
Expand Down Expand Up @@ -143,6 +144,7 @@ class Debugger extends Domain {
}
} else {
_isStepping = false;
_previousSteppingLocation = null;
result = await _remoteDebugger.resume();
}
handleErrorIfPresent(result);
Expand Down Expand Up @@ -354,7 +356,12 @@ class Debugger extends Domain {
final url = urlForScriptId(scriptId);
if (url == null) return null;

return _locations.locationForJs(url, line, column);
final loc = await _locations.locationForJs(url, line, column);
if (loc == null || loc.dartLocation == _previousSteppingLocation) {
return null;
}
_previousSteppingLocation = loc.dartLocation;
return loc;
}

/// Returns script ID for the paused event.
Expand Down
13 changes: 13 additions & 0 deletions dwds/lib/src/debugging/location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ class DartLocation {
return result == 0 ? column.compareTo(otherColumn) : result;
}

@override
int get hashCode => Object.hashAll([uri, line, column]);

@override
bool operator ==(Object? other) {
if (other is! DartLocation) {
return false;
}
return uri.serverPath == other.uri.serverPath &&
line == other.line &&
column == other.column;
}

@override
String toString() => '[${uri.serverPath}:$line:$column]';

Expand Down
16 changes: 15 additions & 1 deletion dwds/test/instances/instance_inspection_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ class TestInspector {
expect(result, isA<Instance>());
return result as Instance;
}

Future<Map<String?, Instance?>> getFrameVariables(
String isolateId, Frame frame) async {
final refs = <String, InstanceRef>{
for (var variable in frame.vars!)
variable.name!: variable.value as InstanceRef
};
final instances = <String, Instance>{};
for (final p in refs.entries) {
instances[p.key] =
await service.getObject(isolateId, p.value.id!) as Instance;
}
return instances;
}
}

Map<String, InstanceRef> _associationsToMap(
Expand Down Expand Up @@ -179,7 +193,7 @@ Object? _getValue(InstanceRef instanceRef) {
return instanceRef.valueAsString == 'true';
case InstanceKind.kDouble:
case InstanceKind.kInt:
return int.parse(instanceRef.valueAsString!);
return double.parse(instanceRef.valueAsString!);
case InstanceKind.kString:
return instanceRef.valueAsString;
default:
Expand Down
155 changes: 155 additions & 0 deletions dwds/test/instances/patterns_inspection_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) 2023, 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.

@TestOn('vm')
@Timeout(Duration(minutes: 2))

import 'dart:io';

import 'package:pub_semver/pub_semver.dart' as semver;
import 'package:test/test.dart';
import 'package:test_common/logging.dart';
import 'package:test_common/test_sdk_configuration.dart';
import 'package:vm_service/vm_service.dart';

import '../fixtures/context.dart';
import '../fixtures/project.dart';
import 'instance_inspection_common.dart';

void main() async {
// Enable verbose logging for debugging.
final debug = false;

final provider = TestSdkConfigurationProvider(verbose: debug);
tearDownAll(provider.dispose);

for (var compilationMode in CompilationMode.values) {
await _runTests(
provider: provider,
compilationMode: compilationMode,
debug: debug,
);
}
}

Future<void> _runTests({
required TestSdkConfigurationProvider provider,
required CompilationMode compilationMode,
required bool debug,
}) async {
final context =
TestContext(TestProject.testExperimentWithSoundNullSafety, provider);
final testInspector = TestInspector(context);

late VmServiceInterface service;
late Stream<Event> stream;
late String isolateId;
late ScriptRef mainScript;

onBreakPoint(breakPointId, body) => testInspector.onBreakPoint(
stream, isolateId, mainScript, breakPointId, body);

getInstanceRef(frame, expression) =>
testInspector.getInstanceRef(isolateId, frame, expression);

getFields(instanceRef, {offset, count}) => testInspector
.getFields(isolateId, instanceRef, offset: offset, count: count);

getFrameVariables(Frame frame) =>
testInspector.getFrameVariables(isolateId, frame);

group('$compilationMode |', () {
setUpAll(() async {
setCurrentLogWriter(debug: debug);
await context.setUp(
compilationMode: compilationMode,
enableExpressionEvaluation: true,
verboseCompiler: debug,
experiments: ['records', 'patterns'],
);
service = context.debugConnection.vmService;

final vm = await service.getVM();
isolateId = vm.isolates!.first.id!;
final scripts = await service.getScripts(isolateId);

await service.streamListen('Debug');
stream = service.onEvent('Debug');

mainScript = scripts.scripts!
.firstWhere((each) => each.uri!.contains('main.dart'));
});

tearDownAll(() async {
await context.tearDown();
});

setUp(() => setCurrentLogWriter(debug: debug));
tearDown(() => service.resume(isolateId));

test('pattern match case 1', () async {
await onBreakPoint('testPatternCase1', (event) async {
final frame = event.topFrame!;

expect(await getFrameVariables(frame), {
'obj': matchListInstance(type: 'List<Object>'),
'a': matchPrimitiveInstance(kind: InstanceKind.kString, value: 'a'),
'n': matchPrimitiveInstance(kind: InstanceKind.kDouble, value: 1),
});
});
});

test('pattern match case 2', () async {
await onBreakPoint('testPatternCase2', (event) async {
final frame = event.topFrame!;

expect(await getFrameVariables(frame), {
'obj': matchListInstance(type: 'List<Object>'),
'a': matchPrimitiveInstance(kind: InstanceKind.kString, value: 'b'),
'n': matchPrimitiveInstance(kind: InstanceKind.kDouble, value: 3.14),
});
});
});

test('pattern match default case', () async {
await onBreakPoint('testPatternDefault', (event) async {
final frame = event.topFrame!;
final frameIndex = frame.index!;
final instanceRef = await getInstanceRef(frameIndex, 'obj');
expect(await getFields(instanceRef), [0, 1]);

expect(await getFrameVariables(frame), {
'obj': matchListInstance(type: 'List<int>'),
});
});
});

test('stepping through pattern match', () async {
await onBreakPoint('callTestPattern1', (Event event) async {
var previousLocation = event.topFrame!.location;
for (var step in [
// Make sure we step into the callee.
for (var i = 0; i < 4; i++) 'Into',
// Make a few steps inside the callee.
for (var i = 0; i < 4; i++) 'Over',
]) {
await service.resume(isolateId, step: step);

event = await stream
.firstWhere((e) => e.kind == EventKind.kPauseInterrupted);

if (step == 'Over') {
expect(event.topFrame!.code!.name, 'testPattern');
}

final location = event.topFrame!.location;
expect(location, isNot(equals(previousLocation)));
previousLocation = location;
}
});
});
}, // TODO(annagrin): Remove when dart 3.0 is stable.
skip: semver.Version.parse(Platform.version.split(' ')[0]) <
semver.Version.parse('3.0.0-351.0.dev'));
}
26 changes: 13 additions & 13 deletions dwds/test/instances/record_inspection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Future<void> _runTests({
compilationMode: compilationMode,
enableExpressionEvaluation: true,
verboseCompiler: debug,
experiments: ['records'],
experiments: ['records', 'patterns'],
);
service = context.debugConnection.vmService;

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

test('simple records', () async {
await onBreakPoint('printSimpleLocal', (event) async {
await onBreakPoint('printSimpleLocalRecord', (event) async {
final frame = event.topFrame!.index!;
final instanceRef = await getInstanceRef(frame, 'record');

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

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

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

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

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

test('complex records fields', () async {
await onBreakPoint('printComplexLocal', (event) async {
await onBreakPoint('printComplexLocalRecord', (event) async {
final frame = event.topFrame!.index!;
final instanceRef = await getInstanceRef(frame, 'record');

Expand Down Expand Up @@ -208,7 +208,7 @@ Future<void> _runTests({
});

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

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

Expand Down Expand Up @@ -272,7 +272,7 @@ Future<void> _runTests({
});

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

test('nested records', () async {
await onBreakPoint('printNestedLocal', (event) async {
await onBreakPoint('printNestedLocalRecord', (event) async {
final frame = event.topFrame!.index!;
final instanceRef = await getInstanceRef(frame, 'record');

Expand Down Expand Up @@ -324,7 +324,7 @@ Future<void> _runTests({
});

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

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

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

Expand Down Expand Up @@ -382,7 +382,7 @@ Future<void> _runTests({
});

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

Expand Down
Loading