OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, the Dartino 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.md file. |
| 4 |
| 5 /// An implementation of the vm service-protocol in terms of a DartinoVmContext. |
| 6 /// Processes are mapped to isolates. |
| 7 // TODO(sigurdm): Handle processes better. |
| 8 // TODO(sigurdm): Find a way to represent fibers. |
| 9 // TODO(sigurdm): Represent remote values. |
| 10 // TODO(sigurdm): Use https://pub.dartlang.org/packages/json_rpc_2 for serving. |
| 11 |
| 12 import "dart:async" show Future; |
| 13 |
| 14 import 'dart:convert' show JSON; |
| 15 |
| 16 import 'dart:io' show File, HttpServer, WebSocket, WebSocketTransformer; |
| 17 |
| 18 import 'hub/session_manager.dart' show SessionState; |
| 19 |
| 20 import 'guess_configuration.dart' show dartinoVersion; |
| 21 |
| 22 import '../debug_state.dart' |
| 23 show BackTrace, BackTraceFrame, Breakpoint, RemoteObject; |
| 24 |
| 25 import '../vm_context.dart' show DartinoVmContext, DebugListener; |
| 26 |
| 27 import '../dartino_system.dart' |
| 28 show DartinoFunction, DartinoFunctionKind, DartinoSystem; |
| 29 |
| 30 import 'debug_info.dart' show DebugInfo, ScopeInfo, SourceLocation; |
| 31 |
| 32 import 'dartino_compiler_implementation.dart' |
| 33 show DartinoCompilerImplementation; |
| 34 |
| 35 import 'codegen_visitor.dart' show LocalValue; |
| 36 |
| 37 import '../program_info.dart' show Configuration; |
| 38 |
| 39 import 'package:collection/collection.dart' show binarySearch; |
| 40 |
| 41 import 'package:compiler/src/scanner/scanner.dart' show Scanner; |
| 42 import 'package:compiler/src/tokens/token.dart' show Token; |
| 43 import 'package:compiler/src/io/source_file.dart' show SourceFile; |
| 44 import 'package:compiler/src/elements/visitor.dart' show BaseElementVisitor; |
| 45 import 'package:compiler/src/elements/elements.dart' |
| 46 show |
| 47 CompilationUnitElement, |
| 48 Element, |
| 49 FunctionElement, |
| 50 LibraryElement, |
| 51 MemberElement, |
| 52 ScopeContainerElement; |
| 53 |
| 54 const bool logging = const bool.fromEnvironment("dartino-log-debug-server"); |
| 55 |
| 56 //TODO(danrubel): Verify const map values |
| 57 const Map<String, dynamic> dartCoreLibRefDesc = const <String, dynamic>{ |
| 58 "type": "@Library", |
| 59 "id": "libraries/dart:core", |
| 60 "fixedId": true, |
| 61 "name": "dart:core", |
| 62 "uri": "dart:core", |
| 63 }; |
| 64 |
| 65 //TODO(danrubel): Verify correct id |
| 66 const String nullId = "objects/Null"; |
| 67 |
| 68 //TODO(danrubel): Verify const map values |
| 69 const Map<String, dynamic> nullClassRefDesc = const <String, dynamic>{ |
| 70 "type": "@Class", |
| 71 "id": "classes/NullClass", |
| 72 "name": "NullClass", |
| 73 }; |
| 74 |
| 75 //TODO(danrubel): Verify const map values |
| 76 const Map<String, dynamic> nullRefDesc = const <String, dynamic>{ |
| 77 "type": "@Null", |
| 78 "id": nullId, |
| 79 "kind": "Null", |
| 80 "class": nullClassRefDesc, |
| 81 "valueAsString": "null", |
| 82 }; |
| 83 |
| 84 //TODO(danrubel): Verify const map values |
| 85 const Map<String, dynamic> nullDesc = const <String, dynamic>{ |
| 86 "type": "Null", |
| 87 "id": nullId, |
| 88 "kind": "Null", |
| 89 "class": nullClassRefDesc, |
| 90 "valueAsString": "null", |
| 91 }; |
| 92 |
| 93 class DebugServer { |
| 94 Future<int> serveSingleShot(SessionState state, |
| 95 {int port: 0, Uri snapshotLocation}) async { |
| 96 HttpServer server = await HttpServer.bind("127.0.0.1", port); |
| 97 // The Atom Dartino plugin waits for "localhost:<port-number>" |
| 98 // to determine the observatory port for debugging. |
| 99 print("localhost:${server.port}"); |
| 100 WebSocket socket = await server.transform(new WebSocketTransformer()).first; |
| 101 await new DebugConnection(state, socket, snapshotLocation: snapshotLocation) |
| 102 .serve(); |
| 103 await state.vmContext.terminate(); |
| 104 await server.close(); |
| 105 return 0; |
| 106 } |
| 107 } |
| 108 |
| 109 class DebugConnection implements DebugListener { |
| 110 final Map<String, bool> streams = new Map<String, bool>(); |
| 111 |
| 112 DartinoVmContext get vmContext => state.vmContext; |
| 113 final SessionState state; |
| 114 final WebSocket socket; |
| 115 final Uri snapshotLocation; |
| 116 |
| 117 final Map<Uri, List<int>> tokenTables = new Map<Uri, List<int>>(); |
| 118 |
| 119 Map lastPauseEvent; |
| 120 |
| 121 DebugConnection(this.state, this.socket, {this.snapshotLocation}); |
| 122 |
| 123 List<int> makeTokenTable(CompilationUnitElement compilationUnit) { |
| 124 // TODO(sigurdm): Are these cached somewhere? |
| 125 Token t = new Scanner(compilationUnit.script.file).tokenize(); |
| 126 List<int> result = new List<int>(); |
| 127 while (t.next != t) { |
| 128 result.add(t.charOffset); |
| 129 t = t.next; |
| 130 } |
| 131 return result; |
| 132 } |
| 133 |
| 134 void send(Map message) { |
| 135 if (logging) { |
| 136 print("Sending ${JSON.encode(message)}"); |
| 137 } |
| 138 socket.add(JSON.encode(message)); |
| 139 } |
| 140 |
| 141 void setStream(String streamId, bool value) { |
| 142 streams[streamId] = value; |
| 143 } |
| 144 |
| 145 Map<String, CompilationUnitElement> scripts = |
| 146 new Map<String, CompilationUnitElement>(); |
| 147 |
| 148 Map frameDesc(BackTraceFrame frame, int index) { |
| 149 List<Map> vars = new List<Map>(); |
| 150 for (ScopeInfo current = frame.scopeInfo(); |
| 151 current != ScopeInfo.sentinel; |
| 152 current = current.previous) { |
| 153 vars.add({ |
| 154 "type": "BoundVariable", |
| 155 "name": current.name, |
| 156 "value": valueDesc(current.lookup(current.name)) |
| 157 }); |
| 158 } |
| 159 return { |
| 160 "type": "Frame", |
| 161 "index": index, |
| 162 "function": functionRef(frame.function), |
| 163 "code": { |
| 164 "id": "code-id", //TODO(danrubel): what is the unique id here? |
| 165 "type": "@Code", |
| 166 "name": "code-name", // TODO(sigurdm): How to create a name here? |
| 167 "kind": "Dart", |
| 168 }, |
| 169 "location": locationDesc(frame.sourceLocation(), false), |
| 170 "vars": vars, |
| 171 }; |
| 172 } |
| 173 |
| 174 Map locationDesc(SourceLocation location, bool includeEndToken) { |
| 175 // TODO(sigurdm): Investigate when this happens. |
| 176 if (location == null || location.file == null) |
| 177 return { |
| 178 "type": "SourceLocation", |
| 179 "script": scriptRef(Uri.parse('file:///unknown')), |
| 180 "tokenPos": 0, |
| 181 }; |
| 182 Uri uri = location.file.uri; |
| 183 // TODO(sigurdm): Avoid this. The uri should be the same as we get from |
| 184 // `CompilationUnit.script.file.uri`. |
| 185 uri = new File(new File.fromUri(uri).resolveSymbolicLinksSync()).uri; |
| 186 |
| 187 int tokenPos = |
| 188 binarySearch(tokenTables[location.file.uri], location.span.begin); |
| 189 |
| 190 Map result = { |
| 191 "type": "SourceLocation", |
| 192 "script": scriptRef(location.file.uri), |
| 193 "tokenPos": tokenPos, |
| 194 }; |
| 195 if (includeEndToken) { |
| 196 int endTokenPos = |
| 197 binarySearch(tokenTables[location.file.uri], location.span.end); |
| 198 result["endTokenPos"] = endTokenPos; |
| 199 } |
| 200 |
| 201 return result; |
| 202 } |
| 203 |
| 204 Map isolateRef(int isolateId) { |
| 205 return { |
| 206 "name": "Isolate $isolateId", |
| 207 "id": "isolates/$isolateId", |
| 208 "fixedId": true, |
| 209 "type": "@Isolate", |
| 210 "number": "$isolateId" |
| 211 }; |
| 212 } |
| 213 |
| 214 Map libraryRef(LibraryElement library) { |
| 215 return { |
| 216 "type": "@Library", |
| 217 "id": "libraries/${library.canonicalUri}", |
| 218 "fixedId": true, |
| 219 "name": "${library.canonicalUri}", |
| 220 "uri": "${library.canonicalUri}", |
| 221 }; |
| 222 } |
| 223 |
| 224 Map libraryDesc(LibraryElement library) { |
| 225 return { |
| 226 "type": "Library", |
| 227 "id": "libraries/${library.canonicalUri}", |
| 228 "fixedId": true, |
| 229 "name": "${library.canonicalUri}", |
| 230 "uri": "${library.canonicalUri}", |
| 231 // TODO(danrubel): determine proper values for the next entry |
| 232 "debuggable": true, |
| 233 // TODO(sigurdm): The following fields are not used by the atom-debugger. |
| 234 // We might want to include them to get full compatibility with the |
| 235 // observatory. |
| 236 "dependencies": [], |
| 237 "classes": [], |
| 238 "variables": [], |
| 239 "functions": [], |
| 240 "scripts": library.compilationUnits |
| 241 .map((CompilationUnitElement compilationUnit) => |
| 242 scriptRef(compilationUnit.script.resourceUri)) |
| 243 .toList() |
| 244 }; |
| 245 } |
| 246 |
| 247 Map breakpointDesc(Breakpoint breakpoint) { |
| 248 DebugInfo debugInfo = |
| 249 state.vmContext.debugState.getDebugInfo(breakpoint.function); |
| 250 SourceLocation location = debugInfo.locationFor(breakpoint.bytecodeIndex); |
| 251 return { |
| 252 "type": "Breakpoint", |
| 253 "breakpointNumber": breakpoint.id, |
| 254 "id": "breakpoints/${breakpoint.id}", |
| 255 "fixedId": true, |
| 256 "resolved": true, |
| 257 "location": locationDesc(location, false), |
| 258 }; |
| 259 } |
| 260 |
| 261 Map scriptRef(Uri uri) { |
| 262 return { |
| 263 "type": "@Script", |
| 264 "id": "scripts/${uri}", |
| 265 "fixedId": true, |
| 266 "uri": "${uri}", |
| 267 "_kind": "script" |
| 268 }; |
| 269 } |
| 270 |
| 271 Map scriptDesc(CompilationUnitElement compilationUnit) { |
| 272 String text = compilationUnit.script.text; |
| 273 SourceFile file = compilationUnit.script.file; |
| 274 List<List<int>> tokenPosTable = []; |
| 275 List<int> tokenTable = tokenTables[compilationUnit.script.resourceUri]; |
| 276 int currentLine = -1; |
| 277 for (int i = 0; i < tokenTable.length; i++) { |
| 278 int line = file.getLine(tokenTable[i]); |
| 279 int column = file.getColumn(line, tokenTable[i]); |
| 280 if (line != currentLine) { |
| 281 currentLine = line; |
| 282 // The debugger lines starts from 1. |
| 283 tokenPosTable.add([currentLine + 1]); |
| 284 } |
| 285 tokenPosTable.last.add(i); |
| 286 tokenPosTable.last.add(column); |
| 287 } |
| 288 return { |
| 289 "type": "Script", |
| 290 "id": "scripts/${compilationUnit.script.resourceUri}", |
| 291 "fixedId": true, |
| 292 "uri": "${compilationUnit.script.resourceUri}", |
| 293 "library": libraryRef(compilationUnit.library), |
| 294 "source": text, |
| 295 "tokenPosTable": tokenPosTable, |
| 296 "lineOffset": 0, // TODO(sigurdm): What is this? |
| 297 "columnOffset": 0, // TODO(sigurdm): What is this? |
| 298 }; |
| 299 } |
| 300 |
| 301 String functionKind(DartinoFunctionKind kind) { |
| 302 return { |
| 303 DartinoFunctionKind.NORMAL: "RegularFunction", |
| 304 DartinoFunctionKind.LAZY_FIELD_INITIALIZER: "Stub", |
| 305 DartinoFunctionKind.INITIALIZER_LIST: "RegularFunction", |
| 306 DartinoFunctionKind.PARAMETER_STUB: "Stub", |
| 307 DartinoFunctionKind.ACCESSOR: "GetterFunction" |
| 308 }[kind]; |
| 309 } |
| 310 |
| 311 Map functionRef(DartinoFunction function) { |
| 312 String name = function.name; |
| 313 //TODO(danrubel): Investigate why this happens. |
| 314 if (name == null || name.isEmpty) name = 'unknown'; |
| 315 return { |
| 316 "type": "@Function", |
| 317 "id": "functions/${function.functionId}", |
| 318 "fixedId": true, |
| 319 "name": "${name}", |
| 320 // TODO(sigurdm): All kinds of owner. |
| 321 "owner": libraryRef(function?.element?.library ?? state.compiler.mainApp), |
| 322 "static": function.element?.isStatic ?? false, |
| 323 "const": function.element?.isConst ?? false, |
| 324 "_kind": functionKind(function.kind), |
| 325 }; |
| 326 } |
| 327 |
| 328 Map functionDesc(DartinoFunction function) { |
| 329 FunctionElement element = function.element; |
| 330 return { |
| 331 "type": "Function", |
| 332 "id": "functions/${function.functionId}", |
| 333 // TODO(sigurdm): All kinds of owner. |
| 334 "owner": libraryRef(element.library), |
| 335 "static": element.isStatic, |
| 336 "const": element.isConst, |
| 337 "_kind": functionKind(function.kind), |
| 338 }; |
| 339 } |
| 340 |
| 341 Map valueDesc(LocalValue lookup) { |
| 342 //TODO(danrubel): Translate local value into description? |
| 343 // Need to return an @InstanceRef or Sentinel |
| 344 return nullRefDesc; |
| 345 } |
| 346 |
| 347 initialize(DartinoCompilerImplementation compiler) { |
| 348 for (LibraryElement library in compiler.libraryLoader.libraries) { |
| 349 cacheScripts(LibraryElement library) { |
| 350 for (CompilationUnitElement compilationUnit |
| 351 in library.compilationUnits) { |
| 352 Uri uri = compilationUnit.script.file.uri; |
| 353 scripts["scripts/$uri"] = compilationUnit; |
| 354 tokenTables[uri] = makeTokenTable(compilationUnit); |
| 355 } |
| 356 } |
| 357 cacheScripts(library); |
| 358 if (library.isPatched) { |
| 359 cacheScripts(library.patch); |
| 360 } |
| 361 } |
| 362 } |
| 363 |
| 364 serve() async { |
| 365 vmContext.listeners.add(this); |
| 366 |
| 367 await vmContext.initialize(state, snapshotLocation: snapshotLocation); |
| 368 |
| 369 initialize(state.compiler.compiler); |
| 370 |
| 371 await for (var message in socket) { |
| 372 if (message is! String) throw "Expected String"; |
| 373 var decodedMessage = JSON.decode(message); |
| 374 if (logging) { |
| 375 print("Received $decodedMessage"); |
| 376 } |
| 377 |
| 378 if (decodedMessage is! Map) throw "Expected Map"; |
| 379 var id = decodedMessage['id']; |
| 380 void sendResult(Map result) { |
| 381 Map message = {"jsonrpc": "2.0", "result": result, "id": id}; |
| 382 send(message); |
| 383 } |
| 384 void sendError(Map error) { |
| 385 Map message = {"jsonrpc": "2.0", "error": error, "id": id}; |
| 386 send(message); |
| 387 } |
| 388 switch (decodedMessage["method"]) { |
| 389 case "streamListen": |
| 390 setStream(decodedMessage["params"]["streamId"], true); |
| 391 sendResult({"type": "Success"}); |
| 392 break; |
| 393 case "streamCancel": |
| 394 setStream(decodedMessage["streamId"], false); |
| 395 sendResult({"type": "Success"}); |
| 396 break; |
| 397 case "getVersion": |
| 398 sendResult({"type": "Version", "major": 3, "minor": 4}); |
| 399 break; |
| 400 case "getVM": |
| 401 List<int> isolates = await state.vmContext.processes(); |
| 402 sendResult({ |
| 403 "type": "VM", |
| 404 "name": "dartino-vm", |
| 405 "architectureBits": { |
| 406 Configuration.Offset64BitsDouble: 64, |
| 407 Configuration.Offset64BitsFloat: 64, |
| 408 Configuration.Offset32BitsDouble: 32, |
| 409 Configuration.Offset32BitsFloat: 32, |
| 410 }[vmContext.configuration], |
| 411 // TODO(sigurdm): Can we give a better description? |
| 412 "targetCPU": "${vmContext.configuration}", |
| 413 // TODO(sigurdm): Can we give a better description? |
| 414 "hostCPU": "${vmContext.configuration}", |
| 415 "version": "$dartinoVersion", |
| 416 // TODO(sigurdm): Can we say something meaningful? |
| 417 "pid": 0, |
| 418 // TODO(sigurdm): Implement a startTime for devices with a clock. |
| 419 "startTime": 0, |
| 420 "isolates": isolates.map(isolateRef).toList() |
| 421 }); |
| 422 break; |
| 423 case "getIsolate": |
| 424 String isolateIdString = decodedMessage["params"]["isolateId"]; |
| 425 int isolateId = int.parse( |
| 426 isolateIdString.substring(isolateIdString.indexOf("/") + 1)); |
| 427 |
| 428 sendResult({ |
| 429 "type": "Isolate", |
| 430 "runnable": true, |
| 431 "livePorts": 0, |
| 432 "startTime": 0, |
| 433 "name": "Isolate $isolateId", |
| 434 "number": "$isolateId", |
| 435 // TODO(sigurdm): This seems to be required by the observatory. |
| 436 "_originNumber": "$isolateId", |
| 437 "breakpoints": |
| 438 state.vmContext.breakpoints().map(breakpointDesc).toList(), |
| 439 "rootLib": libraryRef(state.compiler.compiler.mainApp), |
| 440 "id": "$isolateIdString", |
| 441 "libraries": state.compiler.compiler.libraryLoader.libraries |
| 442 .map(libraryRef) |
| 443 .toList(), |
| 444 "pauseEvent": lastPauseEvent, |
| 445 // TODO(danrubel): determine proper values for these 2 entries |
| 446 "pauseOnExit": false, |
| 447 "exceptionPauseMode": "Unhandled", |
| 448 // Needed by observatory. |
| 449 "_debuggerSettings": {"_exceptions": "unhandled"}, |
| 450 }); |
| 451 break; |
| 452 case "addBreakpoint": |
| 453 String scriptId = decodedMessage["params"]["scriptId"]; |
| 454 Uri uri = scripts[scriptId].script.resourceUri; |
| 455 int line = decodedMessage["params"]["line"]; |
| 456 int column = decodedMessage["params"]["column"] ?? 1; |
| 457 // TODO(sigurdm): Use the isolateId. |
| 458 Breakpoint breakpoint = |
| 459 await state.vmContext.setFileBreakpoint(uri, line, column); |
| 460 if (breakpoint != null) { |
| 461 sendResult({ |
| 462 "type": "Breakpoint", |
| 463 "id": "breakpoints/${breakpoint.id}", |
| 464 "breakpointNumber": breakpoint.id, |
| 465 "resolved": true, |
| 466 "location": locationDesc( |
| 467 breakpoint.location(vmContext.debugState), false), |
| 468 }); |
| 469 } else { |
| 470 sendError({"code": 102, "message": "Cannot add breakpoint"}); |
| 471 } |
| 472 break; |
| 473 case "removeBreakpoint": |
| 474 String breakpointId = decodedMessage["params"]["breakpointId"]; |
| 475 int id = |
| 476 int.parse(breakpointId.substring(breakpointId.indexOf("/") + 1)); |
| 477 if (vmContext.isRunning || vmContext.isTerminated) { |
| 478 sendError({"code": 106, "message": "Isolate must be paused"}); |
| 479 break; |
| 480 } |
| 481 Breakpoint breakpoint = await vmContext.deleteBreakpoint(id); |
| 482 if (breakpoint == null) { |
| 483 // TODO(sigurdm): Is this the right message? |
| 484 sendError({"code": 102, "message": "Cannot remove breakpoint"}); |
| 485 } else { |
| 486 sendResult({"type": "Success"}); |
| 487 } |
| 488 break; |
| 489 case "getObject": |
| 490 // TODO(sigurdm): should not be ignoring the isolate id. |
| 491 String id = decodedMessage["params"]["objectId"]; |
| 492 int slashIndex = id.indexOf('/'); |
| 493 switch (id.substring(0, slashIndex)) { |
| 494 case "libraries": |
| 495 String uri = id.substring(slashIndex + 1); |
| 496 sendResult(libraryDesc(state.compiler.compiler.libraryLoader |
| 497 .lookupLibrary(Uri.parse(uri)))); |
| 498 break; |
| 499 case "scripts": |
| 500 sendResult(scriptDesc(scripts[id])); |
| 501 break; |
| 502 case "functions": |
| 503 sendResult(functionDesc(vmContext.dartinoSystem.functionsById[ |
| 504 int.parse(id.substring(slashIndex + 1))])); |
| 505 break; |
| 506 case "objects": |
| 507 if (id == nullId) { |
| 508 sendResult(nullDesc); |
| 509 } else { |
| 510 //TODO(danrubel): Need to return an Instance description |
| 511 // or Sentinel for the given @InstanceRef |
| 512 throw 'Unknown object: $id'; |
| 513 } |
| 514 break; |
| 515 default: |
| 516 throw "Unsupported object type $id"; |
| 517 } |
| 518 break; |
| 519 case "getStack": |
| 520 String isolateIdString = decodedMessage["params"]["isolateId"]; |
| 521 int isolateId = int.parse( |
| 522 isolateIdString.substring(isolateIdString.indexOf("/") + 1)); |
| 523 BackTrace backTrace = |
| 524 await state.vmContext.backTrace(processId: isolateId); |
| 525 List frames = []; |
| 526 int index = 0; |
| 527 for (BackTraceFrame frame in backTrace.frames) { |
| 528 frames.add(frameDesc(frame, index)); |
| 529 index++; |
| 530 } |
| 531 |
| 532 sendResult({"type": "Stack", "frames": frames, "messages": [],}); |
| 533 break; |
| 534 case "getSourceReport": |
| 535 String scriptId = decodedMessage["params"]["scriptId"]; |
| 536 CompilationUnitElement compilationUnit = scripts[scriptId]; |
| 537 Uri scriptUri = compilationUnit.script.file.uri; |
| 538 List<int> tokenTable = tokenTables[scriptUri]; |
| 539 |
| 540 // We do not support coverage. |
| 541 assert(decodedMessage["params"]["reports"] |
| 542 .contains("PossibleBreakpoints")); |
| 543 int tokenPos = decodedMessage["params"]["tokenPos"] ?? 0; |
| 544 int endTokenPos = |
| 545 decodedMessage["params"]["endTokenPos"] ?? tokenTable.length - 1; |
| 546 int startPos = tokenTable[tokenPos]; |
| 547 int endPos = tokenTable[endTokenPos]; |
| 548 List<int> possibleBreakpoints = new List<int>(); |
| 549 DartinoSystem system = state.compilationResults.last.system; |
| 550 for (FunctionElement function |
| 551 in FunctionsFinder.findNestedFunctions(compilationUnit)) { |
| 552 DartinoFunction dartinoFunction = |
| 553 system.functionsByElement[function]; |
| 554 if (dartinoFunction == null) break; |
| 555 DebugInfo info = state.compiler |
| 556 .createDebugInfo(system.functionsByElement[function], system); |
| 557 if (info == null) break; |
| 558 for (SourceLocation location in info.locations) { |
| 559 // TODO(sigurdm): Investigate these. |
| 560 if (location == null || location.span == null) continue; |
| 561 int position = location.span.begin; |
| 562 if (!(position >= startPos && position <= endPos)) continue; |
| 563 possibleBreakpoints.add(binarySearch(tokenTable, position)); |
| 564 } |
| 565 } |
| 566 Map range = { |
| 567 "scriptIndex": 0, |
| 568 "compiled": true, |
| 569 "startPos": tokenPos, |
| 570 "endPos": endTokenPos, |
| 571 "possibleBreakpoints": possibleBreakpoints, |
| 572 "callSites": [], |
| 573 }; |
| 574 sendResult({ |
| 575 "type": "SourceReport", |
| 576 "ranges": [range], |
| 577 "scripts": [scriptRef(scriptUri)], |
| 578 }); |
| 579 break; |
| 580 case "resume": |
| 581 // TODO(sigurdm): use isolateId. |
| 582 String stepOption = decodedMessage["params"]["step"]; |
| 583 switch (stepOption) { |
| 584 case "Into": |
| 585 // TODO(sigurdm): avoid needing await here. |
| 586 await vmContext.step(); |
| 587 sendResult({"type": "Success"}); |
| 588 break; |
| 589 case "Over": |
| 590 // TODO(sigurdm): avoid needing await here. |
| 591 await vmContext.stepOver(); |
| 592 sendResult({"type": "Success"}); |
| 593 break; |
| 594 case "Out": |
| 595 // TODO(sigurdm): avoid needing await here. |
| 596 await vmContext.stepOut(); |
| 597 sendResult({"type": "Success"}); |
| 598 break; |
| 599 case "OverAsyncSuspension": |
| 600 sendError({ |
| 601 "code": 100, |
| 602 "message": "Feature is disabled", |
| 603 "data": { |
| 604 "details": |
| 605 "Stepping over async suspensions is not implemented", |
| 606 } |
| 607 }); |
| 608 break; |
| 609 default: |
| 610 assert(stepOption == null); |
| 611 if (vmContext.isScheduled) { |
| 612 // TODO(sigurdm): Ensure other commands are not issued during |
| 613 // this. |
| 614 vmContext.cont(); |
| 615 } else { |
| 616 vmContext.startRunning(); |
| 617 } |
| 618 sendResult({"type": "Success"}); |
| 619 } |
| 620 break; |
| 621 case "setExceptionPauseMode": |
| 622 // TODO(sigurdm): implement exception-pause-mode. |
| 623 sendResult({"type": "Success"}); |
| 624 break; |
| 625 case "pause": |
| 626 await vmContext.interrupt(); |
| 627 sendResult({"type": "Success"}); |
| 628 break; |
| 629 case "addBreakpointWithScriptUri": |
| 630 // TODO(sigurdm): Use the isolateId. |
| 631 String scriptUri = decodedMessage["params"]["scriptUri"]; |
| 632 int line = decodedMessage["params"]["line"] ?? 1; |
| 633 int column = decodedMessage["params"]["column"] ?? 1; |
| 634 if (vmContext.isRunning) { |
| 635 sendError({"code": 102, "message": "Cannot add breakpoint"}); |
| 636 break; |
| 637 } |
| 638 Breakpoint breakpoint = await vmContext.setFileBreakpoint( |
| 639 Uri.parse(scriptUri), line, column); |
| 640 if (breakpoint == null) { |
| 641 sendError({"code": 102, "message": "Cannot add breakpoint"}); |
| 642 } else { |
| 643 sendResult({ |
| 644 "type": "Breakpoint", |
| 645 "id": "breakpoints/${breakpoint.id}", |
| 646 "breakpointNumber": breakpoint.id, |
| 647 "resolved": true, |
| 648 "location": locationDesc( |
| 649 breakpoint.location(vmContext.debugState), false), |
| 650 }); |
| 651 } |
| 652 break; |
| 653 default: |
| 654 sendError({ |
| 655 "code": 100, |
| 656 "message": "Feature is disabled", |
| 657 "data": { |
| 658 "details": |
| 659 "Request type ${decodedMessage["method"]} not implemented", |
| 660 } |
| 661 }); |
| 662 if (logging) { |
| 663 print("Unhandled request type: ${decodedMessage["method"]}"); |
| 664 } |
| 665 } |
| 666 } |
| 667 } |
| 668 |
| 669 void streamNotify(String streamId, Map event) { |
| 670 if (streams[streamId] ?? false) { |
| 671 send({ |
| 672 "method": "streamNotify", |
| 673 "params": {"streamId": streamId, "event": event,}, |
| 674 "jsonrpc": "2.0", |
| 675 }); |
| 676 } |
| 677 } |
| 678 |
| 679 @override |
| 680 breakpointAdded(int processId, Breakpoint breakpoint) { |
| 681 streamNotify("Debug", { |
| 682 "type": "Event", |
| 683 "kind": "BreakpointAdded", |
| 684 "isolate": isolateRef(processId), |
| 685 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 686 "breakpoint": breakpointDesc(breakpoint), |
| 687 }); |
| 688 } |
| 689 |
| 690 @override |
| 691 breakpointRemoved(int processId, Breakpoint breakpoint) { |
| 692 streamNotify("Debug", { |
| 693 "type": "Event", |
| 694 "kind": "BreakpointRemoved", |
| 695 "isolate": isolateRef(processId), |
| 696 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 697 "breakpoint": breakpointDesc(breakpoint), |
| 698 }); |
| 699 } |
| 700 |
| 701 @override |
| 702 gc(int processId) { |
| 703 // TODO(sigurdm): Implement gc notification. |
| 704 } |
| 705 |
| 706 @override |
| 707 lostConnection() { |
| 708 socket.close(); |
| 709 } |
| 710 |
| 711 @override |
| 712 pauseBreakpoint( |
| 713 int processId, BackTraceFrame topFrame, Breakpoint breakpoint) { |
| 714 //TODO(danrubel): are there any other breakpoints |
| 715 // at which we are currently paused for a PauseBreakpoint event? |
| 716 List<Breakpoint> pauseBreakpoints = <Breakpoint>[]; |
| 717 pauseBreakpoints.add(breakpoint); |
| 718 Map event = { |
| 719 "type": "Event", |
| 720 "kind": "PauseBreakpoint", |
| 721 "isolate": isolateRef(processId), |
| 722 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 723 "topFrame": frameDesc(topFrame, 0), |
| 724 "atAsyncSuspension": false, |
| 725 "breakpoint": breakpointDesc(breakpoint), |
| 726 "pauseBreakpoints": |
| 727 new List.from(pauseBreakpoints.map((bp) => breakpointDesc(bp))), |
| 728 }; |
| 729 lastPauseEvent = event; |
| 730 streamNotify("Debug", event); |
| 731 } |
| 732 |
| 733 @override |
| 734 pauseException(int processId, BackTraceFrame topFrame, RemoteObject thrown) { |
| 735 Map event = { |
| 736 "type": "Event", |
| 737 "kind": "PauseException", |
| 738 "isolate": isolateRef(processId), |
| 739 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 740 "topFrame": frameDesc(topFrame, 0), |
| 741 "atAsyncSuspension": false, |
| 742 // TODO(sigurdm): pass thrown as an instance. |
| 743 }; |
| 744 streamNotify("Debug", event); |
| 745 } |
| 746 |
| 747 @override |
| 748 pauseExit(int processId, BackTraceFrame topFrame) { |
| 749 // TODO(sigurdm): implement pauseExit |
| 750 } |
| 751 |
| 752 @override |
| 753 pauseInterrupted(int processId, BackTraceFrame topFrame) { |
| 754 Map event = { |
| 755 "type": "Event", |
| 756 "kind": "PauseInterrupted", |
| 757 "isolate": isolateRef(processId), |
| 758 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 759 "topFrame": frameDesc(topFrame, 0), |
| 760 "atAsyncSuspension": false, |
| 761 }; |
| 762 lastPauseEvent = event; |
| 763 streamNotify("Debug", event); |
| 764 } |
| 765 |
| 766 @override |
| 767 pauseStart(int processId) { |
| 768 Map event = { |
| 769 "type": "Event", |
| 770 "kind": "PauseStart", |
| 771 "isolate": isolateRef(processId), |
| 772 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 773 }; |
| 774 lastPauseEvent = event; |
| 775 streamNotify("Debug", event); |
| 776 } |
| 777 |
| 778 @override |
| 779 processExit(int processId) { |
| 780 streamNotify("Isolate", { |
| 781 "type": "Event", |
| 782 "kind": "IsolateExit", |
| 783 "isolate": isolateRef(processId), |
| 784 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 785 }); |
| 786 socket.close(); |
| 787 } |
| 788 |
| 789 @override |
| 790 processRunnable(int processId) { |
| 791 Map event = { |
| 792 "type": "Event", |
| 793 "kind": "IsolateRunnable", |
| 794 "isolate": isolateRef(processId), |
| 795 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 796 }; |
| 797 streamNotify("Isolate", event); |
| 798 } |
| 799 |
| 800 @override |
| 801 processStart(int processId) { |
| 802 streamNotify("Isolate", { |
| 803 "type": "Event", |
| 804 "kind": "IsolateStart", |
| 805 "isolate": isolateRef(processId), |
| 806 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 807 }); |
| 808 } |
| 809 |
| 810 @override |
| 811 resume(int processId) { |
| 812 Map event = { |
| 813 "type": "Event", |
| 814 "kind": "Resume", |
| 815 "isolate": isolateRef(processId), |
| 816 "timestamp": new DateTime.now().millisecondsSinceEpoch, |
| 817 }; |
| 818 BackTraceFrame topFrame = vmContext.debugState.topFrame; |
| 819 if (topFrame != null) { |
| 820 event["topFrame"] = frameDesc(vmContext.debugState.topFrame, 0); |
| 821 } |
| 822 lastPauseEvent = event; |
| 823 streamNotify("Debug", event); |
| 824 } |
| 825 |
| 826 @override |
| 827 writeStdErr(int processId, List<int> data) { |
| 828 Map event = { |
| 829 "type": "Event", |
| 830 "kind": "WriteEvent", |
| 831 "bytes": new String.fromCharCodes(data), |
| 832 }; |
| 833 streamNotify("Stderr", event); |
| 834 } |
| 835 |
| 836 @override |
| 837 writeStdOut(int processId, List<int> data) { |
| 838 Map event = { |
| 839 "type": "Event", |
| 840 "kind": "WriteEvent", |
| 841 "bytes": new String.fromCharCodes(data), |
| 842 }; |
| 843 streamNotify("Stdout", event); |
| 844 } |
| 845 |
| 846 @override |
| 847 terminated() {} |
| 848 } |
| 849 |
| 850 class FunctionsFinder extends BaseElementVisitor { |
| 851 final List<FunctionElement> result = new List<FunctionElement>(); |
| 852 |
| 853 FunctionsFinder(); |
| 854 |
| 855 static List<FunctionElement> findNestedFunctions( |
| 856 CompilationUnitElement element) { |
| 857 FunctionsFinder finder = new FunctionsFinder(); |
| 858 finder.visit(element); |
| 859 return finder.result; |
| 860 } |
| 861 |
| 862 visit(Element e, [arg]) => e.accept(this, arg); |
| 863 |
| 864 visitElement(Element e, _) {} |
| 865 |
| 866 visitFunctionElement(FunctionElement element, _) { |
| 867 result.add(element); |
| 868 MemberElement memberContext = element.memberContext; |
| 869 if (memberContext == element) { |
| 870 memberContext.nestedClosures.forEach(visit); |
| 871 } |
| 872 } |
| 873 |
| 874 visitScopeContainerElement(ScopeContainerElement e, _) { |
| 875 e.forEachLocalMember(visit); |
| 876 } |
| 877 |
| 878 visitCompilationUnitElement(CompilationUnitElement e, _) { |
| 879 e.forEachLocalMember(visit); |
| 880 } |
| 881 } |
OLD | NEW |