js/examples/jorendb.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sw=4 et tw=78:
michael@0 3 *
michael@0 4 * jorendb - A toy command-line debugger for shell-js programs.
michael@0 5 *
michael@0 6 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 7 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 9 */
michael@0 10
michael@0 11 /*
michael@0 12 * jorendb is a simple command-line debugger for shell-js programs. It is
michael@0 13 * intended as a demo of the Debugger object (as there are no shell js programs
michael@0 14 * to speak of).
michael@0 15 *
michael@0 16 * To run it: $JS -d path/to/this/file/jorendb.js
michael@0 17 * To run some JS code under it, try:
michael@0 18 * (jorendb) print load("my-script-to-debug.js")
michael@0 19 * Execution will stop at debugger statements and you'll get a jorendb prompt.
michael@0 20 */
michael@0 21
michael@0 22 // Debugger state.
michael@0 23 var focusedFrame = null;
michael@0 24 var topFrame = null;
michael@0 25 var debuggeeValues = {};
michael@0 26 var nextDebuggeeValueIndex = 1;
michael@0 27 var lastExc = null;
michael@0 28
michael@0 29 // Cleanup functions to run when we next re-enter the repl.
michael@0 30 var replCleanups = [];
michael@0 31
michael@0 32 // Convert a debuggee value v to a string.
michael@0 33 function dvToString(v) {
michael@0 34 return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
michael@0 35 }
michael@0 36
michael@0 37 function showDebuggeeValue(dv) {
michael@0 38 var dvrepr = dvToString(dv);
michael@0 39 var i = nextDebuggeeValueIndex++;
michael@0 40 debuggeeValues["$" + i] = dv;
michael@0 41 print("$" + i + " = " + dvrepr);
michael@0 42 }
michael@0 43
michael@0 44 Object.defineProperty(Debugger.Frame.prototype, "num", {
michael@0 45 configurable: true,
michael@0 46 enumerable: false,
michael@0 47 get: function () {
michael@0 48 var i = 0;
michael@0 49 for (var f = topFrame; f && f !== this; f = f.older)
michael@0 50 i++;
michael@0 51 return f === null ? undefined : i;
michael@0 52 }
michael@0 53 });
michael@0 54
michael@0 55 Debugger.Frame.prototype.frameDescription = function frameDescription() {
michael@0 56 if (this.type == "call")
michael@0 57 return ((this.callee.name || '<anonymous>') +
michael@0 58 "(" + this.arguments.map(dvToString).join(", ") + ")");
michael@0 59 else
michael@0 60 return this.type + " code";
michael@0 61 }
michael@0 62
michael@0 63 Debugger.Frame.prototype.positionDescription = function positionDescription() {
michael@0 64 if (this.script) {
michael@0 65 var line = this.script.getOffsetLine(this.offset);
michael@0 66 if (this.script.url)
michael@0 67 return this.script.url + ":" + line;
michael@0 68 return "line " + line;
michael@0 69 }
michael@0 70 return null;
michael@0 71 }
michael@0 72
michael@0 73 Debugger.Frame.prototype.fullDescription = function fullDescription() {
michael@0 74 var fr = this.frameDescription();
michael@0 75 var pos = this.positionDescription();
michael@0 76 if (pos)
michael@0 77 return fr + ", " + pos;
michael@0 78 return fr;
michael@0 79 }
michael@0 80
michael@0 81 Object.defineProperty(Debugger.Frame.prototype, "line", {
michael@0 82 configurable: true,
michael@0 83 enumerable: false,
michael@0 84 get: function() {
michael@0 85 if (this.script)
michael@0 86 return this.script.getOffsetLine(this.offset);
michael@0 87 else
michael@0 88 return null;
michael@0 89 }
michael@0 90 });
michael@0 91
michael@0 92 function callDescription(f) {
michael@0 93 return ((f.callee.name || '<anonymous>') +
michael@0 94 "(" + f.arguments.map(dvToString).join(", ") + ")");
michael@0 95 }
michael@0 96
michael@0 97 function showFrame(f, n) {
michael@0 98 if (f === undefined || f === null) {
michael@0 99 f = focusedFrame;
michael@0 100 if (f === null) {
michael@0 101 print("No stack.");
michael@0 102 return;
michael@0 103 }
michael@0 104 }
michael@0 105 if (n === undefined) {
michael@0 106 n = f.num;
michael@0 107 if (n === undefined)
michael@0 108 throw new Error("Internal error: frame not on stack");
michael@0 109 }
michael@0 110
michael@0 111 print('#' + n + " " + f.fullDescription());
michael@0 112 }
michael@0 113
michael@0 114 function saveExcursion(fn) {
michael@0 115 var tf = topFrame, ff = focusedFrame;
michael@0 116 try {
michael@0 117 return fn();
michael@0 118 } finally {
michael@0 119 topFrame = tf;
michael@0 120 focusedFrame = ff;
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 // Evaluate an expression in the Debugger global
michael@0 125 function evalCommand(expr) {
michael@0 126 eval(expr);
michael@0 127 }
michael@0 128
michael@0 129 function quitCommand() {
michael@0 130 dbg.enabled = false;
michael@0 131 quit(0);
michael@0 132 }
michael@0 133
michael@0 134 function backtraceCommand() {
michael@0 135 if (topFrame === null)
michael@0 136 print("No stack.");
michael@0 137 for (var i = 0, f = topFrame; f; i++, f = f.older)
michael@0 138 showFrame(f, i);
michael@0 139 }
michael@0 140
michael@0 141 function printCommand(rest) {
michael@0 142 // This is the real deal.
michael@0 143 var cv = saveExcursion(
michael@0 144 () => focusedFrame == null
michael@0 145 ? debuggeeGlobalWrapper.evalInGlobalWithBindings(rest, debuggeeValues)
michael@0 146 : focusedFrame.evalWithBindings(rest, debuggeeValues));
michael@0 147 if (cv === null) {
michael@0 148 if (!dbg.enabled)
michael@0 149 return [cv];
michael@0 150 print("Debuggee died.");
michael@0 151 } else if ('return' in cv) {
michael@0 152 if (!dbg.enabled)
michael@0 153 return [undefined];
michael@0 154 showDebuggeeValue(cv.return);
michael@0 155 } else {
michael@0 156 if (!dbg.enabled)
michael@0 157 return [cv];
michael@0 158 print("Exception caught. (To rethrow it, type 'throw'.)");
michael@0 159 lastExc = cv.throw;
michael@0 160 showDebuggeeValue(lastExc);
michael@0 161 }
michael@0 162 }
michael@0 163
michael@0 164 function detachCommand() {
michael@0 165 dbg.enabled = false;
michael@0 166 return [undefined];
michael@0 167 }
michael@0 168
michael@0 169 function continueCommand() {
michael@0 170 if (focusedFrame === null) {
michael@0 171 print("No stack.");
michael@0 172 return;
michael@0 173 }
michael@0 174 return [undefined];
michael@0 175 }
michael@0 176
michael@0 177 function throwCommand(rest) {
michael@0 178 var v;
michael@0 179 if (focusedFrame !== topFrame) {
michael@0 180 print("To throw, you must select the newest frame (use 'frame 0').");
michael@0 181 return;
michael@0 182 } else if (focusedFrame === null) {
michael@0 183 print("No stack.");
michael@0 184 return;
michael@0 185 } else if (rest === '') {
michael@0 186 return [{throw: lastExc}];
michael@0 187 } else {
michael@0 188 var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
michael@0 189 if (cv === null) {
michael@0 190 if (!dbg.enabled)
michael@0 191 return [cv];
michael@0 192 print("Debuggee died while determining what to throw. Stopped.");
michael@0 193 } else if ('return' in cv) {
michael@0 194 return [{throw: cv.return}];
michael@0 195 } else {
michael@0 196 if (!dbg.enabled)
michael@0 197 return [cv];
michael@0 198 print("Exception determining what to throw. Stopped.");
michael@0 199 showDebuggeeValue(cv.throw);
michael@0 200 }
michael@0 201 return;
michael@0 202 }
michael@0 203 }
michael@0 204
michael@0 205 function frameCommand(rest) {
michael@0 206 var n, f;
michael@0 207 if (rest.match(/[0-9]+/)) {
michael@0 208 n = +rest;
michael@0 209 f = topFrame;
michael@0 210 if (f === null) {
michael@0 211 print("No stack.");
michael@0 212 return;
michael@0 213 }
michael@0 214 for (var i = 0; i < n && f; i++) {
michael@0 215 if (!f.older) {
michael@0 216 print("There is no frame " + rest + ".");
michael@0 217 return;
michael@0 218 }
michael@0 219 f.older.younger = f;
michael@0 220 f = f.older;
michael@0 221 }
michael@0 222 focusedFrame = f;
michael@0 223 showFrame(f, n);
michael@0 224 } else if (rest !== '') {
michael@0 225 if (topFrame === null)
michael@0 226 print("No stack.");
michael@0 227 else
michael@0 228 showFrame();
michael@0 229 } else {
michael@0 230 print("do what now?");
michael@0 231 }
michael@0 232 }
michael@0 233
michael@0 234 function upCommand() {
michael@0 235 if (focusedFrame === null)
michael@0 236 print("No stack.");
michael@0 237 else if (focusedFrame.older === null)
michael@0 238 print("Initial frame selected; you cannot go up.");
michael@0 239 else {
michael@0 240 focusedFrame.older.younger = focusedFrame;
michael@0 241 focusedFrame = focusedFrame.older;
michael@0 242 showFrame();
michael@0 243 }
michael@0 244 }
michael@0 245
michael@0 246 function downCommand() {
michael@0 247 if (focusedFrame === null)
michael@0 248 print("No stack.");
michael@0 249 else if (!focusedFrame.younger)
michael@0 250 print("Youngest frame selected; you cannot go down.");
michael@0 251 else {
michael@0 252 focusedFrame = focusedFrame.younger;
michael@0 253 showFrame();
michael@0 254 }
michael@0 255 }
michael@0 256
michael@0 257 function forcereturnCommand(rest) {
michael@0 258 var v;
michael@0 259 var f = focusedFrame;
michael@0 260 if (f !== topFrame) {
michael@0 261 print("To forcereturn, you must select the newest frame (use 'frame 0').");
michael@0 262 } else if (f === null) {
michael@0 263 print("Nothing on the stack.");
michael@0 264 } else if (rest === '') {
michael@0 265 return [{return: undefined}];
michael@0 266 } else {
michael@0 267 var cv = saveExcursion(function () { return f.eval(rest); });
michael@0 268 if (cv === null) {
michael@0 269 if (!dbg.enabled)
michael@0 270 return [cv];
michael@0 271 print("Debuggee died while determining what to forcereturn. Stopped.");
michael@0 272 } else if ('return' in cv) {
michael@0 273 return [{return: cv.return}];
michael@0 274 } else {
michael@0 275 if (!dbg.enabled)
michael@0 276 return [cv];
michael@0 277 print("Error determining what to forcereturn. Stopped.");
michael@0 278 showDebuggeeValue(cv.throw);
michael@0 279 }
michael@0 280 }
michael@0 281 }
michael@0 282
michael@0 283 function printPop(f, c) {
michael@0 284 var fdesc = f.fullDescription();
michael@0 285 if (c.return) {
michael@0 286 print("frame returning (still selected): " + fdesc);
michael@0 287 showDebuggeeValue(c.return);
michael@0 288 } else if (c.throw) {
michael@0 289 print("frame threw exception: " + fdesc);
michael@0 290 showDebuggeeValue(c.throw);
michael@0 291 print("(To rethrow it, type 'throw'.)");
michael@0 292 lastExc = c.throw;
michael@0 293 } else {
michael@0 294 print("frame was terminated: " + fdesc);
michael@0 295 }
michael@0 296 }
michael@0 297
michael@0 298 // Set |prop| on |obj| to |value|, but then restore its current value
michael@0 299 // when we next enter the repl.
michael@0 300 function setUntilRepl(obj, prop, value) {
michael@0 301 var saved = obj[prop];
michael@0 302 obj[prop] = value;
michael@0 303 replCleanups.push(function () { obj[prop] = saved; });
michael@0 304 }
michael@0 305
michael@0 306 function doStepOrNext(kind) {
michael@0 307 var startFrame = topFrame;
michael@0 308 var startLine = startFrame.line;
michael@0 309 print("stepping in: " + startFrame.fullDescription());
michael@0 310 print("starting line: " + uneval(startLine));
michael@0 311
michael@0 312 function stepPopped(completion) {
michael@0 313 // Note that we're popping this frame; we need to watch for
michael@0 314 // subsequent step events on its caller.
michael@0 315 this.reportedPop = true;
michael@0 316 printPop(this, completion);
michael@0 317 topFrame = focusedFrame = this;
michael@0 318 return repl();
michael@0 319 }
michael@0 320
michael@0 321 function stepEntered(newFrame) {
michael@0 322 print("entered frame: " + newFrame.fullDescription());
michael@0 323 topFrame = focusedFrame = newFrame;
michael@0 324 return repl();
michael@0 325 }
michael@0 326
michael@0 327 function stepStepped() {
michael@0 328 print("stepStepped: " + this.fullDescription());
michael@0 329 // If we've changed frame or line, then report that.
michael@0 330 if (this !== startFrame || this.line != startLine) {
michael@0 331 topFrame = focusedFrame = this;
michael@0 332 if (focusedFrame != startFrame)
michael@0 333 print(focusedFrame.fullDescription());
michael@0 334 return repl();
michael@0 335 }
michael@0 336
michael@0 337 // Otherwise, let execution continue.
michael@0 338 return undefined;
michael@0 339 }
michael@0 340
michael@0 341 if (kind.step)
michael@0 342 setUntilRepl(dbg, 'onEnterFrame', stepEntered);
michael@0 343
michael@0 344 // If we're stepping after an onPop, watch for steps and pops in the
michael@0 345 // next-older frame; this one is done.
michael@0 346 var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
michael@0 347 if (!stepFrame || !stepFrame.script)
michael@0 348 stepFrame = null;
michael@0 349 if (stepFrame) {
michael@0 350 setUntilRepl(stepFrame, 'onStep', stepStepped);
michael@0 351 setUntilRepl(stepFrame, 'onPop', stepPopped);
michael@0 352 }
michael@0 353
michael@0 354 // Let the program continue!
michael@0 355 return [undefined];
michael@0 356 }
michael@0 357
michael@0 358 function stepCommand() { return doStepOrNext({step:true}); }
michael@0 359 function nextCommand() { return doStepOrNext({next:true}); }
michael@0 360
michael@0 361 // Build the table of commands.
michael@0 362 var commands = {};
michael@0 363 var commandArray = [
michael@0 364 backtraceCommand, "bt", "where",
michael@0 365 continueCommand, "c",
michael@0 366 detachCommand,
michael@0 367 downCommand, "d",
michael@0 368 forcereturnCommand,
michael@0 369 frameCommand, "f",
michael@0 370 nextCommand, "n",
michael@0 371 printCommand, "p",
michael@0 372 quitCommand, "q",
michael@0 373 stepCommand, "s",
michael@0 374 throwCommand, "t",
michael@0 375 upCommand, "u",
michael@0 376 helpCommand, "h",
michael@0 377 evalCommand, "!",
michael@0 378 ];
michael@0 379 var last = null;
michael@0 380 for (var i = 0; i < commandArray.length; i++) {
michael@0 381 var cmd = commandArray[i];
michael@0 382 if (typeof cmd === "string")
michael@0 383 commands[cmd] = last;
michael@0 384 else
michael@0 385 last = commands[cmd.name.replace(/Command$/, '')] = cmd;
michael@0 386 }
michael@0 387
michael@0 388 function helpCommand(rest) {
michael@0 389 print("Available commands:");
michael@0 390 var printcmd = function(group) {
michael@0 391 print(" " + group.join(", "));
michael@0 392 }
michael@0 393
michael@0 394 var group = [];
michael@0 395 for (var cmd of commandArray) {
michael@0 396 if (typeof cmd === "string") {
michael@0 397 group.push(cmd);
michael@0 398 } else {
michael@0 399 if (group.length) printcmd(group);
michael@0 400 group = [ cmd.name.replace(/Command$/, '') ];
michael@0 401 }
michael@0 402 }
michael@0 403 printcmd(group);
michael@0 404 }
michael@0 405
michael@0 406 // Break cmd into two parts: its first word and everything else. If it begins
michael@0 407 // with punctuation, treat that as a separate word.
michael@0 408 function breakcmd(cmd) {
michael@0 409 cmd = cmd.trimLeft();
michael@0 410 if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
michael@0 411 return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
michael@0 412 var m = /\s/.exec(cmd);
michael@0 413 if (m === null)
michael@0 414 return [cmd, ''];
michael@0 415 return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
michael@0 416 }
michael@0 417
michael@0 418 function runcmd(cmd) {
michael@0 419 var pieces = breakcmd(cmd);
michael@0 420 if (pieces[0] === "")
michael@0 421 return undefined;
michael@0 422
michael@0 423 var first = pieces[0], rest = pieces[1];
michael@0 424 if (!commands.hasOwnProperty(first)) {
michael@0 425 print("unrecognized command '" + first + "'");
michael@0 426 return undefined;
michael@0 427 }
michael@0 428
michael@0 429 var cmd = commands[first];
michael@0 430 if (cmd.length === 0 && rest !== '') {
michael@0 431 print("this command cannot take an argument");
michael@0 432 return undefined;
michael@0 433 }
michael@0 434
michael@0 435 return cmd(rest);
michael@0 436 }
michael@0 437
michael@0 438 function repl() {
michael@0 439 while (replCleanups.length > 0)
michael@0 440 replCleanups.pop()();
michael@0 441
michael@0 442 var cmd;
michael@0 443 for (;;) {
michael@0 444 putstr("\n" + prompt);
michael@0 445 cmd = readline();
michael@0 446 if (cmd === null)
michael@0 447 return null;
michael@0 448
michael@0 449 try {
michael@0 450 var result = runcmd(cmd);
michael@0 451 if (result === undefined)
michael@0 452 ; // do nothing
michael@0 453 else if (Array.isArray(result))
michael@0 454 return result[0];
michael@0 455 else
michael@0 456 throw new Error("Internal error: result of runcmd wasn't array or undefined");
michael@0 457 } catch (exc) {
michael@0 458 print("*** Internal error: exception in the debugger code.");
michael@0 459 print(" " + exc);
michael@0 460 print(exc.stack);
michael@0 461 }
michael@0 462 }
michael@0 463 }
michael@0 464
michael@0 465 var dbg = new Debugger();
michael@0 466 dbg.onDebuggerStatement = function (frame) {
michael@0 467 return saveExcursion(function () {
michael@0 468 topFrame = focusedFrame = frame;
michael@0 469 print("'debugger' statement hit.");
michael@0 470 showFrame();
michael@0 471 return repl();
michael@0 472 });
michael@0 473 };
michael@0 474 dbg.onThrow = function (frame, exc) {
michael@0 475 return saveExcursion(function () {
michael@0 476 topFrame = focusedFrame = frame;
michael@0 477 print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
michael@0 478 showFrame();
michael@0 479 print("Exception value is:");
michael@0 480 showDebuggeeValue(exc);
michael@0 481 return repl();
michael@0 482 });
michael@0 483 };
michael@0 484
michael@0 485 // The depth of jorendb nesting.
michael@0 486 var jorendbDepth;
michael@0 487 if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
michael@0 488
michael@0 489 var debuggeeGlobal = newGlobal("new-compartment");
michael@0 490 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
michael@0 491 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
michael@0 492
michael@0 493 print("jorendb version -0.0");
michael@0 494 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
michael@0 495
michael@0 496 var args = arguments;
michael@0 497 while(args.length > 0) {
michael@0 498 var arg = args.shift();
michael@0 499 if (arg == '-f') {
michael@0 500 arg = args.shift();
michael@0 501 debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 });
michael@0 502 } else if (arg == '-e') {
michael@0 503 arg = args.shift();
michael@0 504 debuggeeGlobal.eval(arg);
michael@0 505 } else {
michael@0 506 throw("jorendb does not implement command-line argument '" + arg + "'");
michael@0 507 }
michael@0 508 }
michael@0 509
michael@0 510 repl();

mercurial