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