| Index: pkg/dartino_compiler/lib/src/debug_service_protocol.dart
|
| diff --git a/pkg/dartino_compiler/lib/src/debug_service_protocol.dart b/pkg/dartino_compiler/lib/src/debug_service_protocol.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c21df67cc9e8f4c913bc81f548c29eddb31339ba
|
| --- /dev/null
|
| +++ b/pkg/dartino_compiler/lib/src/debug_service_protocol.dart
|
| @@ -0,0 +1,881 @@
|
| +// Copyright (c) 2016, the Dartino 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.md file.
|
| +
|
| +/// An implementation of the vm service-protocol in terms of a DartinoVmContext.
|
| +/// Processes are mapped to isolates.
|
| +// TODO(sigurdm): Handle processes better.
|
| +// TODO(sigurdm): Find a way to represent fibers.
|
| +// TODO(sigurdm): Represent remote values.
|
| +// TODO(sigurdm): Use https://pub.dartlang.org/packages/json_rpc_2 for serving.
|
| +
|
| +import "dart:async" show Future;
|
| +
|
| +import 'dart:convert' show JSON;
|
| +
|
| +import 'dart:io' show File, HttpServer, WebSocket, WebSocketTransformer;
|
| +
|
| +import 'hub/session_manager.dart' show SessionState;
|
| +
|
| +import 'guess_configuration.dart' show dartinoVersion;
|
| +
|
| +import '../debug_state.dart'
|
| + show BackTrace, BackTraceFrame, Breakpoint, RemoteObject;
|
| +
|
| +import '../vm_context.dart' show DartinoVmContext, DebugListener;
|
| +
|
| +import '../dartino_system.dart'
|
| + show DartinoFunction, DartinoFunctionKind, DartinoSystem;
|
| +
|
| +import 'debug_info.dart' show DebugInfo, ScopeInfo, SourceLocation;
|
| +
|
| +import 'dartino_compiler_implementation.dart'
|
| + show DartinoCompilerImplementation;
|
| +
|
| +import 'codegen_visitor.dart' show LocalValue;
|
| +
|
| +import '../program_info.dart' show Configuration;
|
| +
|
| +import 'package:collection/collection.dart' show binarySearch;
|
| +
|
| +import 'package:compiler/src/scanner/scanner.dart' show Scanner;
|
| +import 'package:compiler/src/tokens/token.dart' show Token;
|
| +import 'package:compiler/src/io/source_file.dart' show SourceFile;
|
| +import 'package:compiler/src/elements/visitor.dart' show BaseElementVisitor;
|
| +import 'package:compiler/src/elements/elements.dart'
|
| + show
|
| + CompilationUnitElement,
|
| + Element,
|
| + FunctionElement,
|
| + LibraryElement,
|
| + MemberElement,
|
| + ScopeContainerElement;
|
| +
|
| +const bool logging = const bool.fromEnvironment("dartino-log-debug-server");
|
| +
|
| +//TODO(danrubel): Verify const map values
|
| +const Map<String, dynamic> dartCoreLibRefDesc = const <String, dynamic>{
|
| + "type": "@Library",
|
| + "id": "libraries/dart:core",
|
| + "fixedId": true,
|
| + "name": "dart:core",
|
| + "uri": "dart:core",
|
| +};
|
| +
|
| +//TODO(danrubel): Verify correct id
|
| +const String nullId = "objects/Null";
|
| +
|
| +//TODO(danrubel): Verify const map values
|
| +const Map<String, dynamic> nullClassRefDesc = const <String, dynamic>{
|
| + "type": "@Class",
|
| + "id": "classes/NullClass",
|
| + "name": "NullClass",
|
| +};
|
| +
|
| +//TODO(danrubel): Verify const map values
|
| +const Map<String, dynamic> nullRefDesc = const <String, dynamic>{
|
| + "type": "@Null",
|
| + "id": nullId,
|
| + "kind": "Null",
|
| + "class": nullClassRefDesc,
|
| + "valueAsString": "null",
|
| +};
|
| +
|
| +//TODO(danrubel): Verify const map values
|
| +const Map<String, dynamic> nullDesc = const <String, dynamic>{
|
| + "type": "Null",
|
| + "id": nullId,
|
| + "kind": "Null",
|
| + "class": nullClassRefDesc,
|
| + "valueAsString": "null",
|
| +};
|
| +
|
| +class DebugServer {
|
| + Future<int> serveSingleShot(SessionState state,
|
| + {int port: 0, Uri snapshotLocation}) async {
|
| + HttpServer server = await HttpServer.bind("127.0.0.1", port);
|
| + // The Atom Dartino plugin waits for "localhost:<port-number>"
|
| + // to determine the observatory port for debugging.
|
| + print("localhost:${server.port}");
|
| + WebSocket socket = await server.transform(new WebSocketTransformer()).first;
|
| + await new DebugConnection(state, socket, snapshotLocation: snapshotLocation)
|
| + .serve();
|
| + await state.vmContext.terminate();
|
| + await server.close();
|
| + return 0;
|
| + }
|
| +}
|
| +
|
| +class DebugConnection implements DebugListener {
|
| + final Map<String, bool> streams = new Map<String, bool>();
|
| +
|
| + DartinoVmContext get vmContext => state.vmContext;
|
| + final SessionState state;
|
| + final WebSocket socket;
|
| + final Uri snapshotLocation;
|
| +
|
| + final Map<Uri, List<int>> tokenTables = new Map<Uri, List<int>>();
|
| +
|
| + Map lastPauseEvent;
|
| +
|
| + DebugConnection(this.state, this.socket, {this.snapshotLocation});
|
| +
|
| + List<int> makeTokenTable(CompilationUnitElement compilationUnit) {
|
| + // TODO(sigurdm): Are these cached somewhere?
|
| + Token t = new Scanner(compilationUnit.script.file).tokenize();
|
| + List<int> result = new List<int>();
|
| + while (t.next != t) {
|
| + result.add(t.charOffset);
|
| + t = t.next;
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + void send(Map message) {
|
| + if (logging) {
|
| + print("Sending ${JSON.encode(message)}");
|
| + }
|
| + socket.add(JSON.encode(message));
|
| + }
|
| +
|
| + void setStream(String streamId, bool value) {
|
| + streams[streamId] = value;
|
| + }
|
| +
|
| + Map<String, CompilationUnitElement> scripts =
|
| + new Map<String, CompilationUnitElement>();
|
| +
|
| + Map frameDesc(BackTraceFrame frame, int index) {
|
| + List<Map> vars = new List<Map>();
|
| + for (ScopeInfo current = frame.scopeInfo();
|
| + current != ScopeInfo.sentinel;
|
| + current = current.previous) {
|
| + vars.add({
|
| + "type": "BoundVariable",
|
| + "name": current.name,
|
| + "value": valueDesc(current.lookup(current.name))
|
| + });
|
| + }
|
| + return {
|
| + "type": "Frame",
|
| + "index": index,
|
| + "function": functionRef(frame.function),
|
| + "code": {
|
| + "id": "code-id", //TODO(danrubel): what is the unique id here?
|
| + "type": "@Code",
|
| + "name": "code-name", // TODO(sigurdm): How to create a name here?
|
| + "kind": "Dart",
|
| + },
|
| + "location": locationDesc(frame.sourceLocation(), false),
|
| + "vars": vars,
|
| + };
|
| + }
|
| +
|
| + Map locationDesc(SourceLocation location, bool includeEndToken) {
|
| + // TODO(sigurdm): Investigate when this happens.
|
| + if (location == null || location.file == null)
|
| + return {
|
| + "type": "SourceLocation",
|
| + "script": scriptRef(Uri.parse('file:///unknown')),
|
| + "tokenPos": 0,
|
| + };
|
| + Uri uri = location.file.uri;
|
| + // TODO(sigurdm): Avoid this. The uri should be the same as we get from
|
| + // `CompilationUnit.script.file.uri`.
|
| + uri = new File(new File.fromUri(uri).resolveSymbolicLinksSync()).uri;
|
| +
|
| + int tokenPos =
|
| + binarySearch(tokenTables[location.file.uri], location.span.begin);
|
| +
|
| + Map result = {
|
| + "type": "SourceLocation",
|
| + "script": scriptRef(location.file.uri),
|
| + "tokenPos": tokenPos,
|
| + };
|
| + if (includeEndToken) {
|
| + int endTokenPos =
|
| + binarySearch(tokenTables[location.file.uri], location.span.end);
|
| + result["endTokenPos"] = endTokenPos;
|
| + }
|
| +
|
| + return result;
|
| + }
|
| +
|
| + Map isolateRef(int isolateId) {
|
| + return {
|
| + "name": "Isolate $isolateId",
|
| + "id": "isolates/$isolateId",
|
| + "fixedId": true,
|
| + "type": "@Isolate",
|
| + "number": "$isolateId"
|
| + };
|
| + }
|
| +
|
| + Map libraryRef(LibraryElement library) {
|
| + return {
|
| + "type": "@Library",
|
| + "id": "libraries/${library.canonicalUri}",
|
| + "fixedId": true,
|
| + "name": "${library.canonicalUri}",
|
| + "uri": "${library.canonicalUri}",
|
| + };
|
| + }
|
| +
|
| + Map libraryDesc(LibraryElement library) {
|
| + return {
|
| + "type": "Library",
|
| + "id": "libraries/${library.canonicalUri}",
|
| + "fixedId": true,
|
| + "name": "${library.canonicalUri}",
|
| + "uri": "${library.canonicalUri}",
|
| + // TODO(danrubel): determine proper values for the next entry
|
| + "debuggable": true,
|
| + // TODO(sigurdm): The following fields are not used by the atom-debugger.
|
| + // We might want to include them to get full compatibility with the
|
| + // observatory.
|
| + "dependencies": [],
|
| + "classes": [],
|
| + "variables": [],
|
| + "functions": [],
|
| + "scripts": library.compilationUnits
|
| + .map((CompilationUnitElement compilationUnit) =>
|
| + scriptRef(compilationUnit.script.resourceUri))
|
| + .toList()
|
| + };
|
| + }
|
| +
|
| + Map breakpointDesc(Breakpoint breakpoint) {
|
| + DebugInfo debugInfo =
|
| + state.vmContext.debugState.getDebugInfo(breakpoint.function);
|
| + SourceLocation location = debugInfo.locationFor(breakpoint.bytecodeIndex);
|
| + return {
|
| + "type": "Breakpoint",
|
| + "breakpointNumber": breakpoint.id,
|
| + "id": "breakpoints/${breakpoint.id}",
|
| + "fixedId": true,
|
| + "resolved": true,
|
| + "location": locationDesc(location, false),
|
| + };
|
| + }
|
| +
|
| + Map scriptRef(Uri uri) {
|
| + return {
|
| + "type": "@Script",
|
| + "id": "scripts/${uri}",
|
| + "fixedId": true,
|
| + "uri": "${uri}",
|
| + "_kind": "script"
|
| + };
|
| + }
|
| +
|
| + Map scriptDesc(CompilationUnitElement compilationUnit) {
|
| + String text = compilationUnit.script.text;
|
| + SourceFile file = compilationUnit.script.file;
|
| + List<List<int>> tokenPosTable = [];
|
| + List<int> tokenTable = tokenTables[compilationUnit.script.resourceUri];
|
| + int currentLine = -1;
|
| + for (int i = 0; i < tokenTable.length; i++) {
|
| + int line = file.getLine(tokenTable[i]);
|
| + int column = file.getColumn(line, tokenTable[i]);
|
| + if (line != currentLine) {
|
| + currentLine = line;
|
| + // The debugger lines starts from 1.
|
| + tokenPosTable.add([currentLine + 1]);
|
| + }
|
| + tokenPosTable.last.add(i);
|
| + tokenPosTable.last.add(column);
|
| + }
|
| + return {
|
| + "type": "Script",
|
| + "id": "scripts/${compilationUnit.script.resourceUri}",
|
| + "fixedId": true,
|
| + "uri": "${compilationUnit.script.resourceUri}",
|
| + "library": libraryRef(compilationUnit.library),
|
| + "source": text,
|
| + "tokenPosTable": tokenPosTable,
|
| + "lineOffset": 0, // TODO(sigurdm): What is this?
|
| + "columnOffset": 0, // TODO(sigurdm): What is this?
|
| + };
|
| + }
|
| +
|
| + String functionKind(DartinoFunctionKind kind) {
|
| + return {
|
| + DartinoFunctionKind.NORMAL: "RegularFunction",
|
| + DartinoFunctionKind.LAZY_FIELD_INITIALIZER: "Stub",
|
| + DartinoFunctionKind.INITIALIZER_LIST: "RegularFunction",
|
| + DartinoFunctionKind.PARAMETER_STUB: "Stub",
|
| + DartinoFunctionKind.ACCESSOR: "GetterFunction"
|
| + }[kind];
|
| + }
|
| +
|
| + Map functionRef(DartinoFunction function) {
|
| + String name = function.name;
|
| + //TODO(danrubel): Investigate why this happens.
|
| + if (name == null || name.isEmpty) name = 'unknown';
|
| + return {
|
| + "type": "@Function",
|
| + "id": "functions/${function.functionId}",
|
| + "fixedId": true,
|
| + "name": "${name}",
|
| + // TODO(sigurdm): All kinds of owner.
|
| + "owner": libraryRef(function?.element?.library ?? state.compiler.mainApp),
|
| + "static": function.element?.isStatic ?? false,
|
| + "const": function.element?.isConst ?? false,
|
| + "_kind": functionKind(function.kind),
|
| + };
|
| + }
|
| +
|
| + Map functionDesc(DartinoFunction function) {
|
| + FunctionElement element = function.element;
|
| + return {
|
| + "type": "Function",
|
| + "id": "functions/${function.functionId}",
|
| + // TODO(sigurdm): All kinds of owner.
|
| + "owner": libraryRef(element.library),
|
| + "static": element.isStatic,
|
| + "const": element.isConst,
|
| + "_kind": functionKind(function.kind),
|
| + };
|
| + }
|
| +
|
| + Map valueDesc(LocalValue lookup) {
|
| + //TODO(danrubel): Translate local value into description?
|
| + // Need to return an @InstanceRef or Sentinel
|
| + return nullRefDesc;
|
| + }
|
| +
|
| + initialize(DartinoCompilerImplementation compiler) {
|
| + for (LibraryElement library in compiler.libraryLoader.libraries) {
|
| + cacheScripts(LibraryElement library) {
|
| + for (CompilationUnitElement compilationUnit
|
| + in library.compilationUnits) {
|
| + Uri uri = compilationUnit.script.file.uri;
|
| + scripts["scripts/$uri"] = compilationUnit;
|
| + tokenTables[uri] = makeTokenTable(compilationUnit);
|
| + }
|
| + }
|
| + cacheScripts(library);
|
| + if (library.isPatched) {
|
| + cacheScripts(library.patch);
|
| + }
|
| + }
|
| + }
|
| +
|
| + serve() async {
|
| + vmContext.listeners.add(this);
|
| +
|
| + await vmContext.initialize(state, snapshotLocation: snapshotLocation);
|
| +
|
| + initialize(state.compiler.compiler);
|
| +
|
| + await for (var message in socket) {
|
| + if (message is! String) throw "Expected String";
|
| + var decodedMessage = JSON.decode(message);
|
| + if (logging) {
|
| + print("Received $decodedMessage");
|
| + }
|
| +
|
| + if (decodedMessage is! Map) throw "Expected Map";
|
| + var id = decodedMessage['id'];
|
| + void sendResult(Map result) {
|
| + Map message = {"jsonrpc": "2.0", "result": result, "id": id};
|
| + send(message);
|
| + }
|
| + void sendError(Map error) {
|
| + Map message = {"jsonrpc": "2.0", "error": error, "id": id};
|
| + send(message);
|
| + }
|
| + switch (decodedMessage["method"]) {
|
| + case "streamListen":
|
| + setStream(decodedMessage["params"]["streamId"], true);
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "streamCancel":
|
| + setStream(decodedMessage["streamId"], false);
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "getVersion":
|
| + sendResult({"type": "Version", "major": 3, "minor": 4});
|
| + break;
|
| + case "getVM":
|
| + List<int> isolates = await state.vmContext.processes();
|
| + sendResult({
|
| + "type": "VM",
|
| + "name": "dartino-vm",
|
| + "architectureBits": {
|
| + Configuration.Offset64BitsDouble: 64,
|
| + Configuration.Offset64BitsFloat: 64,
|
| + Configuration.Offset32BitsDouble: 32,
|
| + Configuration.Offset32BitsFloat: 32,
|
| + }[vmContext.configuration],
|
| + // TODO(sigurdm): Can we give a better description?
|
| + "targetCPU": "${vmContext.configuration}",
|
| + // TODO(sigurdm): Can we give a better description?
|
| + "hostCPU": "${vmContext.configuration}",
|
| + "version": "$dartinoVersion",
|
| + // TODO(sigurdm): Can we say something meaningful?
|
| + "pid": 0,
|
| + // TODO(sigurdm): Implement a startTime for devices with a clock.
|
| + "startTime": 0,
|
| + "isolates": isolates.map(isolateRef).toList()
|
| + });
|
| + break;
|
| + case "getIsolate":
|
| + String isolateIdString = decodedMessage["params"]["isolateId"];
|
| + int isolateId = int.parse(
|
| + isolateIdString.substring(isolateIdString.indexOf("/") + 1));
|
| +
|
| + sendResult({
|
| + "type": "Isolate",
|
| + "runnable": true,
|
| + "livePorts": 0,
|
| + "startTime": 0,
|
| + "name": "Isolate $isolateId",
|
| + "number": "$isolateId",
|
| + // TODO(sigurdm): This seems to be required by the observatory.
|
| + "_originNumber": "$isolateId",
|
| + "breakpoints":
|
| + state.vmContext.breakpoints().map(breakpointDesc).toList(),
|
| + "rootLib": libraryRef(state.compiler.compiler.mainApp),
|
| + "id": "$isolateIdString",
|
| + "libraries": state.compiler.compiler.libraryLoader.libraries
|
| + .map(libraryRef)
|
| + .toList(),
|
| + "pauseEvent": lastPauseEvent,
|
| + // TODO(danrubel): determine proper values for these 2 entries
|
| + "pauseOnExit": false,
|
| + "exceptionPauseMode": "Unhandled",
|
| + // Needed by observatory.
|
| + "_debuggerSettings": {"_exceptions": "unhandled"},
|
| + });
|
| + break;
|
| + case "addBreakpoint":
|
| + String scriptId = decodedMessage["params"]["scriptId"];
|
| + Uri uri = scripts[scriptId].script.resourceUri;
|
| + int line = decodedMessage["params"]["line"];
|
| + int column = decodedMessage["params"]["column"] ?? 1;
|
| + // TODO(sigurdm): Use the isolateId.
|
| + Breakpoint breakpoint =
|
| + await state.vmContext.setFileBreakpoint(uri, line, column);
|
| + if (breakpoint != null) {
|
| + sendResult({
|
| + "type": "Breakpoint",
|
| + "id": "breakpoints/${breakpoint.id}",
|
| + "breakpointNumber": breakpoint.id,
|
| + "resolved": true,
|
| + "location": locationDesc(
|
| + breakpoint.location(vmContext.debugState), false),
|
| + });
|
| + } else {
|
| + sendError({"code": 102, "message": "Cannot add breakpoint"});
|
| + }
|
| + break;
|
| + case "removeBreakpoint":
|
| + String breakpointId = decodedMessage["params"]["breakpointId"];
|
| + int id =
|
| + int.parse(breakpointId.substring(breakpointId.indexOf("/") + 1));
|
| + if (vmContext.isRunning || vmContext.isTerminated) {
|
| + sendError({"code": 106, "message": "Isolate must be paused"});
|
| + break;
|
| + }
|
| + Breakpoint breakpoint = await vmContext.deleteBreakpoint(id);
|
| + if (breakpoint == null) {
|
| + // TODO(sigurdm): Is this the right message?
|
| + sendError({"code": 102, "message": "Cannot remove breakpoint"});
|
| + } else {
|
| + sendResult({"type": "Success"});
|
| + }
|
| + break;
|
| + case "getObject":
|
| + // TODO(sigurdm): should not be ignoring the isolate id.
|
| + String id = decodedMessage["params"]["objectId"];
|
| + int slashIndex = id.indexOf('/');
|
| + switch (id.substring(0, slashIndex)) {
|
| + case "libraries":
|
| + String uri = id.substring(slashIndex + 1);
|
| + sendResult(libraryDesc(state.compiler.compiler.libraryLoader
|
| + .lookupLibrary(Uri.parse(uri))));
|
| + break;
|
| + case "scripts":
|
| + sendResult(scriptDesc(scripts[id]));
|
| + break;
|
| + case "functions":
|
| + sendResult(functionDesc(vmContext.dartinoSystem.functionsById[
|
| + int.parse(id.substring(slashIndex + 1))]));
|
| + break;
|
| + case "objects":
|
| + if (id == nullId) {
|
| + sendResult(nullDesc);
|
| + } else {
|
| + //TODO(danrubel): Need to return an Instance description
|
| + // or Sentinel for the given @InstanceRef
|
| + throw 'Unknown object: $id';
|
| + }
|
| + break;
|
| + default:
|
| + throw "Unsupported object type $id";
|
| + }
|
| + break;
|
| + case "getStack":
|
| + String isolateIdString = decodedMessage["params"]["isolateId"];
|
| + int isolateId = int.parse(
|
| + isolateIdString.substring(isolateIdString.indexOf("/") + 1));
|
| + BackTrace backTrace =
|
| + await state.vmContext.backTrace(processId: isolateId);
|
| + List frames = [];
|
| + int index = 0;
|
| + for (BackTraceFrame frame in backTrace.frames) {
|
| + frames.add(frameDesc(frame, index));
|
| + index++;
|
| + }
|
| +
|
| + sendResult({"type": "Stack", "frames": frames, "messages": [],});
|
| + break;
|
| + case "getSourceReport":
|
| + String scriptId = decodedMessage["params"]["scriptId"];
|
| + CompilationUnitElement compilationUnit = scripts[scriptId];
|
| + Uri scriptUri = compilationUnit.script.file.uri;
|
| + List<int> tokenTable = tokenTables[scriptUri];
|
| +
|
| + // We do not support coverage.
|
| + assert(decodedMessage["params"]["reports"]
|
| + .contains("PossibleBreakpoints"));
|
| + int tokenPos = decodedMessage["params"]["tokenPos"] ?? 0;
|
| + int endTokenPos =
|
| + decodedMessage["params"]["endTokenPos"] ?? tokenTable.length - 1;
|
| + int startPos = tokenTable[tokenPos];
|
| + int endPos = tokenTable[endTokenPos];
|
| + List<int> possibleBreakpoints = new List<int>();
|
| + DartinoSystem system = state.compilationResults.last.system;
|
| + for (FunctionElement function
|
| + in FunctionsFinder.findNestedFunctions(compilationUnit)) {
|
| + DartinoFunction dartinoFunction =
|
| + system.functionsByElement[function];
|
| + if (dartinoFunction == null) break;
|
| + DebugInfo info = state.compiler
|
| + .createDebugInfo(system.functionsByElement[function], system);
|
| + if (info == null) break;
|
| + for (SourceLocation location in info.locations) {
|
| + // TODO(sigurdm): Investigate these.
|
| + if (location == null || location.span == null) continue;
|
| + int position = location.span.begin;
|
| + if (!(position >= startPos && position <= endPos)) continue;
|
| + possibleBreakpoints.add(binarySearch(tokenTable, position));
|
| + }
|
| + }
|
| + Map range = {
|
| + "scriptIndex": 0,
|
| + "compiled": true,
|
| + "startPos": tokenPos,
|
| + "endPos": endTokenPos,
|
| + "possibleBreakpoints": possibleBreakpoints,
|
| + "callSites": [],
|
| + };
|
| + sendResult({
|
| + "type": "SourceReport",
|
| + "ranges": [range],
|
| + "scripts": [scriptRef(scriptUri)],
|
| + });
|
| + break;
|
| + case "resume":
|
| + // TODO(sigurdm): use isolateId.
|
| + String stepOption = decodedMessage["params"]["step"];
|
| + switch (stepOption) {
|
| + case "Into":
|
| + // TODO(sigurdm): avoid needing await here.
|
| + await vmContext.step();
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "Over":
|
| + // TODO(sigurdm): avoid needing await here.
|
| + await vmContext.stepOver();
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "Out":
|
| + // TODO(sigurdm): avoid needing await here.
|
| + await vmContext.stepOut();
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "OverAsyncSuspension":
|
| + sendError({
|
| + "code": 100,
|
| + "message": "Feature is disabled",
|
| + "data": {
|
| + "details":
|
| + "Stepping over async suspensions is not implemented",
|
| + }
|
| + });
|
| + break;
|
| + default:
|
| + assert(stepOption == null);
|
| + if (vmContext.isScheduled) {
|
| + // TODO(sigurdm): Ensure other commands are not issued during
|
| + // this.
|
| + vmContext.cont();
|
| + } else {
|
| + vmContext.startRunning();
|
| + }
|
| + sendResult({"type": "Success"});
|
| + }
|
| + break;
|
| + case "setExceptionPauseMode":
|
| + // TODO(sigurdm): implement exception-pause-mode.
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "pause":
|
| + await vmContext.interrupt();
|
| + sendResult({"type": "Success"});
|
| + break;
|
| + case "addBreakpointWithScriptUri":
|
| + // TODO(sigurdm): Use the isolateId.
|
| + String scriptUri = decodedMessage["params"]["scriptUri"];
|
| + int line = decodedMessage["params"]["line"] ?? 1;
|
| + int column = decodedMessage["params"]["column"] ?? 1;
|
| + if (vmContext.isRunning) {
|
| + sendError({"code": 102, "message": "Cannot add breakpoint"});
|
| + break;
|
| + }
|
| + Breakpoint breakpoint = await vmContext.setFileBreakpoint(
|
| + Uri.parse(scriptUri), line, column);
|
| + if (breakpoint == null) {
|
| + sendError({"code": 102, "message": "Cannot add breakpoint"});
|
| + } else {
|
| + sendResult({
|
| + "type": "Breakpoint",
|
| + "id": "breakpoints/${breakpoint.id}",
|
| + "breakpointNumber": breakpoint.id,
|
| + "resolved": true,
|
| + "location": locationDesc(
|
| + breakpoint.location(vmContext.debugState), false),
|
| + });
|
| + }
|
| + break;
|
| + default:
|
| + sendError({
|
| + "code": 100,
|
| + "message": "Feature is disabled",
|
| + "data": {
|
| + "details":
|
| + "Request type ${decodedMessage["method"]} not implemented",
|
| + }
|
| + });
|
| + if (logging) {
|
| + print("Unhandled request type: ${decodedMessage["method"]}");
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + void streamNotify(String streamId, Map event) {
|
| + if (streams[streamId] ?? false) {
|
| + send({
|
| + "method": "streamNotify",
|
| + "params": {"streamId": streamId, "event": event,},
|
| + "jsonrpc": "2.0",
|
| + });
|
| + }
|
| + }
|
| +
|
| + @override
|
| + breakpointAdded(int processId, Breakpoint breakpoint) {
|
| + streamNotify("Debug", {
|
| + "type": "Event",
|
| + "kind": "BreakpointAdded",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + "breakpoint": breakpointDesc(breakpoint),
|
| + });
|
| + }
|
| +
|
| + @override
|
| + breakpointRemoved(int processId, Breakpoint breakpoint) {
|
| + streamNotify("Debug", {
|
| + "type": "Event",
|
| + "kind": "BreakpointRemoved",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + "breakpoint": breakpointDesc(breakpoint),
|
| + });
|
| + }
|
| +
|
| + @override
|
| + gc(int processId) {
|
| + // TODO(sigurdm): Implement gc notification.
|
| + }
|
| +
|
| + @override
|
| + lostConnection() {
|
| + socket.close();
|
| + }
|
| +
|
| + @override
|
| + pauseBreakpoint(
|
| + int processId, BackTraceFrame topFrame, Breakpoint breakpoint) {
|
| + //TODO(danrubel): are there any other breakpoints
|
| + // at which we are currently paused for a PauseBreakpoint event?
|
| + List<Breakpoint> pauseBreakpoints = <Breakpoint>[];
|
| + pauseBreakpoints.add(breakpoint);
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "PauseBreakpoint",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + "topFrame": frameDesc(topFrame, 0),
|
| + "atAsyncSuspension": false,
|
| + "breakpoint": breakpointDesc(breakpoint),
|
| + "pauseBreakpoints":
|
| + new List.from(pauseBreakpoints.map((bp) => breakpointDesc(bp))),
|
| + };
|
| + lastPauseEvent = event;
|
| + streamNotify("Debug", event);
|
| + }
|
| +
|
| + @override
|
| + pauseException(int processId, BackTraceFrame topFrame, RemoteObject thrown) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "PauseException",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + "topFrame": frameDesc(topFrame, 0),
|
| + "atAsyncSuspension": false,
|
| + // TODO(sigurdm): pass thrown as an instance.
|
| + };
|
| + streamNotify("Debug", event);
|
| + }
|
| +
|
| + @override
|
| + pauseExit(int processId, BackTraceFrame topFrame) {
|
| + // TODO(sigurdm): implement pauseExit
|
| + }
|
| +
|
| + @override
|
| + pauseInterrupted(int processId, BackTraceFrame topFrame) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "PauseInterrupted",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + "topFrame": frameDesc(topFrame, 0),
|
| + "atAsyncSuspension": false,
|
| + };
|
| + lastPauseEvent = event;
|
| + streamNotify("Debug", event);
|
| + }
|
| +
|
| + @override
|
| + pauseStart(int processId) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "PauseStart",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + };
|
| + lastPauseEvent = event;
|
| + streamNotify("Debug", event);
|
| + }
|
| +
|
| + @override
|
| + processExit(int processId) {
|
| + streamNotify("Isolate", {
|
| + "type": "Event",
|
| + "kind": "IsolateExit",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + });
|
| + socket.close();
|
| + }
|
| +
|
| + @override
|
| + processRunnable(int processId) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "IsolateRunnable",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + };
|
| + streamNotify("Isolate", event);
|
| + }
|
| +
|
| + @override
|
| + processStart(int processId) {
|
| + streamNotify("Isolate", {
|
| + "type": "Event",
|
| + "kind": "IsolateStart",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + });
|
| + }
|
| +
|
| + @override
|
| + resume(int processId) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "Resume",
|
| + "isolate": isolateRef(processId),
|
| + "timestamp": new DateTime.now().millisecondsSinceEpoch,
|
| + };
|
| + BackTraceFrame topFrame = vmContext.debugState.topFrame;
|
| + if (topFrame != null) {
|
| + event["topFrame"] = frameDesc(vmContext.debugState.topFrame, 0);
|
| + }
|
| + lastPauseEvent = event;
|
| + streamNotify("Debug", event);
|
| + }
|
| +
|
| + @override
|
| + writeStdErr(int processId, List<int> data) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "WriteEvent",
|
| + "bytes": new String.fromCharCodes(data),
|
| + };
|
| + streamNotify("Stderr", event);
|
| + }
|
| +
|
| + @override
|
| + writeStdOut(int processId, List<int> data) {
|
| + Map event = {
|
| + "type": "Event",
|
| + "kind": "WriteEvent",
|
| + "bytes": new String.fromCharCodes(data),
|
| + };
|
| + streamNotify("Stdout", event);
|
| + }
|
| +
|
| + @override
|
| + terminated() {}
|
| +}
|
| +
|
| +class FunctionsFinder extends BaseElementVisitor {
|
| + final List<FunctionElement> result = new List<FunctionElement>();
|
| +
|
| + FunctionsFinder();
|
| +
|
| + static List<FunctionElement> findNestedFunctions(
|
| + CompilationUnitElement element) {
|
| + FunctionsFinder finder = new FunctionsFinder();
|
| + finder.visit(element);
|
| + return finder.result;
|
| + }
|
| +
|
| + visit(Element e, [arg]) => e.accept(this, arg);
|
| +
|
| + visitElement(Element e, _) {}
|
| +
|
| + visitFunctionElement(FunctionElement element, _) {
|
| + result.add(element);
|
| + MemberElement memberContext = element.memberContext;
|
| + if (memberContext == element) {
|
| + memberContext.nestedClosures.forEach(visit);
|
| + }
|
| + }
|
| +
|
| + visitScopeContainerElement(ScopeContainerElement e, _) {
|
| + e.forEachLocalMember(visit);
|
| + }
|
| +
|
| + visitCompilationUnitElement(CompilationUnitElement e, _) {
|
| + e.forEachLocalMember(visit);
|
| + }
|
| +}
|
|
|