blob: 1838d41a46c992f2066b652e3801dde71be26a7d [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:vm_service/vm_service.dart';
import '../debugger/debugger.dart';
import '../ui/analytics.dart' as ga;
import '../ui/fake_flutter/fake_flutter.dart'
show ValueNotifier, ValueListenable;
class DebuggerState {
VmService _service;
StreamSubscription<Event> _debugSubscription;
IsolateRef isolateRef;
List<ScriptRef> scripts;
final Map<String, Script> _scriptCache = <String, Script>{};
final _isPaused = ValueNotifier<bool>(false);
ValueListenable<bool> get isPaused => _isPaused;
final _hasFrames = ValueNotifier<bool>(false);
ValueNotifier<bool> _supportsStepping;
ValueListenable<bool> get supportsStepping {
return _supportsStepping ??= () {
final notifier = ValueNotifier<bool>(_isPaused.value && _hasFrames.value);
void update() {
notifier.value = _isPaused.value && _hasFrames.value;
}
_isPaused.addListener(update);
_hasFrames.addListener(update);
return notifier;
}();
}
Event lastEvent;
final _breakpoints = ValueNotifier<List<Breakpoint>>([]);
ValueListenable<List<Breakpoint>> get breakpoints => _breakpoints;
final _exceptionPauseMode = ValueNotifier<String>(null);
ValueListenable<String> get exceptionPauseMode => _exceptionPauseMode;
InstanceRef _reportedException;
void setVmService(VmService service) {
_service = service;
_debugSubscription = _service.onDebugEvent.listen(_handleIsolateEvent);
}
void switchToIsolate(IsolateRef ref) async {
isolateRef = ref;
_isPaused.value = false;
_clearCaches();
if (ref == null) {
_breakpoints.value = [];
return;
}
final dynamic result = await _service.getIsolate(isolateRef.id);
if (result is Isolate) {
final Isolate isolate = result;
if (isolate.pauseEvent != null &&
isolate.pauseEvent.kind != EventKind.kResume) {
lastEvent = isolate.pauseEvent;
_reportedException = isolate.pauseEvent.exception;
_isPaused.value = true;
}
_breakpoints.value = isolate.breakpoints;
_exceptionPauseMode.value = isolate.exceptionPauseMode;
}
}
Future<Success> pause() => _service.pause(isolateRef.id);
Future<Success> resume() => _service.resume(isolateRef.id);
Future<Success> stepOver() {
ga.select(ga.debugger, ga.stepOver);
// Handle async suspensions; issue StepOption.kOverAsyncSuspension.
final bool useAsyncStepping = lastEvent?.atAsyncSuspension == true;
return _service.resume(isolateRef.id,
step: useAsyncStepping
? StepOption.kOverAsyncSuspension
: StepOption.kOver);
}
Future<Success> stepIn() {
ga.select(ga.debugger, ga.stepIn);
return _service.resume(isolateRef.id, step: StepOption.kInto);
}
Future<Success> stepOut() {
ga.select(ga.debugger, ga.stepOut);
return _service.resume(isolateRef.id, step: StepOption.kOut);
}
Future<void> clearBreakpoints() async {
final List<Breakpoint> breakpoints = _breakpoints.value.toList();
await Future.forEach(breakpoints, (Breakpoint breakpoint) {
return removeBreakpoint(breakpoint);
});
}
Future<void> addBreakpoint(String scriptId, int line) {
return _service.addBreakpoint(isolateRef.id, scriptId, line);
}
Future<void> addBreakpointByPathFragment(String path, int line) async {
final ScriptRef ref =
scripts.firstWhere((ref) => ref.uri.endsWith(path), orElse: () => null);
if (ref != null) {
return _service.addBreakpoint(isolateRef.id, ref.id, line);
}
}
Future<void> removeBreakpoint(Breakpoint breakpoint) {
return _service.removeBreakpoint(isolateRef.id, breakpoint.id);
}
Future<void> setExceptionPauseMode(String mode) {
return _service.setExceptionPauseMode(isolateRef.id, mode);
}
Future<Stack> getStack() {
return _service.getStack(isolateRef.id);
}
InstanceRef get reportedException => _reportedException;
void _handleIsolateEvent(Event event) {
if (event.isolate.id != isolateRef.id) {
return;
}
_hasFrames.value = event.topFrame != null;
lastEvent = event;
switch (event.kind) {
case EventKind.kResume:
_isPaused.value = false;
_reportedException = null;
break;
case EventKind.kPauseStart:
case EventKind.kPauseExit:
case EventKind.kPauseBreakpoint:
case EventKind.kPauseInterrupted:
case EventKind.kPauseException:
case EventKind.kPausePostRequest:
_reportedException = event.exception;
_isPaused.value = true;
break;
case EventKind.kBreakpointAdded:
_breakpoints.value = [..._breakpoints.value, event.breakpoint];
break;
case EventKind.kBreakpointResolved:
_breakpoints.value = [
for (var b in _breakpoints.value) if (b != event.breakpoint) b,
event.breakpoint
];
break;
case EventKind.kBreakpointRemoved:
_breakpoints.value = [
for (var b in _breakpoints.value) if (b != event.breakpoint) b
];
break;
}
}
void _clearCaches() {
_scriptCache.clear();
lastEvent = null;
_reportedException = null;
}
void dispose() {
_debugSubscription?.cancel();
}
/// Get the populated [Instance] object, given an [InstanceRef].
///
/// The return value can be one of [Instance] or [Sentinel].
Future<dynamic> getInstance(InstanceRef instanceRef) {
return _service.getObject(isolateRef.id, instanceRef.id);
}
Future<Script> getScript(ScriptRef scriptRef) async {
if (!_scriptCache.containsKey(scriptRef.id)) {
_scriptCache[scriptRef.id] =
await _service.getObject(isolateRef.id, scriptRef.id);
}
return _scriptCache[scriptRef.id];
}
SourcePosition calculatePosition(Script script, int tokenPos) {
final List<List<int>> table = script.tokenPosTable;
if (table == null) {
return null;
}
for (List<int> row in table) {
if (row == null || row.isEmpty) {
continue;
}
final int line = row.elementAt(0);
int index = 1;
while (index < row.length - 1) {
if (row.elementAt(index) == tokenPos) {
return SourcePosition(line, row.elementAt(index + 1));
}
index += 2;
}
}
return null;
}
String commonScriptPrefix;
LibraryRef rootLib;
void setRootLib(LibraryRef rootLib) {
this.rootLib = rootLib;
String scriptPrefix = rootLib.uri;
if (scriptPrefix.startsWith('package:')) {
scriptPrefix = scriptPrefix.substring(0, scriptPrefix.indexOf('/') + 1);
} else if (scriptPrefix.contains('/lib/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/lib/'));
if (scriptPrefix.contains('/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/') + 1);
}
} else if (scriptPrefix.contains('/bin/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/bin/'));
if (scriptPrefix.contains('/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/') + 1);
}
} else if (scriptPrefix.contains('/test/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/test/'));
if (scriptPrefix.contains('/')) {
scriptPrefix =
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/') + 1);
}
} else {
scriptPrefix = null;
}
commonScriptPrefix = scriptPrefix;
}
String getShortScriptName(String uri) {
if (commonScriptPrefix == null) {
return uri;
}
if (!uri.startsWith(commonScriptPrefix)) {
return uri;
}
if (commonScriptPrefix.startsWith('package:')) {
return uri.substring('package:'.length);
} else {
return uri.substring(commonScriptPrefix.length);
}
}
void updateFrom(Isolate isolate) {
_breakpoints.value = isolate.breakpoints;
}
}