1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
// Check that stepping doesn't make it look like unreachable code is running.
// Because our script source notes record only those bytecode offsets
// at which source positions change, the default behavior in the
// absence of a source note is to attribute a bytecode instruction to
// the same source location as the preceding instruction. When control
// flows from the preceding bytecode to the one we're emitting, that's
// usually plausible. But successors in the bytecode stream are not
// necessarily successors in the control flow graph. If the preceding
// bytecode was a back edge of a loop, or the jump at the end of a
// 'then' clause, its source position can be completely unrelated to
// that of its successor.
//
// We try to avoid showing such nonsense offsets to the user by
// requiring breakpoints and single-stepping to stop only at a line's
// entry points, as reported by Debugger.Script.prototype.getLineOffsets;
// and then ensuring that those entry points are all offsets mentioned
// explicitly in the source notes, and hence deliberately attributed
// to the given bytecode.
//
// This bit of JavaScript compiles to bytecode ending in a branch
// instruction whose source position is the body of an unreachable
// loop. The first instruction of the bytecode we emit following it
// will inherit this nonsense position, if we have not explicitly
// emitted a source note for said instruction.
//
// This test steps across such code and verifies that control never
// appears to enter the unreachable loop.
var bitOfCode = `debugger; // +0
if(false) { // +1
for(var b=0; b<0; b++) { // +2
c = 2 // +3
} // +4
}`; // +5
var g = newGlobal({newCompartment: true});
var dbg = Debugger(g);
g.eval("function nothing() { }\n");
var log = '';
dbg.onDebuggerStatement = function(frame) {
let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber;
frame.onStep = function() {
let foundLine = this.script.getOffsetLocation(this.offset).lineNumber;
if (this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) {
log += (foundLine - debugLine).toString(16);
}
};
};
function testOne(name, body, expected) {
print(name);
log = '';
g.eval(`function ${name} () { ${body} }`);
g.eval(`${name}();`);
assertEq(log, expected);
}
// Test the instructions at the end of a "try".
testOne("testTryFinally",
`try {
${bitOfCode}
} finally { // +6
} // +7
nothing(); // +8
`, "1689");
// The same but without a finally clause.
testOne("testTryCatch",
`try {
${bitOfCode}
} catch (e) { // +6
} // +7
nothing(); // +8
`, "189");
// Test the instructions at the end of a "catch".
testOne("testCatchFinally",
`try {
throw new TypeError();
} catch (e) {
${bitOfCode}
} finally { // +6
} // +7
nothing(); // +8
`, "1689");
// Test the instruction at the end of a "finally" clause.
testOne("testFinally",
`try {
} finally {
${bitOfCode}
} // +6
nothing(); // +7
`, "178");
// Test the instruction at the end of a "then" clause.
testOne("testThen",
`if (1 === 1) {
${bitOfCode}
} else { // +6
} // +7
nothing(); // +8
`, "189");
// Test the instructions leaving a switch block.
testOne("testSwitch",
`var x = 5;
switch (x) {
case 5:
${bitOfCode}
} // +6
nothing(); // +7
`, "178");
|