Index: runtime/bin/vmservice/client/lib/src/service/object.dart |
diff --git a/runtime/bin/vmservice/client/lib/src/service/object.dart b/runtime/bin/vmservice/client/lib/src/service/object.dart |
index e9d4adb291d3635bc9c57878fb2a4c28698eec7b..c17062f2300cbc54667c22fbd18f84413de0a43d 100644 |
--- a/runtime/bin/vmservice/client/lib/src/service/object.dart |
+++ b/runtime/bin/vmservice/client/lib/src/service/object.dart |
@@ -88,8 +88,6 @@ abstract class ServiceObject extends Observable { |
case 'Library': |
obj = new Library._empty(owner); |
break; |
- case 'Null': |
- return null; |
case 'ServiceError': |
obj = new ServiceError._empty(owner); |
break; |
@@ -268,6 +266,7 @@ abstract class VM extends ServiceObjectOwner { |
"Expected 'ServiceEvent' but found '${map['type']}'"); |
return; |
} |
+ |
// Extract the owning isolate from the event itself. |
String owningIsolateId = map['isolate']['id']; |
_getIsolate(owningIsolateId).then((owningIsolate) { |
@@ -416,6 +415,7 @@ abstract class VM extends ServiceObjectOwner { |
/// will only receive a map encoding a valid ServiceObject. |
Future<ObservableMap> getAsMap(String id) { |
return getString(id).then((response) { |
+ print("GOT $response"); |
koda
2014/07/29 15:55:12
Seems you left some debug printing here. But this
Cutch
2014/07/29 17:04:35
We have the logging package for this. It's easy to
|
var map = _parseJSON(response); |
return _processMap(map); |
}).catchError((error) { |
@@ -754,6 +754,8 @@ class Isolate extends ServiceObjectOwner with Coverage { |
_loaded = true; |
loading = false; |
+ reloadBreakpoints(); |
+ |
// Remap DebuggerEvent to ServiceEvent so that the observatory can |
// work against 1.5 vms in the short term. |
// |
@@ -890,6 +892,144 @@ class Isolate extends ServiceObjectOwner with Coverage { |
} |
return node; |
} |
+ |
+ ServiceMap breakpoints; |
+ |
+ void _removeBreakpoint(ServiceMap bpt) { |
+ var script = bpt['location']['script']; |
+ var tokenPos = bpt['location']['tokenPos']; |
+ assert(tokenPos != null); |
+ if (script.loaded) { |
+ var line = script.tokenToLine(tokenPos); |
+ assert(line != null); |
+ assert(script.lines[line - 1].bpt == bpt); |
+ script.lines[line - 1].bpt = null; |
+ } |
+ } |
+ |
+ void _addBreakpoint(ServiceMap bpt) { |
+ var script = bpt['location']['script']; |
+ var tokenPos = bpt['location']['tokenPos']; |
+ assert(tokenPos != null); |
+ if (script.loaded) { |
+ var line = script.tokenToLine(tokenPos); |
+ assert(line != null); |
+ assert(script.lines[line - 1].bpt == null); |
+ script.lines[line - 1].bpt = bpt; |
+ } else { |
+ // Load the script and then plop in the breakpoint. |
+ script.load().then((_) { |
+ _addBreakpoint(bpt); |
+ }); |
+ } |
+ } |
+ |
+ void _updateBreakpoints(ServiceMap newBreakpoints) { |
+ // Remove all of the old breakpoints from the Script lines. |
+ if (breakpoints != null) { |
+ for (var bpt in breakpoints['breakpoints']) { |
+ _removeBreakpoint(bpt); |
+ } |
+ } |
+ // Add all of the new breakpoints to the Script lines. |
+ for (var bpt in newBreakpoints['breakpoints']) { |
+ _addBreakpoint(bpt); |
+ } |
+ breakpoints = newBreakpoints; |
+ } |
+ |
+ Future<ServiceObject> _inProgressReloadBpts; |
+ |
+ Future reloadBreakpoints() { |
+ // TODO(turnidge): Can reusing the Future here ever cause us to |
+ // get stale breakpoints? |
+ if (_inProgressReloadBpts == null) { |
+ _inProgressReloadBpts = |
+ get('debug/breakpoints').then((newBpts) { |
+ _updateBreakpoints(newBpts); |
+ }).whenComplete(() { |
+ _inProgressReloadBpts = null; |
+ }); |
+ } |
+ return _inProgressReloadBpts; |
+ } |
+ |
+ Future<ServiceObject> setBreakpoint(Script script, int line) { |
+ return get(script.id + "/setBreakpoint?line=${line}").then((result) { |
+ if (result is DartError) { |
+ // Unable to set a breakpoint at desired line. |
+ script.lines[line - 1].possibleBpt = false; |
+ } |
+ return reloadBreakpoints(); |
+ }); |
+ } |
+ |
+ Future clearBreakpoint(ServiceMap bpt) { |
+ return get('${bpt.id}/clear').then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ if (pauseEvent != null && |
+ pauseEvent.breakpoint != null && |
+ (pauseEvent.breakpoint['id'] == bpt['id'])) { |
+ return isolate.reload(); |
+ } else { |
+ return reloadBreakpoints(); |
+ } |
+ }); |
+ } |
+ |
+ Future pause() { |
+ return get("debug/pause").then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ return isolate.reload(); |
+ }); |
+ } |
+ |
+ Future resume() { |
+ return get("debug/resume").then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ return isolate.reload(); |
+ }); |
+ } |
+ |
+ Future stepInto() { |
+ print('isolate.stepInto'); |
+ return get("debug/resume?step=into").then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ return isolate.reload(); |
+ }); |
+ } |
+ |
+ Future stepOver() { |
+ return get("debug/resume?step=over").then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ return isolate.reload(); |
+ }); |
+ } |
+ |
+ Future stepOut() { |
+ return get("debug/resume?step=out").then((result) { |
+ if (result is DartError) { |
+ // TODO(turnidge): Handle this more gracefully. |
+ Logger.root.severe(result.message); |
+ } |
+ return isolate.reload(); |
+ }); |
+ } |
} |
/// A [ServiceObject] which implements [ObservableMap]. |
@@ -1307,8 +1447,8 @@ class ServiceFunction extends ServiceObject with Coverage { |
script = map['script']; |
tokenPos = map['tokenPos']; |
endTokenPos = map['endTokenPos']; |
- code = map['code']; |
- unoptimizedCode = map['unoptimized_code']; |
+ code = _convertNull(map['code']); |
+ unoptimizedCode = _convertNull(map['unoptimized_code']); |
isOptimizable = map['is_optimizable']; |
isInlinable = map['is_inlinable']; |
deoptimizations = map['deoptimizations']; |
@@ -1326,10 +1466,58 @@ class ServiceFunction extends ServiceObject with Coverage { |
} |
class ScriptLine extends Observable { |
+ final Script script; |
final int line; |
final String text; |
@observable int hits; |
- ScriptLine(this.line, this.text); |
+ @observable ServiceMap bpt; |
+ @observable bool possibleBpt = true; |
+ |
+ static bool _isTrivialToken(String token) { |
+ if (token == 'else') { |
+ return true; |
+ } |
+ for (var c in token.split('')) { |
+ switch (c) { |
+ case '{': |
+ case '}': |
+ case '(': |
+ case ')': |
+ case ';': |
+ break; |
+ default: |
+ return false; |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ static bool _isTrivialLine(String text) { |
+ var wsTokens = text.split(new RegExp(r"(\s)+")); |
+ for (var wsToken in wsTokens) { |
+ var tokens = wsToken.split(new RegExp(r"(\b)")); |
+ for (var token in tokens) { |
+ if (!_isTrivialToken(token)) { |
+ return false; |
+ } |
+ } |
+ } |
+ return true; |
+ } |
+ |
+ ScriptLine(this.script, this.line, this.text) { |
+ possibleBpt = !_isTrivialLine(text); |
+ |
+ // TODO(turnidge): This is not so efficient. Consider improving. |
+ for (var bpt in this.script.isolate.breakpoints['breakpoints']) { |
+ var bptScript = bpt['location']['script']; |
+ var bptTokenPos = bpt['location']['tokenPos']; |
+ if (bptScript == this.script && |
+ bptScript.tokenToLine(bptTokenPos) == line) { |
+ this.bpt = bpt; |
+ } |
+ } |
+ } |
} |
class Script extends ServiceObject with Coverage { |
@@ -1383,9 +1571,12 @@ class Script extends ServiceObject with Coverage { |
_tokenToCol.clear(); |
firstTokenPos = null; |
lastTokenPos = null; |
+ var lineSet = new Set(); |
+ |
for (var line in table) { |
// Each entry begins with a line number... |
var lineNumber = line[0]; |
+ lineSet.add(lineNumber); |
for (var pos = 1; pos < line.length; pos += 2) { |
// ...and is followed by (token offset, col number) pairs. |
var tokenOffset = line[pos]; |
@@ -1405,6 +1596,13 @@ class Script extends ServiceObject with Coverage { |
_tokenToCol[tokenOffset] = colNumber; |
} |
} |
+ |
+ for (var line in lines) { |
+ // Remove possible breakpoints on lines with no tokens. |
+ if (!lineSet.contains(line.line)) { |
+ line.possibleBpt = false; |
+ } |
+ } |
} |
void _processHits(List scriptHits) { |
@@ -1437,15 +1635,12 @@ class Script extends ServiceObject with Coverage { |
lines.clear(); |
Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}'); |
for (var i = 0; i < sourceLines.length; i++) { |
- lines.add(new ScriptLine(i + 1, sourceLines[i])); |
+ lines.add(new ScriptLine(this, i + 1, sourceLines[i])); |
} |
_applyHitsToLines(); |
} |
void _applyHitsToLines() { |
- if (lines.length == 0) { |
- return; |
- } |
for (var line in lines) { |
var hits = _hits[line.line]; |
line.hits = hits; |
@@ -1990,6 +2185,15 @@ class Socket extends ServiceObject { |
} |
} |
+// Convert any ServiceMaps representing a null instance into an actual null. |
+_convertNull(obj) { |
+ if (obj is ServiceMap && |
+ obj.serviceType == 'Null') { |
+ return null; |
+ } |
+ return obj; |
+} |
+ |
// Returns true if [map] is a service map. i.e. it has the following keys: |
// 'id' and a 'type'. |
bool _isServiceMap(ObservableMap m) { |