Index: tests/standalone/debugger/debug_lib.dart |
diff --git a/tests/standalone/debugger/debug_lib.dart b/tests/standalone/debugger/debug_lib.dart |
deleted file mode 100644 |
index 92a0d103c096dc47007b9ffcd15ee7c19844184d..0000000000000000000000000000000000000000 |
--- a/tests/standalone/debugger/debug_lib.dart |
+++ /dev/null |
@@ -1,749 +0,0 @@ |
-// Copyright (c) 2013, the Dart 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 file. |
- |
-// Library used by debugger wire protocol tests (standalone VM debugging). |
- |
-library DartDebugger; |
- |
-import "dart:async"; |
-import "dart:convert"; |
-import "dart:io"; |
-import "dart:math"; |
- |
-// Whether or not to print the debugger wire messages on the console. |
-var verboseWire = false; |
- |
-// Class to buffer wire protocol data from debug target and |
-// break it down to individual json messages. |
-class JsonBuffer { |
- String buffer = null; |
- |
- append(String s) { |
- if (buffer == null || buffer.length == 0) { |
- buffer = s; |
- } else { |
- buffer += s; |
- } |
- } |
- |
- String getNextMessage() { |
- if (buffer == null) return null; |
- int msgLen = objectLength(); |
- if (msgLen == 0) return null; |
- String msg = null; |
- if (msgLen == buffer.length) { |
- msg = buffer; |
- buffer = null; |
- } else { |
- assert(msgLen < buffer.length); |
- msg = buffer.substring(0, msgLen); |
- buffer = buffer.substring(msgLen); |
- } |
- return msg; |
- } |
- |
- bool haveGarbage() { |
- if (buffer == null || buffer.length == 0) return false; |
- var i = 0, char = " "; |
- while (i < buffer.length) { |
- char = buffer[i]; |
- if (char != " " && char != "\n" && char != "\r" && char != "\t") break; |
- i++; |
- } |
- if (i >= buffer.length) { |
- return false; |
- } else { |
- return char != "{"; |
- } |
- } |
- |
- // Returns the character length of the next json message in the |
- // buffer, or 0 if there is only a partial message in the buffer. |
- // The object value must start with '{' and continues to the |
- // matching '}'. No attempt is made to otherwise validate the contents |
- // as JSON. If it is invalid, a later JSON.decode() will fail. |
- int objectLength() { |
- int skipWhitespace(int index) { |
- while (index < buffer.length) { |
- String char = buffer[index]; |
- if (char != " " && char != "\n" && char != "\r" && char != "\t") break; |
- index++; |
- } |
- return index; |
- } |
- int skipString(int index) { |
- assert(buffer[index - 1] == '"'); |
- while (index < buffer.length) { |
- String char = buffer[index]; |
- if (char == '"') return index + 1; |
- if (char == r'\') index++; |
- if (index == buffer.length) return index; |
- index++; |
- } |
- return index; |
- } |
- int index = 0; |
- index = skipWhitespace(index); |
- // Bail out if the first non-whitespace character isn't '{'. |
- if (index == buffer.length || buffer[index] != '{') return 0; |
- int nesting = 0; |
- while (index < buffer.length) { |
- String char = buffer[index++]; |
- if (char == '{') { |
- nesting++; |
- } else if (char == '}') { |
- nesting--; |
- if (nesting == 0) return index; |
- } else if (char == '"') { |
- // Strings can contain braces. Skip their content. |
- index = skipString(index); |
- } |
- } |
- return 0; |
- } |
-} |
- |
- |
-getJsonValue(Map jsonMsg, String path) { |
- List properties = path.split(new RegExp(":")); |
- assert(properties.length >= 1); |
- var node = jsonMsg; |
- for (int i = 0; i < properties.length; i++) { |
- if (node == null) return null; |
- String property = properties[i]; |
- var index = null; |
- if (property.endsWith("]")) { |
- var bracketPos = property.lastIndexOf("["); |
- if (bracketPos <= 0) return null; |
- var indexStr = property.substring(bracketPos + 1, property.length - 1); |
- try { |
- index = int.parse(indexStr); |
- } on FormatException { |
- print("$indexStr is not a valid array index"); |
- return null; |
- } |
- property = property.substring(0, bracketPos); |
- } |
- if (node is Map) { |
- node = node[property]; |
- } else { |
- return null; |
- } |
- if (index != null) { |
- if (node is List && node.length > index) { |
- node = node[index]; |
- } else { |
- return null; |
- } |
- } |
- } |
- return node; |
-} |
- |
- |
-// Returns true if [template] is a subset of [map]. |
-bool matchMaps(Map template, Map msg) { |
- bool isMatch = true; |
- template.forEach((k, v) { |
- if (msg.containsKey(k)) { |
- var receivedValue = msg[k]; |
- if ((v is Map) && (receivedValue is Map)) { |
- if (!matchMaps(v, receivedValue)) isMatch = false; |
- } else if (v == null) { |
- // null in the template matches everything. |
- } else if (v != receivedValue) { |
- isMatch = false; |
- } |
- } else { |
- isMatch = false; |
- } |
- }); |
- return isMatch; |
-} |
- |
- |
-class Command { |
- var template; |
- |
- void send(Debugger debugger) { |
- debugger.sendMessage(template); |
- } |
- |
- void matchResponse(Debugger debugger) { |
- Map response = debugger.currentMessage; |
- var id = template["id"]; |
- assert(id != null && id >= 0); |
- if (response["id"] != id) { |
- debugger.error("Error: expected messaged id $id but got ${response["id"]}."); |
- } |
- } |
-} |
- |
-class GetLineTableCmd extends Command { |
- GetLineTableCmd() { |
- template = {"id": 0, |
- "command": "getLineNumberTable", |
- "params": {"isolateId": 0, "libraryId": 0, "url": ""}}; |
- } |
- |
- void send(Debugger debugger) { |
- assert(debugger.scriptUrl != null); |
- template["params"]["url"] = debugger.scriptUrl; |
- template["params"]["libraryId"] = debugger.libraryId; |
- debugger.sendMessage(template); |
- } |
- |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- List<List<int>> table = getJsonValue(debugger.currentMessage, "result:lines"); |
- debugger.tokenToLine = {}; |
- for (var line in table) { |
- // Each entry begins with a line number... |
- var lineNumber = line[0]; |
- for (var pos = 1; pos < line.length; pos += 2) { |
- // ...and is followed by (token offset, col number) pairs. |
- var tokenOffset = line[pos]; |
- debugger.tokenToLine[tokenOffset] = lineNumber; |
- } |
- } |
- } |
-} |
- |
- |
-class LineMatcher extends Command { |
- int expectedLineNumber; |
- |
- LineMatcher(this.expectedLineNumber) { |
- template = {"id": 0, "command": "getStackTrace", "params": {"isolateId": 0}}; |
- } |
- |
- void matchResponse(Debugger debugger) { |
- assert(debugger.tokenToLine != null); |
- super.matchResponse(debugger); |
- var msg = debugger.currentMessage; |
- List frames = getJsonValue(msg, "result:callFrames"); |
- assert(frames != null); |
- var tokenOffset = frames[0]["location"]["tokenOffset"]; |
- assert(tokenOffset != null); |
- var lineNumber = debugger.tokenToLine[tokenOffset]; |
- assert(lineNumber != null); |
- if (expectedLineNumber != lineNumber) { |
- debugger.error("Error: expected pause at line $expectedLineNumber " |
- "but reported line is $lineNumber."); |
- return; |
- } |
- print("Matched line number $lineNumber"); |
- } |
-} |
- |
-MatchLine(lineNumber) { |
- return new LineMatcher(lineNumber); |
-} |
- |
- |
-class FrameMatcher extends Command { |
- int frameIndex; |
- List<String> functionNames; |
- bool exactMatch; |
- |
- FrameMatcher(this.frameIndex, this.functionNames, this.exactMatch) { |
- template = {"id": 0, "command": "getStackTrace", "params": {"isolateId": 0}}; |
- } |
- |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- var msg = debugger.currentMessage; |
- List frames = getJsonValue(msg, "result:callFrames"); |
- assert(frames != null); |
- if (debugger.scriptUrl == null) { |
- var name = frames[0]["functionName"]; |
- if (name == "main") { |
- // Extract script url of debugged script. |
- debugger.scriptUrl = frames[0]["location"]["url"]; |
- assert(debugger.scriptUrl != null); |
- debugger.libraryId = frames[0]["location"]["libraryId"]; |
- assert(debugger.libraryId != null); |
- } |
- } |
- if (frames.length < functionNames.length) { |
- debugger.error("Error: stack trace not long enough " |
- "to match ${functionNames.length} frames"); |
- return; |
- } |
- for (int i = 0; i < functionNames.length; i++) { |
- var idx = i + frameIndex; |
- var name = frames[idx]["functionName"]; |
- assert(name != null); |
- bool isMatch = exactMatch ? name == functionNames[i] |
- : name.contains(functionNames[i]); |
- if (!isMatch) { |
- debugger.error("Error: call frame $idx: " |
- "expected function name '${functionNames[i]}' but found '$name'"); |
- return; |
- } |
- } |
- print("Matched frames: $functionNames"); |
- } |
-} |
- |
- |
-MatchFrame(int frameIndex, String functionName, {exactMatch: false}) { |
- return new FrameMatcher(frameIndex, [functionName], exactMatch); |
-} |
- |
-MatchFrames(List<String> functionNames, {exactMatch: false}) { |
- return new FrameMatcher(0, functionNames, exactMatch); |
-} |
- |
- |
-class LocalsMatcher extends Command { |
- Map locals = {}; |
- |
- LocalsMatcher(this.locals) { |
- template = {"id": 0, "command": "getStackTrace", "params": {"isolateId": 0}}; |
- } |
- |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- |
- List frames = getJsonValue(debugger.currentMessage, "result:callFrames"); |
- assert(frames != null); |
- |
- String functionName = frames[0]['functionName']; |
- List localsList = frames[0]['locals']; |
- Map reportedLocals = {}; |
- localsList.forEach((local) => reportedLocals[local['name']] = local['value']); |
- for (String key in locals.keys) { |
- if (reportedLocals[key] == null) { |
- debugger.error("Error in $functionName(): no value reported for local " |
- "variable $key"); |
- return; |
- } |
- String expected = locals[key]; |
- String actual = reportedLocals[key]['text']; |
- if (expected != actual) { |
- debugger.error("Error in $functionName(): got '$actual' for local " |
- "variable $key, but expected '$expected'"); |
- return; |
- } |
- } |
- print("Matched locals ${locals.keys}"); |
- } |
-} |
- |
- |
-MatchLocals(Map localValues) { |
- return new LocalsMatcher(localValues); |
-} |
- |
- |
-// Used to check if local variables are visible. |
-class AssertLocalsNotVisibleMatcher extends Command { |
- List<String> locals; |
- int frame_index; |
- |
- AssertLocalsNotVisibleMatcher(this.locals, this.frame_index) { |
- template = {"id": 0, "command": "getStackTrace", "params": {"isolateId": 0}}; |
- } |
- |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- |
- List frames = getJsonValue(debugger.currentMessage, "result:callFrames"); |
- assert(frames != null); |
- |
- String functionName = frames[frame_index]['functionName']; |
- List localsList = frames[frame_index]['locals']; |
- Map reportedLocals = {}; |
- localsList.forEach((local) => reportedLocals[local['name']] = local['value']); |
- for (String key in locals) { |
- if (reportedLocals[key] != null) { |
- debugger.error("Error in $functionName(): local variable $key not " |
- "expected in scope (reported value " |
- "${reportedLocals[key]['text']})"); |
- return; |
- } |
- } |
- print("Matched locals $locals"); |
- } |
-} |
- |
- |
-AssertLocalsNotVisible(List<String> locals, [int frame_index = 0]) { |
- return new AssertLocalsNotVisibleMatcher(locals, frame_index); |
-} |
- |
- |
-class EventMatcher { |
- String eventName; |
- Map params; |
- |
- EventMatcher(this.eventName, this.params); |
- |
- void matchEvent(Debugger debugger) { |
- for (Event event in debugger.events) { |
- if (event.name == eventName) { |
- if (params == null || matchMaps(params, event.params)) { |
- // Remove the matched event, so we don't match against it in the future. |
- debugger.events.remove(event); |
- return; |
- } |
- } |
- } |
- |
- String msg = params == null ? '' : params.toString(); |
- debugger.error("Error: could not match event $eventName $msg"); |
- } |
-} |
- |
- |
-ExpectEvent(String eventName, [Map params]) { |
- return new EventMatcher(eventName, params); |
-} |
- |
- |
-class RunCommand extends Command { |
- RunCommand.resume() { |
- template = {"id": 0, "command": "resume", "params": {"isolateId": 0}}; |
- } |
- RunCommand.step() { |
- template = {"id": 0, "command": "stepOver", "params": {"isolateId": 0}}; |
- } |
- RunCommand.stepInto() { |
- template = {"id": 0, "command": "stepInto", "params": {"isolateId": 0}}; |
- } |
- RunCommand.stepOut() { |
- template = {"id": 0, "command": "stepOut", "params": {"isolateId": 0}}; |
- } |
- void send(Debugger debugger) { |
- debugger.sendMessage(template); |
- debugger.isPaused = false; |
- } |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- print("Command: ${template['command']}"); |
- } |
-} |
- |
- |
-Resume() => new RunCommand.resume(); |
-Step() => new RunCommand.step(); |
-StepInto() => new RunCommand.stepInto(); |
-StepOut() => new RunCommand.stepOut(); |
- |
-class SetBreakpointCommand extends Command { |
- int line; |
- String url; |
- SetBreakpointCommand(this.line, this.url) { |
- template = {"id": 0, |
- "command": "setBreakpoint", |
- "params": { "isolateId": 0, |
- "url": null, |
- "line": null }}; |
- } |
- |
- void send(Debugger debugger) { |
- assert(debugger.scriptUrl != null); |
- if (url == null) { |
- url = debugger.scriptUrl; |
- } |
- template["params"]["url"] = url; |
- template["params"]["line"] = line; |
- debugger.sendMessage(template); |
- } |
- |
- void matchResponse(Debugger debugger) { |
- super.matchResponse(debugger); |
- print("Set breakpoint at line $line in $url"); |
- } |
-} |
- |
-SetBreakpoint(int line, {String url}) => new SetBreakpointCommand(line, url); |
- |
-class Event { |
- String name; |
- Map params; |
- |
- Event(Map json) { |
- name = json['event']; |
- params = json['params']; |
- } |
-} |
- |
- |
-// A debug script is a list of Command objects. |
-class DebugScript { |
- List entries; |
- DebugScript(List scriptEntries) { |
- entries = new List.from(scriptEntries.reversed); |
- entries.add(new GetLineTableCmd()); |
- entries.add(MatchFrame(0, "main")); |
- } |
- bool get isEmpty => entries.isEmpty; |
- bool get isNextEventMatcher => !isEmpty && currentEntry is EventMatcher; |
- get currentEntry => entries.last; |
- advance() => entries.removeLast(); |
- add(entry) => entries.add(entry); |
-} |
- |
- |
-class Debugger { |
- // Debug target process properties. |
- Process targetProcess; |
- Socket socket; |
- JsonBuffer responses = new JsonBuffer(); |
- |
- DebugScript script; |
- int seqNr = 0; // Sequence number of next debugger command message. |
- Command lastCommand = null; // Most recent command sent to target. |
- List<String> errors = new List(); |
- List<Event> events = new List(); |
- bool cleanupDone = false; |
- |
- // Data collected from debug target. |
- Map currentMessage = null; // Currently handled message sent by target. |
- String scriptUrl = null; |
- int libraryId = null; |
- Map<int,int> tokenToLine = null; |
- bool shutdownEventSeen = false; |
- int isolateId = 0; |
- bool isPaused = false; |
- |
- Debugger(this.targetProcess, this.script) { |
- var stdoutStringStream = targetProcess.stdout |
- .transform(UTF8.decoder) |
- .transform(new LineSplitter()); |
- stdoutStringStream.listen((line) { |
- print("TARG: $line"); |
- if (line.startsWith("Debugger listening")) { |
- RegExp portExpr = new RegExp(r"\d+"); |
- var port = portExpr.stringMatch(line); |
- print("Debug target found listening at port '$port'"); |
- openConnection(int.parse(port)); |
- } |
- }); |
- |
- var stderrStringStream = targetProcess.stderr |
- .transform(UTF8.decoder) |
- .transform(new LineSplitter()); |
- stderrStringStream.listen((line) { |
- print("TARG: $line"); |
- }); |
- } |
- |
- // Handle debugger events, updating the debugger state. |
- void handleEvent(Map<String,dynamic> msg) { |
- events.add(new Event(msg)); |
- |
- if (msg["event"] == "isolate") { |
- if (msg["params"]["reason"] == "created") { |
- isolateId = msg["params"]["id"]; |
- assert(isolateId != null); |
- print("Debuggee isolate id $isolateId created."); |
- } else if (msg["params"]["reason"] == "shutdown") { |
- print("Debuggee isolate id ${msg["params"]["id"]} shut down."); |
- shutdownEventSeen = true; |
- if (!script.isEmpty) { |
- error("Error: premature isolate shutdown event seen."); |
- error("Next expected event: ${script.currentEntry}"); |
- } |
- } |
- } else if (msg["event"] == "breakpointResolved") { |
- var bpId = msg["params"]["breakpointId"]; |
- assert(bpId != null); |
- var isolateId = msg["params"]["isolateId"]; |
- assert(isolateId != null); |
- var location = msg["params"]["location"]; |
- assert(location != null); |
- print("Isolate $isolateId: breakpoint $bpId resolved" |
- " at location $location"); |
- // We may want to maintain a table of breakpoints in the future. |
- } else if (msg["event"] == "paused") { |
- isPaused = true; |
- } else { |
- error("Error: unknown debugger event received"); |
- } |
- } |
- |
- // Handle one JSON message object and match it to the |
- // expected events and responses in the debugging script. |
- void handleMessage(Map<String,dynamic> receivedMsg) { |
- currentMessage = receivedMsg; |
- if (receivedMsg["event"] != null) { |
- handleEvent(receivedMsg); |
- if (errorsDetected) { |
- error("Error while handling debugger event"); |
- error("Event received from debug target: $receivedMsg"); |
- } |
- } else if (receivedMsg["id"] != null) { |
- // This is a response to the last command we sent. |
- assert(lastCommand != null); |
- lastCommand.matchResponse(this); |
- lastCommand = null; |
- if (errorsDetected) { |
- error("Error while matching response to debugger command"); |
- error("Response received from debug target: $receivedMsg"); |
- } |
- } |
- } |
- |
- // Send next debugger command in the script, if a response |
- // from the last command has been received and processed. |
- void sendNextCommand() { |
- while (script.isNextEventMatcher) { |
- EventMatcher matcher = script.currentEntry; |
- script.advance(); |
- matcher.matchEvent(this); |
- } |
- |
- if (lastCommand == null) { |
- if (script.currentEntry is Command) { |
- script.currentEntry.send(this); |
- lastCommand = script.currentEntry; |
- seqNr++; |
- script.advance(); |
- } |
- } |
- } |
- |
- // Handle data received over the wire from the debug target |
- // process. Split input from JSON wire format into individual |
- // message objects (maps). |
- void handleMessages() { |
- var msg = responses.getNextMessage(); |
- while (msg != null) { |
- if (verboseWire) print("RECV: $msg"); |
- if (responses.haveGarbage()) { |
- error("Error: leftover text after message: '${responses.buffer}'"); |
- error("Previous message may be malformed, was: '$msg'"); |
- cleanup(); |
- return; |
- } |
- var msgObj = JSON.decode(msg); |
- handleMessage(msgObj); |
- if (errorsDetected) { |
- error("Error while handling script entry"); |
- error("Message received from debug target: $msg"); |
- cleanup(); |
- return; |
- } |
- if (shutdownEventSeen) { |
- cleanup(); |
- return; |
- } |
- if (isPaused) sendNextCommand(); |
- msg = responses.getNextMessage(); |
- } |
- } |
- |
- // Send a debugger command to the target VM. |
- void sendMessage(Map<String,dynamic> msg) { |
- if (msg["id"] != null) { |
- msg["id"] = seqNr; |
- } |
- if (msg["params"] != null && msg["params"]["isolateId"] != null) { |
- msg["params"]["isolateId"] = isolateId; |
- } |
- String jsonMsg = JSON.encode(msg); |
- if (verboseWire) print("SEND: $jsonMsg"); |
- socket.write(jsonMsg); |
- } |
- |
- bool get errorsDetected => errors.length > 0; |
- |
- // Record error message. |
- void error(String s) { |
- errors.add(s); |
- } |
- |
- void openConnection(int portNumber) { |
- Socket.connect("127.0.0.1", portNumber).then((s) { |
- s.setOption(SocketOption.TCP_NODELAY, true); |
- this.socket = s; |
- var stringStream = socket.transform(UTF8.decoder); |
- stringStream.listen((str) { |
- try { |
- responses.append(str); |
- handleMessages(); |
- } catch(e, trace) { |
- print("Unexpected exception:\n$e\n$trace"); |
- cleanup(); |
- } |
- }, |
- onDone: () { |
- print("Connection closed by debug target"); |
- cleanup(); |
- }, |
- onError: (e, trace) { |
- print("Error '$e' detected in input stream from debug target"); |
- if (trace != null) print("StackTrace: $trace"); |
- cleanup(); |
- }); |
- }, |
- onError: (e, trace) { |
- String msg = "Error while connecting to debugee: $e"; |
- if (trace != null) msg += "\nStackTrace: $trace"; |
- error(msg); |
- cleanup(); |
- }); |
- } |
- |
- void cleanup() { |
- if (cleanupDone) return; |
- if (socket != null) { |
- socket.close().catchError((error) { |
- // Print this directly in addition to adding it to the |
- // error message queue, in case the error message queue |
- // gets printed before this error handler is called. |
- print("Error occurred while closing socket: $error"); |
- error("Error while closing socket: $error"); |
- }); |
- } |
- var targetPid = targetProcess.pid; |
- if (errorsDetected || !shutdownEventSeen) { |
- print("Sending kill signal to process $targetPid..."); |
- targetProcess.kill(); |
- } |
- // If the process was already dead, exitCode is |
- // available and we call exit() in the next event loop cycle. |
- // Otherwise this will wait for the process to exit. |
- targetProcess.exitCode.then((exitCode) { |
- print("process $targetPid terminated with exit code $exitCode."); |
- if (exitCode != 0) { |
- error("Error: target process died with exit code $exitCode"); |
- } |
- if (errorsDetected) { |
- print("\n===== Errors detected: ====="); |
- for (int i = 0; i < errors.length; i++) print(errors[i]); |
- print("============================\n"); |
- } |
- exit(errors.length); |
- }); |
- cleanupDone = true; |
- } |
-} |
- |
- |
-bool RunScript(List script, List<String> arguments) { |
- if (arguments.contains("--debuggee")) { |
- return false; |
- } |
- verboseWire = arguments.contains("--wire"); |
- |
- // Port number 0 means debug target picks a free port dynamically. |
- var targetOpts = [ "--debug:0" ]; |
- if (arguments.contains("--verbose")) { |
- targetOpts.add("--verbose_debug"); |
- } |
- targetOpts.add(Platform.script.toFilePath()); |
- targetOpts.add("--debuggee"); |
- print('args: ${targetOpts.join(" ")}'); |
- |
- Process.start(Platform.executable, targetOpts).then((Process process) { |
- print("Debug target process started, pid ${process.pid}."); |
- process.stdin.close(); |
- var debugger = new Debugger(process, new DebugScript(script)); |
- }); |
- return true; |
-} |