dbg = {}; dbg.log = log; var textCommandProcessor = {}; textCommandProcessor.break = function (str) { var md = str.match(/^b(reak)?\s+([^:]+):(\d+)/); if (!md) { return ({commandname : "break", success : false, stringResult : "command could not be parsed"}); } var scripts = dbg.scripts[md[2]], tmpScript = null; if (scripts) { var breakLine = parseInt(md[3], 10), off = -1; for (var n=0; n < scripts.length; n++) { offsets = scripts[n].getLineOffsets(breakLine); if (offsets.length > 0) { off = offsets[0]; tmpScript = scripts[n]; break; } } if (off >= 0) { tmpScript.setBreakpoint(off, breakpointHandler); return ({commandname : "break", success : true, jsfilename : md[2], breakpointlinenumber : breakLine}); } else { return ({commandname : "break", success : false, stringResult : "no valid offsets at that line"}); } } else { return ({commandname : "break", success : false, jsfilename : md[2], stringResult : "Invalid script name"}); } } textCommandProcessor.info = function (str) { var report = ""; var md = str.match(/^info\s+(\S+)/); if (md) { report += "info - NYI"; report += "\nmd[0] = " + md[0]; report += "\nmd[1] = " + md[1]; return ({commandname : "info", success : true, stringResult : report}); } else { return ({commandname : "info", success : false, stringResult : report}); } } textCommandProcessor.clear = function (str) { var report = ""; report += "clearing all breakpoints"; dbg.dbg.clearAllBreakpoints(); return ({commandname : "clear", success : true, stringResult : report}); } textCommandProcessor.scripts = function (str) { var report = "List of available scripts\n"; report += Object.keys(dbg.scripts).join("\n"); return ({commandname : "scripts", success : true, stringResult : report}); } textCommandProcessor.step = function (str, frame, script) { if (frame) { dbg.breakLine = script.getOffsetLine(frame.offset) + 1; frame.onStep = function () { stepFunction(frame, frame.script); return undefined; }; stop = true; _unlockVM(); return ({commandname : "step", success : true, stringResult : ""}); } else { return ({commandname : "step", success : false, stringResult : ""}); } } textCommandProcessor.continue = function (str, frame, script) { if (frame) { frame.onStep = undefined; dbg.breakLine = 0; } stop = true; _unlockVM(); return ({commandname : "continue", success : true, stringResult : ""}); } textCommandProcessor.deval = function (str, frame, script) { // debugger eval var md = str.match(/^deval\s+(.+)/); if (md[1]) { try { var devalReturn = eval(md[1]); if (devalReturn) { var stringreport = debugObject(devalReturn, true); return ({commandname : "deval", success : true, stringResult : stringreport}); } } catch (e) { return ({commandname : "deval", success : false, stringResult : "exception:\n" + e.message}); } } else { return ({commandname : "deval", success : false, stringResult : "could not parse script to evaluate"}); } } textCommandProcessor.eval = function (str, frame, script) { if (!frame) { return ({commandname : "eval", success : false, stringResult : "no frame to eval in"}); } var stringToEval = str.substring(4); if (stringToEval) { try { var evalResult = frame['eval']("JSON.stringify(eval(" + stringToEval + "));"); if (evalResult && evalResult['return']) { var stringreport = evalResult['return']; // var stringreport = debugObject(evalResult['return']); return ({commandname : "eval", success : true, stringResult : stringreport}); } else if (evalResult && evalResult['throw']) { return ({commandname : "eval", success : false, stringResult : "got exception: " + evalResult['throw'].message}); } else { return ({commandname : "eval", success : false, stringResult : "invalid return from eval"}); } } catch (e) { dbg.log("exception = " + e); return ({commandname : "eval", success : false, stringResult : "Exception : " + e}); } } } textCommandProcessor.line = function (str, frame, script) { if (frame) { try { return ({commandname : "line", success : true, stringResult : script.getOffsetLine(frame.offset)}); } catch (e) { return ({commandname : "line", success : false, stringResult : "exception " + e}); } } return ({commandname : "line", success : false, // probably entering script stringResult : "NOLINE"}); } textCommandProcessor.backtrace = function (str, frame, script) { if (!frame) { return ({commandname : "backtrace", success : false, stringResult : "no valid frame"}); } var result = ""; var cur = frame, stack = [cur.script.url + ":" + cur.script.getOffsetLine(cur.offset)]; while ((cur = cur.older)) { stack.push(cur.script.url + ":" + cur.script.getOffsetLine(cur.offset)); } result += stack.join("\n"); return ({commandname : "backtrace", success : true, stringResult : result}); } textCommandProcessor.uiresponse = function (str) { var subcommandstring = (str.substring("uiresponse".length)).replace(/\s+/g, ''); var response = ""; switch (subcommandstring) { case "json": dbg.responder = jsonResponder; response += "DEBUGGER UI : responding with json messages"; break; case "plaintext": dbg.responder = textResponder; response += "DEBUGGER UI : responding with plaintext messages"; break; } // note : we return an empty string // dbg.log(response); return ({commandname : "uiresponse", success : true, stringResult : ""}); } textCommandProcessor.help = function () { _printHelp(); return ({commandname : "help", success : true, stringResult : ""}); } textCommandProcessor.getCommandProcessor = function (str) { // break var md = str.match(/[a-z]*/); if (!md) { return null; } switch (md[0]) { case "b" : case "break" : return textCommandProcessor.break; case "info" : return textCommandProcessor.info; case "clear" : return textCommandProcessor.clear; case "scripts" : return textCommandProcessor.scripts; case "s" : case "step" : return textCommandProcessor.step; case "c" : case "continue" : return textCommandProcessor.continue; case "deval" : return textCommandProcessor.deval; case "eval" : return textCommandProcessor.eval; case "line" : return textCommandProcessor.line; case "bt" : return textCommandProcessor.backtrace; case "uiresponse" : return textCommandProcessor.uiresponse; case "help" : return textCommandProcessor.help; default : return null; } } // JSON output var jsonResponder = {}; jsonResponder.write = function (str) { _bufferWrite(str); _bufferWrite("\n"); _bufferWrite(String.fromCharCode(23)); } jsonResponder.onBreakpoint = function (filename, linenumber) { var response = {"from" : "server", "why" : "onBreakpoint", "data" : {"jsfilename" : filename, "linenumber" : linenumber}}; this.write(JSON.stringify(response)); } jsonResponder.onStep = function (filename, linenumber) { var response = {"from" : "server", "why" : "onStep", "data" : {"jsfilename" : filename, "linenumber" : linenumber}}; this.write(JSON.stringify(response)); } jsonResponder.commandResponse = function (commandresult) { var response = {"from" : "server", "why" : "commandresponse", "data" : commandresult}; this.write(JSON.stringify(response)); } jsonResponder.commandNotFound = function () { // do nothing } // Plain Old Text output var textResponder = {}; textResponder.write = function (str) { _bufferWrite(str); _bufferWrite("\n"); _bufferWrite(String.fromCharCode(23)); } textResponder.onBreakpoint = function (filename, linenumber) { var shortFilename = filename.substring(filename.lastIndexOf("/") + 1); var response = "Breakpoint hit at " + shortFilename + " line number : " + linenumber; this.write(response); } textResponder.onStep = function (filename, linenumber) { var shortFilename = filename.substring(filename.lastIndexOf("/") + 1); var response = "Stopped at " + shortFilename + " line number : " + linenumber; this.write(response); } textResponder.commandResponse = function (commandresult) { var response = ""; try { switch (commandresult.commandname) { case "break" : if (!commandresult.success) { response += "ERROR : " + commandresult.stringResult; } break; case "info" : if (!commandresult.success) { response += "ERROR : " + commandresult.stringResult; } break; case "clear" : break; case "scripts" : if (true === commandresult.success) { response += commandresult.stringResult; } break; case "step" : if (!commandresult.success) { response += "ERROR : step failed " + commandresult.stringResult; } break; case "continue" : if (!commandresult.success) { response += "ERROR : continue failed " + commandresult.stringResult; } break; case "deval" : if (true === commandresult.success) { response += commandresult.stringResult; } else { response += "ERROR : deval failed " + commandresult.stringResult; } break; case "eval" : if (true === commandresult.success) { response += commandresult.stringResult; } else { response += "ERROR : eval failed " + commandresult.stringResult; } break; case "line" : if (true === commandresult.success) { response += commandresult.stringResult; } else { response += "ERROR : " + commandresult.stringResult; } break; case "backtrace" : if (true === commandresult.success) { response += commandresult.stringResult; } else { response += "ERROR : " + commandresult.stringResult; } break; case "help" : break; } } catch (e) { response += "\nException logging response " + e; } this.write(response); } textResponder.commandNotFound = function () { _printCommandNotFound(); } var breakpointHandler = { hit: function (frame) { try { dbg.responder.onBreakpoint(frame.script.url, frame.script.getOffsetLine(frame.offset)); } catch (e) { dbg.log("exception " + e); } var script = frame.script; _lockVM(frame, frame.script); } }; var stepFunction = function (frame, script) { if (dbg.breakLine > 0) { var curLine = script.getOffsetLine(frame.offset); if (curLine < dbg.breakLine) { return; } else { try { dbg.responder.onStep(frame.script.url, frame.script.getOffsetLine(frame.offset)); } catch (e) { dbg.log("exception " + e); } _lockVM(frame, script); // dbg.breakLine = 0; // frame.onStep = undefined; } } else { dbg.log("invalid state onStep"); } }; var debugObject = function (r, isNormal) { var stringres = ""; try { stringres += "* " + (typeof r) + "\n"; if (typeof r != "object") { stringres += "~> " + r + "\n"; } else { var props; if (isNormal) { props = Object.keys(r); } else { props = r.getOwnPropertyNames(); } for (k in props) { var desc = r.getOwnPropertyDescriptor(props[k]); stringres += "~> " + props[k] + " = "; if (desc.value) { stringres += "" + desc.value; } else if (desc.get) { stringres += "" + desc.get(); } else { stringres += "undefined (no value or getter)"; } stringres += "\n"; } } return stringres; } catch (e) { return ("Exception when accessing object properties = " + e); } } dbg.breakLine = 0; this.processInput = function (inputstr, frame, script) { var command_func; var command_return; var commands_array = []; var _command; var i; if (!inputstr) { return; } // remove Carriage Return's inputstr = inputstr.replace(/\r+/, ""); // split into an array using Line Feed as the delimiter commands_array = inputstr.split("\n"); // trace the commands received // dbg.log("received " + commands_array.length + " commands:"); // for (i = 0; i < commands_array.length; i++) { // if (i in commands_array) { // dbg.log("~~~ commandstring =" + commands_array[i]); // dbg.log(" commandstring.length = " + commands_array[i].length); // } // } for (i = 0; i < commands_array.length; i++) { if (i in commands_array) { _command = commands_array[i]; if (_command === "") { // dbg.log("Empty input. Ignoring."); } else { // dbg.log(_command); command_func = dbg.getCommandProcessor(_command); if (!command_func) { dbg.log("did not find a command processor!"); dbg.responder.commandNotFound(); } else { try { command_return = command_func(_command, frame, script); if (true === command_return.success) { dbg.responder.commandResponse(command_return); } else { dbg.log("command failed. return value = " + command_return.stringResult); dbg.responder.commandResponse(command_return); } } catch (e) { dbg.log("Exception in command processing. e =\n" + e + "\n"); var _output = {success : false, commandname : command_func.name, stringResult : e}; dbg.responder.commandResponse(_output); } } } } } }; _printCommandNotFound = function() { var str = "ERROR : command not found!\n"; _bufferWrite(str); }; _printHelp = function() { var help = "break filename:numer\tAdds a breakpoint at a given filename and line number\n" + "clear\tClear all breakpoints\n" + "c / continue\tContinues the execution\n" + "s / step\tStep\n" + "bt\tBacktrace\n" + "scripts\tShow the scripts\n" + "line\tShows current line\n" + "eval js_command\tEvaluates JS code\n" + "deval js_command\tEvaluates JS Debugger command\n" + "uiresponse [json|plaintext] Switch between JSON and plaintext output from the debugger\n"; _bufferWrite(help); }; dbg.scripts = []; dbg.onNewScript = function (script) { // skip if the url is this script var last = script.url.split("/").pop(); var children = script.getChildScripts(), arr = [script].concat(children); /** * just dumping all the offsets from the scripts for (var i in arr) { dbg.log("script: " + arr[i].url); for (var start=arr[i].startLine, j=start; j < start+arr[i].lineCount; j++) { var offsets = arr[i].getLineOffsets(j); dbg.log(" off: " + offsets.join(",") + "; line: " + j); } } */ dbg.scripts[last] = arr; }; dbg.onError = function (frame, report) { if (dbg.socket && report) { _socketWrite(dbg.socket, "!! exception @ " + report.file + ":" + report.line); } dbg.log("!! exception"); }; this._prepareDebugger = function (global) { var tmp = new Debugger(global); tmp.onNewScript = dbg.onNewScript; tmp.onDebuggerStatement = dbg.onDebuggerStatement; tmp.onError = dbg.onError; dbg.dbg = tmp; // use the text command processor at startup dbg.getCommandProcessor = textCommandProcessor.getCommandProcessor; // use the text responder at startup dbg.responder = textResponder; }; this._startDebugger = function (global, files, startFunc) { // dbg.log("[DBG] starting debug session"); for (var i in files) { try { global['eval']("require('" + files[i] + "');"); } catch (e) { dbg.log("[DBG] error evaluating file: " + files[i]); } } // dbg.log("[DBG] all files required"); if (startFunc) { // dbg.log("executing start func: " + startFunc); global['eval'](startFunc); } // beginDebug(); }