mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 673125: Implement the Debugger.Frame.prototype.onStep accessor. r=jorendorff.
This commit is contained in:
parent
3abd972834
commit
01972cd38f
@ -14,7 +14,7 @@ dbg.onDebuggerStatement = function (frame) {
|
||||
var args = frame.arguments;
|
||||
assertEq(args.length, argc);
|
||||
for (var i = 0; i < argc; i++)
|
||||
assertEq(args[i], i);
|
||||
assertEq(args[i], i);
|
||||
hits++;
|
||||
}
|
||||
|
||||
|
24
js/src/jit-test/tests/debug/Frame-onStep-01.js
Normal file
24
js/src/jit-test/tests/debug/Frame-onStep-01.js
Normal file
@ -0,0 +1,24 @@
|
||||
// Simple Debugger.Frame.prototype.onStep test.
|
||||
// Test that onStep fires often enough to see all four values of a.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.a = 0;
|
||||
g.eval("function f() {\n" +
|
||||
" a += 2;\n" +
|
||||
" a += 2;\n" +
|
||||
" a += 2;\n" +
|
||||
" return a;\n" +
|
||||
"}\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var seen = [0, 0, 0, 0, 0, 0, 0];
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
frame.onStep = function () {
|
||||
assertEq(arguments.length, 0);
|
||||
assertEq(this, frame);
|
||||
seen[g.a] = 1;
|
||||
};
|
||||
}
|
||||
|
||||
g.f();
|
||||
assertEq(seen.join(""), "1010101");
|
27
js/src/jit-test/tests/debug/Frame-onStep-02.js
Normal file
27
js/src/jit-test/tests/debug/Frame-onStep-02.js
Normal file
@ -0,0 +1,27 @@
|
||||
// Setting frame.onStep to undefined turns off single-stepping.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.a = 0;
|
||||
g.eval("function f() {\n" +
|
||||
" a++;\n" +
|
||||
" a++;\n" +
|
||||
" a++;\n" +
|
||||
" a++;\n" +
|
||||
" return a;\n" +
|
||||
"}\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var seen = [0, 0, 0, 0, 0];
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
seen[g.a] = 1;
|
||||
frame.onStep = function () {
|
||||
seen[g.a] = 1;
|
||||
if (g.a === 2) {
|
||||
frame.onStep = undefined;
|
||||
assertEq(frame.onStep, undefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
g.f();
|
||||
assertEq(seen.join(","), "1,1,1,0,0");
|
28
js/src/jit-test/tests/debug/Frame-onStep-03.js
Normal file
28
js/src/jit-test/tests/debug/Frame-onStep-03.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Setting onStep does not affect later calls to the same function.
|
||||
// (onStep is per-frame, not per-function.)
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.a = 1;
|
||||
g.eval("function f(a) {\n" +
|
||||
" var x = 2 * a;\n" +
|
||||
" return x * x;\n" +
|
||||
"}\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var log = '';
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
log += '+';
|
||||
frame.onStep = function () {
|
||||
if (log.charAt(log.length - 1) != 's')
|
||||
log += 's';
|
||||
};
|
||||
};
|
||||
|
||||
g.f(1);
|
||||
log += '|';
|
||||
g.f(2);
|
||||
log += '|';
|
||||
dbg.onEnterFrame = undefined;
|
||||
g.f(3);
|
||||
|
||||
assertEq(log, '+s|+s|');
|
34
js/src/jit-test/tests/debug/Frame-onStep-04.js
Normal file
34
js/src/jit-test/tests/debug/Frame-onStep-04.js
Normal file
@ -0,0 +1,34 @@
|
||||
// When a recursive function has many frames on the stack, onStep may be set or
|
||||
// not independently on each frame.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function f(x) {\n" +
|
||||
" if (x > 0)\n" +
|
||||
" f(x - 1);\n" +
|
||||
" else\n" +
|
||||
" debugger;\n" +
|
||||
" return x;\n" +
|
||||
"}");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var seen = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
function step() {
|
||||
seen[this.arguments[0]] = 1;
|
||||
}
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
// Turn on stepping for even-numbered frames.
|
||||
var x = frame.arguments[0];
|
||||
if (x % 2 === 0)
|
||||
frame.onStep = step;
|
||||
};
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
// This is called with 8 call frames on the stack, 7 down to 0.
|
||||
// At this point we should have seen all the even-numbered frames.
|
||||
assertEq(seen.join(""), "10101010");
|
||||
|
||||
// Now reset seen to see which frames fire onStep on the way out.
|
||||
seen = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
};
|
||||
|
||||
g.f(7);
|
||||
assertEq(seen.join(""), "10101010");
|
14
js/src/jit-test/tests/debug/Frame-onStep-05.js
Normal file
14
js/src/jit-test/tests/debug/Frame-onStep-05.js
Normal file
@ -0,0 +1,14 @@
|
||||
// Upon returning to a frame with an onStep hook, the hook is called before the
|
||||
// next line.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.log = '';
|
||||
g.eval("function f() { debugger; }");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
frame.older.onStep = function () { g.log += 's'; };
|
||||
};
|
||||
g.eval("f();\n" +
|
||||
"log += 'x';\n");
|
||||
assertEq(g.log.charAt(0), 's');
|
66
js/src/jit-test/tests/debug/Frame-onStep-06.js
Normal file
66
js/src/jit-test/tests/debug/Frame-onStep-06.js
Normal file
@ -0,0 +1,66 @@
|
||||
// After returning from an implicit toString call, the calling frame's onStep
|
||||
// hook fires.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("var originalX = {toString: function () { debugger; log += 'x'; return 1; }};\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
g.log += 'd';
|
||||
frame.older.onStep = function () {
|
||||
if (!g.log.match(/[sy]$/))
|
||||
g.log += 's';
|
||||
};
|
||||
};
|
||||
|
||||
// expr is an expression that will trigger an implicit toString call.
|
||||
function check(expr) {
|
||||
g.log = '';
|
||||
g.x = g.originalX;
|
||||
g.eval(expr + ";\n" +
|
||||
"log += 'y';\n");
|
||||
assertEq(g.log, 'dxsy');
|
||||
}
|
||||
|
||||
check("'' + x");
|
||||
check("0 + x");
|
||||
check("0 - x");
|
||||
check("0 * x");
|
||||
check("0 / x");
|
||||
check("0 % x");
|
||||
check("+x");
|
||||
check("x in {}");
|
||||
check("x++");
|
||||
check("++x");
|
||||
check("x--");
|
||||
check("--x");
|
||||
check("x < 0");
|
||||
check("x > 0");
|
||||
check("x >= 0");
|
||||
check("x <= 0");
|
||||
check("x == 0");
|
||||
check("x != 0");
|
||||
check("x & 1");
|
||||
check("x | 1");
|
||||
check("x ^ 1");
|
||||
check("~x");
|
||||
check("x << 1");
|
||||
check("x >> 1");
|
||||
check("x >>> 1");
|
||||
|
||||
g.eval("function lastStep() { throw StopIteration; }");
|
||||
g.eval("function emptyIterator() { debugger; log += 'x'; return { next: lastStep }; }");
|
||||
g.eval("var customEmptyIterator = { __iterator__: emptyIterator };");
|
||||
g.log = '';
|
||||
g.eval("for (i in customEmptyIterator);\n" +
|
||||
"log += 'y';\n");
|
||||
assertEq(g.log, 'dxsy');
|
||||
|
||||
g.eval("var getter = { get x() { debugger; return log += 'x'; } }");
|
||||
check("getter.x");
|
||||
|
||||
g.eval("var setter = { set x(v) { debugger; return log += 'x'; } }");
|
||||
check("setter.x = 1");
|
||||
|
||||
g.eval("Object.defineProperty(this, 'thisgetter', { get: function() { debugger; log += 'x'; }});");
|
||||
check("thisgetter");
|
23
js/src/jit-test/tests/debug/Frame-onStep-07.js
Normal file
23
js/src/jit-test/tests/debug/Frame-onStep-07.js
Normal file
@ -0,0 +1,23 @@
|
||||
// The tracejit does not interfere with frame.onStep.
|
||||
//
|
||||
// The function f() writes 'L' to the log in a loop. If we enable stepping and
|
||||
// write an 's' each time frame.onStep is called, any two Ls should have at
|
||||
// least one 's' between them.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.N = RUNLOOP + 2;
|
||||
g.log = '';
|
||||
g.eval("function f() {\n" +
|
||||
" for (var i = 0; i <= N; i++)\n" +
|
||||
" log += 'L';\n" +
|
||||
"}\n");
|
||||
g.f();
|
||||
assertEq(/LL/.exec(g.log) !== null, true);
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
frame.onStep = function () { g.log += 's'; };
|
||||
};
|
||||
g.log = '';
|
||||
g.f();
|
||||
assertEq(/LL/.exec(g.log), null);
|
29
js/src/jit-test/tests/debug/Frame-onStep-08.js
Normal file
29
js/src/jit-test/tests/debug/Frame-onStep-08.js
Normal file
@ -0,0 +1,29 @@
|
||||
// frame.onStep can coexist with breakpoints.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = Debugger(g);
|
||||
var log = '';
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
var handler = {hit: function () { log += 'B'; }};
|
||||
var lines = frame.script.getAllOffsets();
|
||||
for (var line in lines) {
|
||||
line = Number(line);
|
||||
var offs = lines[line];
|
||||
for (var i = 0; i < offs.length; i++)
|
||||
frame.script.setBreakpoint(offs[i], handler);
|
||||
}
|
||||
|
||||
frame.onStep = function () { log += 's'; };
|
||||
};
|
||||
|
||||
g.eval("one = 1;\n" +
|
||||
"two = 2;\n" +
|
||||
"three = 3;\n" +
|
||||
"four = 4;\n");
|
||||
assertEq(g.four, 4);
|
||||
|
||||
// Breakpoints hit on all four lines.
|
||||
assertEq(log.replace(/[^B]/g, ''), 'BBBB');
|
||||
|
||||
// onStep was called between each pair of breakpoints.
|
||||
assertEq(/BB/.exec(log), null);
|
24
js/src/jit-test/tests/debug/Frame-onStep-09.js
Normal file
24
js/src/jit-test/tests/debug/Frame-onStep-09.js
Normal file
@ -0,0 +1,24 @@
|
||||
// After an implicit toString call throws an exception, the calling frame's
|
||||
// onStep hook fires.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("var x = {toString: function () { debugger; log += 'x'; throw 'mud'; }};");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
g.log += 'd';
|
||||
frame.older.onStep = function () {
|
||||
if (!g.log.match(/[sy]$/))
|
||||
g.log += 's';
|
||||
};
|
||||
};
|
||||
|
||||
g.log = '';
|
||||
g.eval("try { x + ''; } catch (x) { }\n" +
|
||||
"log += 'y';\n");
|
||||
assertEq(g.log, "dxsy");
|
||||
|
||||
g.log = '';
|
||||
g.eval("try { '' + x; } catch (x) { }\n" +
|
||||
"log += 'y';\n");
|
||||
assertEq(g.log, "dxsy");
|
28
js/src/jit-test/tests/debug/Frame-onStep-10.js
Normal file
28
js/src/jit-test/tests/debug/Frame-onStep-10.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Throwing and catching an error in an onStep handler shouldn't interfere
|
||||
// with throwing and catching in the debuggee.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function f() { debugger; throw 'mud'; }");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var stepped = false;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
frame.older.onStep = function () {
|
||||
stepped = true;
|
||||
try {
|
||||
throw 'snow';
|
||||
} catch (x) {
|
||||
assertEq(x, 'snow');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
stepped = false;
|
||||
g.eval("var caught;\n" +
|
||||
"try {\n" +
|
||||
" f();\n" +
|
||||
"} catch (x) {\n" +
|
||||
" caught = x;\n" +
|
||||
"}\n");
|
||||
assertEq(stepped, true);
|
||||
assertEq(g.caught, 'mud');
|
78
js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
Normal file
78
js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
Normal file
@ -0,0 +1,78 @@
|
||||
// Test that a frame's onStep handler gets called at least once on each line of a function.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = new Debugger(g);
|
||||
|
||||
// When we hit a 'debugger' statement, set offsets to the frame's script's
|
||||
// table of line offsets --- a sparse array indexed by line number. Begin
|
||||
// single-stepping the current frame; for each source line we hit, delete
|
||||
// the line's entry in offsets. Thus, at the end, offsets is an array with
|
||||
// an element for each line we did not reach.
|
||||
var doSingleStep = true;
|
||||
var offsets;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
var script = frame.script;
|
||||
offsets = script.getAllOffsets();
|
||||
print("debugger line: " + script.getOffsetLine(frame.offset));
|
||||
print("original lines: " + uneval(Object.keys(offsets)));
|
||||
if (doSingleStep) {
|
||||
frame.onStep = function onStepHandler() {
|
||||
var line = script.getOffsetLine(this.offset);
|
||||
delete offsets[line];
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
g.eval(
|
||||
'function t(a, b, c) { \n' +
|
||||
' debugger; \n' +
|
||||
' var x = a; \n' +
|
||||
' x += b; \n' +
|
||||
' if (x < 10) \n' +
|
||||
' x -= c; \n' +
|
||||
' return x; \n' +
|
||||
'} \n'
|
||||
);
|
||||
|
||||
// This should stop at every line but the first of the function.
|
||||
g.eval('t(1,2,3)');
|
||||
assertEq(Object.keys(offsets).length, 1);
|
||||
|
||||
// This should stop at every line but the first of the function, and the
|
||||
// body of the 'if'.
|
||||
g.eval('t(10,20,30)');
|
||||
assertEq(Object.keys(offsets).length, 2);
|
||||
|
||||
// This shouldn't stop at all. It's the frame that's in single-step mode,
|
||||
// not the script, so the prior execution of t in single-step mode should
|
||||
// have no effect on this one.
|
||||
doSingleStep = false;
|
||||
g.eval('t(0, 0, 0)');
|
||||
assertEq(Object.keys(offsets).length, 6);
|
||||
doSingleStep = true;
|
||||
|
||||
// Single-step in an eval frame. This should reach every line but the
|
||||
// first.
|
||||
g.eval(
|
||||
'debugger; \n' +
|
||||
'var a=1, b=2, c=3; \n' +
|
||||
'var x = a; \n' +
|
||||
'x += b; \n' +
|
||||
'if (x < 10) \n' +
|
||||
' x -= c; \n'
|
||||
);
|
||||
print("final lines: " + uneval(Object.keys(offsets)));
|
||||
assertEq(Object.keys(offsets).length, 1);
|
||||
|
||||
// Single-step in a global code frame. This should reach every line but the
|
||||
// first.
|
||||
g.evaluate(
|
||||
'debugger; \n' +
|
||||
'var a=1, b=2, c=3; \n' +
|
||||
'var x = a; \n' +
|
||||
'x += b; \n' +
|
||||
'if (x < 10) \n' +
|
||||
' x -= c; \n'
|
||||
);
|
||||
print("final lines: " + uneval(Object.keys(offsets)));
|
||||
assertEq(Object.keys(offsets).length, 1);
|
14
js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js
Normal file
14
js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js
Normal file
@ -0,0 +1,14 @@
|
||||
// If frame.onStep returns {return:val}, the frame returns.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function f(x) {\n" +
|
||||
" var a = x * x;\n" +
|
||||
" return a;\n" +
|
||||
"}\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onEnterFrame = function (frame) {
|
||||
frame.onStep = function () { return {return: "pass"}; };
|
||||
};
|
||||
|
||||
assertEq(g.f(4), "pass");
|
17
js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js
Normal file
17
js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js
Normal file
@ -0,0 +1,17 @@
|
||||
// If frame.onStep returns {throw:}, an exception is thrown in the debuggee.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function h() { debugger; }\n" +
|
||||
"function f() {\n" +
|
||||
" h();\n" +
|
||||
" return 'fail';\n" +
|
||||
"}\n");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
frame.older.onStep = function () { return {throw: "pass"}; };
|
||||
};
|
||||
|
||||
assertThrowsValue(g.f, "pass");
|
19
js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js
Normal file
19
js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js
Normal file
@ -0,0 +1,19 @@
|
||||
// If frame.onStep returns null, the debuggee terminates.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function h() { debugger; }");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var hits = 0;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
hits++;
|
||||
if (hits == 1) {
|
||||
var rv = frame.eval("h();\n" +
|
||||
"throw 'fail';\n");
|
||||
assertEq(rv, null);
|
||||
} else {
|
||||
frame.older.onStep = function () { return null; };
|
||||
}
|
||||
};
|
||||
g.h();
|
||||
assertEq(hits, 2);
|
31
js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js
Normal file
31
js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js
Normal file
@ -0,0 +1,31 @@
|
||||
// If frame.onStep returns null, debuggee catch and finally blocks are skipped.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
g.eval("function h() { debugger; }");
|
||||
|
||||
var dbg = Debugger(g);
|
||||
var hits = 0;
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
hits++;
|
||||
if (hits == 1) {
|
||||
var rv = frame.eval("try {\n" +
|
||||
" h();\n" +
|
||||
" throw 'fail';\n" +
|
||||
"} catch (exc) {\n" +
|
||||
" caught = exc;\n" +
|
||||
"} finally {\n" +
|
||||
" finallyHit = true;\n" +
|
||||
"}\n");
|
||||
assertEq(rv, null);
|
||||
} else {
|
||||
frame.older.onStep = function () {
|
||||
this.onStep = undefined;
|
||||
return null;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
g.h();
|
||||
assertEq(hits, 2);
|
||||
assertEq("caught" in g, false);
|
||||
assertEq("finallyHit" in g, false);
|
@ -4,9 +4,9 @@ var g = newGlobal('new-compartment');
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
try {
|
||||
frame.arguments[0].deleteProperty("x");
|
||||
frame.arguments[0].deleteProperty("x");
|
||||
} catch (exc) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
throw new Error("deleteProperty should throw");
|
||||
};
|
||||
|
@ -27,14 +27,14 @@ function test(code) {
|
||||
var xw = gw.getOwnPropertyDescriptor("x").value;
|
||||
|
||||
function check() {
|
||||
// The Debugger.Object seal/freeze/preventExtensions methods
|
||||
// had the same effect as the corresponding ES5 Object methods.
|
||||
g.compareObjects();
|
||||
// The Debugger.Object seal/freeze/preventExtensions methods
|
||||
// had the same effect as the corresponding ES5 Object methods.
|
||||
g.compareObjects();
|
||||
|
||||
// The Debugger.Object introspection methods agree with the ES5 Object methods.
|
||||
assertEq(xw.isExtensible(), g.Object.isExtensible(g.x), code + ' isExtensible');
|
||||
assertEq(xw.isSealed(), g.Object.isSealed(g.x), code + ' isSealed');
|
||||
assertEq(xw.isFrozen(), g.Object.isFrozen(g.x), code + ' isFrozen');
|
||||
// The Debugger.Object introspection methods agree with the ES5 Object methods.
|
||||
assertEq(xw.isExtensible(), g.Object.isExtensible(g.x), code + ' isExtensible');
|
||||
assertEq(xw.isSealed(), g.Object.isSealed(g.x), code + ' isSealed');
|
||||
assertEq(xw.isFrozen(), g.Object.isFrozen(g.x), code + ' isFrozen');
|
||||
}
|
||||
|
||||
check();
|
||||
|
@ -6,9 +6,9 @@ var hits = 0;
|
||||
dbg.onExceptionUnwind = function (frame, value) { hits = 'BAD'; };
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
if (hits++ === 0)
|
||||
assertEq(frame.eval("debugger;"), null);
|
||||
assertEq(frame.eval("debugger;"), null);
|
||||
else
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
assertEq(g.eval("debugger; 2"), 2);
|
||||
|
@ -370,3 +370,4 @@ MSG_DEF(JSMSG_DEBUG_BAD_LINE, 283, 0, JSEXN_TYPEERR, "invalid line numbe
|
||||
MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGING, 284, 0, JSEXN_ERR, "can't set breakpoint: script global is not a debuggee")
|
||||
MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 285, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object")
|
||||
MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 286, 0, JSEXN_TYPEERR, "value is not a function or undefined")
|
||||
MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 287, 0, JSEXN_ERR, "stack frame is not running JavaScript code")
|
||||
|
@ -1886,12 +1886,13 @@ Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
|
||||
|
||||
#define RESTORE_INTERP_VARS() \
|
||||
JS_BEGIN_MACRO \
|
||||
script = regs.fp()->script(); \
|
||||
SET_SCRIPT(regs.fp()->script()); \
|
||||
argv = regs.fp()->maybeFormalArgs(); \
|
||||
atoms = FrameAtomBase(cx, regs.fp()); \
|
||||
JS_ASSERT(&cx->regs() == ®s); \
|
||||
if (cx->isExceptionPending()) \
|
||||
goto error; \
|
||||
CHECK_INTERRUPT_HANDLER(); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define MONITOR_BRANCH() \
|
||||
@ -1974,6 +1975,13 @@ Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
|
||||
DO_OP(); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define SET_SCRIPT(s) \
|
||||
JS_BEGIN_MACRO \
|
||||
script = (s); \
|
||||
if (script->stepModeEnabled()) \
|
||||
ENABLE_INTERRUPTS(); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define CHECK_INTERRUPT_HANDLER() \
|
||||
JS_BEGIN_MACRO \
|
||||
if (cx->debugHooks->interruptHook) \
|
||||
@ -2006,7 +2014,8 @@ Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
|
||||
|
||||
/* Copy in hot values that change infrequently. */
|
||||
JSRuntime *const rt = cx->runtime;
|
||||
JSScript *script = regs.fp()->script();
|
||||
JSScript *script;
|
||||
SET_SCRIPT(regs.fp()->script());
|
||||
int *pcCounts = script->pcCounters.get(JSRUNMODE_INTERP);
|
||||
Value *argv = regs.fp()->maybeFormalArgs();
|
||||
CHECK_INTERRUPT_HANDLER();
|
||||
@ -2151,18 +2160,24 @@ Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
|
||||
{
|
||||
bool moreInterrupts = false;
|
||||
JSInterruptHook hook = cx->debugHooks->interruptHook;
|
||||
if (hook) {
|
||||
if (hook || script->stepModeEnabled()) {
|
||||
#ifdef JS_TRACER
|
||||
if (TRACE_RECORDER(cx))
|
||||
AbortRecording(cx, "interrupt hook");
|
||||
AbortRecording(cx, "interrupt hook or singleStepMode");
|
||||
#ifdef JS_METHODJIT
|
||||
if (TRACE_PROFILER(cx))
|
||||
AbortProfiling(cx);
|
||||
#endif
|
||||
#endif
|
||||
Value rval;
|
||||
switch (hook(cx, script, regs.pc, Jsvalify(&rval),
|
||||
cx->debugHooks->interruptHookData)) {
|
||||
JSTrapStatus status = JSTRAP_CONTINUE;
|
||||
if (hook) {
|
||||
status = hook(cx, script, regs.pc, Jsvalify(&rval),
|
||||
cx->debugHooks->interruptHookData);
|
||||
}
|
||||
if (status == JSTRAP_CONTINUE && script->stepModeEnabled())
|
||||
status = Debugger::onSingleStep(cx, &rval);
|
||||
switch (status) {
|
||||
case JSTRAP_ERROR:
|
||||
goto error;
|
||||
case JSTRAP_CONTINUE:
|
||||
@ -2381,7 +2396,6 @@ BEGIN_CASE(JSOP_STOP)
|
||||
JS_ASSERT(!regs.fp()->hasImacropc());
|
||||
JS_ASSERT(!js_IsActiveWithOrBlock(cx, ®s.fp()->scopeChain(), 0));
|
||||
interpReturnOK = ScriptEpilogue(cx, regs.fp(), interpReturnOK);
|
||||
CHECK_INTERRUPT_HANDLER();
|
||||
|
||||
/* The JIT inlines ScriptEpilogue. */
|
||||
#ifdef JS_METHODJIT
|
||||
@ -2390,10 +2404,11 @@ BEGIN_CASE(JSOP_STOP)
|
||||
cx->stack.popInlineFrame(regs);
|
||||
|
||||
/* Sync interpreter locals. */
|
||||
script = regs.fp()->script();
|
||||
SET_SCRIPT(regs.fp()->script());
|
||||
pcCounts = script->pcCounters.get(JSRUNMODE_INTERP);
|
||||
argv = regs.fp()->maybeFormalArgs();
|
||||
atoms = FrameAtomBase(cx, regs.fp());
|
||||
CHECK_INTERRUPT_HANDLER();
|
||||
|
||||
JS_ASSERT(*regs.pc == JSOP_TRAP || *regs.pc == JSOP_NEW || *regs.pc == JSOP_CALL ||
|
||||
*regs.pc == JSOP_FUNCALL || *regs.pc == JSOP_FUNAPPLY);
|
||||
@ -4060,7 +4075,7 @@ BEGIN_CASE(JSOP_FUNAPPLY)
|
||||
goto error;
|
||||
|
||||
/* Refresh local js::Interpret state. */
|
||||
script = newScript;
|
||||
SET_SCRIPT(newScript);
|
||||
pcCounts = script->pcCounters.get(JSRUNMODE_INTERP);
|
||||
argv = regs.fp()->formalArgsEnd() - fun->nargs;
|
||||
atoms = script->atomMap.vector;
|
||||
@ -4083,7 +4098,6 @@ BEGIN_CASE(JSOP_FUNAPPLY)
|
||||
goto error;
|
||||
if (!TRACE_RECORDER(cx) && !TRACE_PROFILER(cx) && status == mjit::Compile_Okay) {
|
||||
interpReturnOK = mjit::JaegerShot(cx);
|
||||
CHECK_INTERRUPT_HANDLER();
|
||||
goto jit_return;
|
||||
}
|
||||
}
|
||||
|
@ -1193,6 +1193,9 @@ stubs::Trap(VMFrame &f, uint32 trapTypes)
|
||||
if (hook)
|
||||
result = hook(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval),
|
||||
f.cx->debugHooks->interruptHookData);
|
||||
|
||||
if (result == JSTRAP_CONTINUE)
|
||||
result = Debugger::onSingleStep(f.cx, &rval);
|
||||
}
|
||||
|
||||
if (result == JSTRAP_CONTINUE && (trapTypes & JSTRAP_TRAP))
|
||||
|
@ -63,6 +63,7 @@ extern Class DebuggerFrame_class;
|
||||
enum {
|
||||
JSSLOT_DEBUGFRAME_OWNER,
|
||||
JSSLOT_DEBUGFRAME_ARGUMENTS,
|
||||
JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
|
||||
JSSLOT_DEBUGFRAME_COUNT
|
||||
};
|
||||
|
||||
@ -388,11 +389,24 @@ Debugger::getHook(Hook hook) const
|
||||
bool
|
||||
Debugger::hasAnyLiveHooks() const
|
||||
{
|
||||
return enabled && (getHook(OnDebuggerStatement) ||
|
||||
getHook(OnExceptionUnwind) ||
|
||||
getHook(OnNewScript) ||
|
||||
getHook(OnEnterFrame) ||
|
||||
!JS_CLIST_IS_EMPTY(&breakpoints));
|
||||
if (!enabled)
|
||||
return false;
|
||||
|
||||
if (getHook(OnDebuggerStatement) ||
|
||||
getHook(OnExceptionUnwind) ||
|
||||
getHook(OnNewScript) ||
|
||||
getHook(OnEnterFrame))
|
||||
return true;
|
||||
|
||||
if (!JS_CLIST_IS_EMPTY(&breakpoints))
|
||||
return true;
|
||||
|
||||
for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
|
||||
if (!r.front().value->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
@ -433,8 +447,16 @@ Debugger::slowPathOnLeaveFrame(JSContext *cx)
|
||||
for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
|
||||
Debugger *dbg = *p;
|
||||
if (FrameMap::Ptr p = dbg->frames.lookup(fp)) {
|
||||
StackFrame *frame = p->key;
|
||||
JSObject *frameobj = p->value;
|
||||
frameobj->setPrivate(NULL);
|
||||
|
||||
/* If this frame had an onStep handler, adjust the script's count. */
|
||||
if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
|
||||
frame->isScriptFrame()) {
|
||||
frame->script()->changeStepModeCount(cx, -1);
|
||||
}
|
||||
|
||||
dbg->frames.remove(p);
|
||||
}
|
||||
}
|
||||
@ -878,6 +900,101 @@ Debugger::onTrap(JSContext *cx, Value *vp)
|
||||
return JSTRAP_CONTINUE;
|
||||
}
|
||||
|
||||
JSTrapStatus
|
||||
Debugger::onSingleStep(JSContext *cx, Value *vp)
|
||||
{
|
||||
StackFrame *fp = cx->fp();
|
||||
|
||||
/*
|
||||
* We may be stepping over a JSOP_EXCEPTION, that pushes the context's
|
||||
* pending exception for a 'catch' clause to handle. Don't let the
|
||||
* onStep handlers mess with that (other than by returning a resumption
|
||||
* value).
|
||||
*/
|
||||
Value exception = UndefinedValue();
|
||||
bool exceptionPending = cx->isExceptionPending();
|
||||
if (exceptionPending) {
|
||||
exception = cx->getPendingException();
|
||||
cx->clearPendingException();
|
||||
}
|
||||
|
||||
/* We should only receive single-step traps for scripted frames. */
|
||||
JS_ASSERT(fp->isScriptFrame());
|
||||
|
||||
/*
|
||||
* Build list of Debugger.Frame instances referring to this frame with
|
||||
* onStep handlers.
|
||||
*/
|
||||
AutoObjectVector frames(cx);
|
||||
GlobalObject *global = fp->scopeChain().getGlobal();
|
||||
if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
|
||||
for (Debugger **d = debuggers->begin(); d != debuggers->end(); d++) {
|
||||
Debugger *dbg = *d;
|
||||
if (FrameMap::Ptr p = dbg->frames.lookup(fp)) {
|
||||
JSObject *frame = p->value;
|
||||
if (!frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined() &&
|
||||
!frames.append(frame))
|
||||
return JSTRAP_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
/*
|
||||
* Validate the single-step count on this frame's script, to ensure that
|
||||
* we're not receiving traps we didn't ask for. Even when frames is
|
||||
* non-empty (and thus we know this trap was requested), do the check
|
||||
* anyway, to make sure the count has the correct non-zero value.
|
||||
*
|
||||
* The converse --- ensuring that we do receive traps when we should --- can
|
||||
* be done with unit tests.
|
||||
*/
|
||||
{
|
||||
uint32 stepperCount = 0;
|
||||
JSScript *trappingScript = fp->script();
|
||||
if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
|
||||
for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) {
|
||||
Debugger *dbg = *p;
|
||||
for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
|
||||
StackFrame *frame = r.front().key;
|
||||
JSObject *frameobj = r.front().value;
|
||||
if (frame->isScriptFrame() &&
|
||||
frame->script() == trappingScript &&
|
||||
!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
|
||||
{
|
||||
stepperCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trappingScript->compileAndGo)
|
||||
JS_ASSERT(stepperCount == trappingScript->stepModeCount());
|
||||
else
|
||||
JS_ASSERT(stepperCount <= trappingScript->stepModeCount());
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Call all the onStep handlers we found. */
|
||||
for (JSObject **p = frames.begin(); p != frames.end(); p++) {
|
||||
JSObject *frame = *p;
|
||||
Debugger *dbg = Debugger::fromChildJSObject(frame);
|
||||
AutoCompartment ac(cx, dbg->object);
|
||||
if (!ac.enter())
|
||||
return JSTRAP_ERROR;
|
||||
const Value &handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
|
||||
Value rval;
|
||||
bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, NULL, &rval);
|
||||
JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp);
|
||||
if (st != JSTRAP_CONTINUE)
|
||||
return st;
|
||||
}
|
||||
|
||||
vp->setUndefined();
|
||||
if (exceptionPending)
|
||||
cx->setPendingException(exception);
|
||||
return JSTRAP_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
/*** Debugger JSObjects **************************************************************************/
|
||||
|
||||
@ -1027,8 +1144,12 @@ Debugger::trace(JSTracer *trc)
|
||||
MarkObject(trc, *uncaughtExceptionHook, "hooks");
|
||||
|
||||
/*
|
||||
* Mark Debugger.Frame objects that are reachable from JS if we look them up
|
||||
* again (because the corresponding StackFrame is still on the stack).
|
||||
* Mark Debugger.Frame objects. These are all reachable from JS, because the
|
||||
* corresponding StackFrames are still on the stack.
|
||||
*
|
||||
* (Once we support generator frames properly, we will need
|
||||
* weakly-referenced Debugger.Frame objects as well, for suspended generator
|
||||
* frames.)
|
||||
*/
|
||||
for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
|
||||
JSObject *frameobj = r.front().value;
|
||||
@ -2650,6 +2771,54 @@ DebuggerFrame_getLive(JSContext *cx, uintN argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsValidHook(const Value &v)
|
||||
{
|
||||
return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebuggerFrame_getOnStep(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, fp);
|
||||
(void) fp; // Silence GCC warning
|
||||
Value handler = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
|
||||
JS_ASSERT(IsValidHook(handler));
|
||||
args.rval() = handler;
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebuggerFrame_setOnStep(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
REQUIRE_ARGC("Debugger.Frame.set onStep", 1);
|
||||
THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, fp);
|
||||
if (!fp->isScriptFrame()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_SCRIPT_FRAME);
|
||||
return false;
|
||||
}
|
||||
if (!IsValidHook(args[0])) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
|
||||
return false;
|
||||
}
|
||||
|
||||
Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
|
||||
int delta = !args[0].isUndefined() - !prior.isUndefined();
|
||||
if (delta != 0) {
|
||||
/* Try to adjust this frame's script single-step mode count. */
|
||||
AutoCompartment ac(cx, &fp->scopeChain());
|
||||
if (!ac.enter())
|
||||
return false;
|
||||
if (!fp->script()->changeStepModeCount(cx, delta))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Now that the step mode switch has succeeded, we can install the handler. */
|
||||
thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
JSBool
|
||||
@ -2795,6 +2964,7 @@ static JSPropertySpec DebuggerFrame_properties[] = {
|
||||
JS_PSG("script", DebuggerFrame_getScript, 0),
|
||||
JS_PSG("this", DebuggerFrame_getThis, 0),
|
||||
JS_PSG("type", DebuggerFrame_getType, 0),
|
||||
JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
|
@ -306,6 +306,7 @@ class Debugger {
|
||||
NewScriptKind kind);
|
||||
static inline void onDestroyScript(JSScript *script);
|
||||
static JSTrapStatus onTrap(JSContext *cx, Value *vp);
|
||||
static JSTrapStatus onSingleStep(JSContext *cx, Value *vp);
|
||||
|
||||
/************************************* Functions for use by Debugger.cpp. */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user