Chromium Code Reviews| 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..0717b16ab01ff6f986d1f796ca0265286d2aa536 |
| --- /dev/null |
| +++ b/pkg/dartino_compiler/lib/src/debug_service_protocol.dart |
| @@ -0,0 +1,901 @@ |
| +// 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. |
| + |
| +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: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; |
| + |
| +// import 'package:stack_trace/stack_trace.dart'; |
| + |
| +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 Chain.capture(() async { |
|
Søren Gjesse
2016/06/13 13:06:30
What is the cost of having this always be there?
sigurdm
2016/06/14 08:17:21
I removed it.
|
| + await new DebugConnection(state, socket, snapshotLocation: snapshotLocation) |
| + .serve(); |
| +// }, onError: (final error, final Chain chain) { |
| +// print("$error ${chain.terse}"); |
| +// }); |
| + 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>(); |
| + |
| + static int binarySearch(List<int> table, int needle) { |
|
Søren Gjesse
2016/06/13 13:06:30
Maybe use binarySearch from the collection package
sigurdm
2016/06/14 08:17:21
Good idea!
Done
|
| + int l = 0; |
| + int r = table.length - 1; |
| + while (true) { |
| + if (l > r) return -1; |
| + int m = (l + r) ~/ 2; |
| + int a = table[m]; |
| + if (a < needle) { |
| + l = m + 1; |
| + } else if (a > needle) { |
| + r = m - 1; |
| + } else { |
| + return m; |
| + } |
| + } |
| + } |
| + |
| + 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) { |
|
Søren Gjesse
2016/06/13 13:06:30
Have you checked if it is possible to re-use somet
sigurdm
2016/06/14 08:17:21
I added a TODO.
|
| + 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); |
| + } |
| +} |