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