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