Script-getPossibleBreakpoints.js (8424B)
1 // simple ExpressionStatement 2 assertBreakpoints(` 3 /*S*/a; 4 /*S*/obj.prop; 5 `); 6 7 // ExpressionStatement with calls 8 assertBreakpoints(` 9 /*S*/a(); 10 /*S*/obj./*B*/prop(); 11 `); 12 13 // calls with many args 14 assertBreakpoints(` 15 /*S*/a/*B*/(1); 16 /*S*/a/*B*/(1,2); 17 /*S*/a/*B*/(1,2,3); 18 `); 19 20 21 // ExpressionStatement with nested expression calls. 22 assertBreakpoints(` 23 "45"; 24 /*S*/"45" + /*B*/a(); 25 /*S*/b() + "45"; 26 27 /*S*/"45" + o./*B*/a(); 28 /*S*/o./*B*/b() + "45"; 29 /*S*/"45" + o./*B*/a() + o./*B*/b(); 30 /*S*/o./*B*/b() + "45" + o./*B*/a(); 31 /*S*/o./*B*/b() + o./*B*/a() + "45"; 32 `); 33 34 // var VariableStatement initializers 35 assertBreakpoints(` 36 var foo1 = /*S*/"" + o.a + "" + /*B*/b(), 37 foo2 = /*S*/"45", 38 foo3 = /*S*/"45" + /*B*/a(), 39 foo4 = /*S*/b() + "45", 40 foo5 = /*S*/"45" + /*B*/a() + /*B*/b(), 41 foo6 = /*S*/b() + "45" + /*B*/a(), 42 foo7 = /*S*/b() + /*B*/a() + "45", 43 foo8 = /*S*/"45" + o./*B*/a(), 44 foo9 = /*S*/o./*B*/b() + "45", 45 foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(), 46 foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(), 47 foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45"; 48 `); 49 50 // let VariableStatement initializers 51 assertBreakpoints(` 52 let foo1 = /*S*/"" + o.a + "" + /*B*/b(), 53 foo2 = /*S*/"45", 54 foo3 = /*S*/"45" + /*B*/a(), 55 foo4 = /*S*/b() + "45", 56 foo5 = /*S*/"45" + /*B*/a() + /*B*/b(), 57 foo6 = /*S*/b() + "45" + /*B*/a(), 58 foo7 = /*S*/b() + /*B*/a() + "45", 59 foo8 = /*S*/"45" + o./*B*/a(), 60 foo9 = /*S*/o./*B*/b() + "45", 61 foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(), 62 foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(), 63 foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45"; 64 `); 65 66 // const VariableStatement initializers 67 assertBreakpoints(` 68 const foo1 = /*S*/"" + o.a + "" + /*B*/b(), 69 foo2 = /*S*/"45", 70 foo3 = /*S*/"45" + /*B*/a(), 71 foo4 = /*S*/b() + "45", 72 foo5 = /*S*/"45" + /*B*/a() + /*B*/b(), 73 foo6 = /*S*/b() + "45" + /*B*/a(), 74 foo7 = /*S*/b() + /*B*/a() + "45", 75 foo8 = /*S*/"45" + o./*B*/a(), 76 foo9 = /*S*/o./*B*/b() + "45", 77 foo10 = /*S*/"45" + o./*B*/a() + o./*B*/b(), 78 foo11 = /*S*/o./*B*/b() + "45" + o./*B*/a(), 79 foo12 = /*S*/o./*B*/b() + o./*B*/a() + "45"; 80 `); 81 82 // EmptyStatement 83 assertBreakpoints(` 84 ; 85 ; 86 ; 87 /*S*/a(); 88 `); 89 90 // IfStatement 91 assertBreakpoints(` 92 if (/*S*/a) {} 93 if (/*S*/a()) {} 94 if (/*S*/obj.prop) {} 95 if (/*S*/obj./*B*/prop()) {} 96 if (/*S*/"42" + a) {} 97 if (/*S*/"42" + /*B*/a()) {} 98 if (/*S*/"42" + obj.prop) {} 99 if (/*S*/"42" + obj./*B*/prop()) {} 100 `); 101 102 // DoWhile 103 assertBreakpoints(` 104 do { 105 /*S*/fn(); 106 } while(/*S*/a) 107 do { 108 /*S*/fn(); 109 } while(/*S*/"42" + /*B*/a()); 110 `); 111 112 // While 113 assertBreakpoints(` 114 while(/*S*/a) { 115 /*S*/fn(); 116 } 117 while(/*S*/"42" + /*B*/a()) { 118 /*S*/fn(); 119 } 120 `); 121 122 // ForExpr 123 assertBreakpoints(` 124 for (/*S*/b = 42; /*S*/c; /*S*/d) /*S*/fn(); 125 for (var b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn(); 126 for (let b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn(); 127 for (const b = /*S*/42; /*S*/c; /*S*/d) /*S*/fn(); 128 for (b in /*S*/d) /*S*/fn(); 129 for (var b in /*S*/d) /*S*/fn(); 130 for (let b in /*S*/d) /*S*/fn(); 131 for (const b in /*S*/d) /*S*/fn(); 132 for (b of /*S*/d) /*S*/fn(); 133 for (var b of /*S*/d) /*S*/fn(); 134 for (let b of /*S*/d) /*S*/fn(); 135 for (const b of /*S*/d) /*S*/fn(); 136 `); 137 138 // SwitchStatement 139 assertBreakpoints(` 140 switch (/*S*/d) { 141 case 42: 142 /*S*/fn(); 143 } 144 `); 145 146 // ContinueStatement 147 assertBreakpoints(` 148 while (/*S*/a) { 149 /*S*/continue; 150 } 151 `); 152 153 // BreakStatement 154 assertBreakpoints(` 155 while (/*S*/a) { 156 /*S*/break; 157 } 158 `); 159 160 // ReturnStatement 161 assertBreakpoints(` 162 /*S*/return a + /*B*/b(); 163 `); 164 165 // WithStatement 166 assertBreakpoints(` 167 with (/*S*/a) { 168 /*S*/fn(); 169 } 170 `); 171 172 // ThrowStatement 173 assertBreakpoints(` 174 /*S*/throw /*B*/fn(); 175 /*S*/throw "42" + /*B*/fn(); 176 `); 177 178 // DebuggerStatement 179 assertBreakpoints(` 180 /*S*/debugger; 181 /*S*/debugger; 182 `); 183 184 // BlockStatent wrapper 185 assertBreakpoints(` 186 { 187 /*S*/a(); 188 } 189 `); 190 191 // ClassDeclaration 192 assertBreakpoints(` 193 class Foo2 {} 194 /*S*/class Foo extends ("" + o.a + /*B*/a() + /*B*/b()) { } 195 `); 196 197 // Misc examples 198 assertBreakpoints(` 199 /*S*/void /*B*/a(); 200 `); 201 assertBreakpoints(` 202 /*S*/a() + /*B*/b(); 203 `); 204 assertBreakpoints(` 205 for ( 206 var i = /*S*/0; 207 /*S*/i < n; // 4 208 /*S*/++i 209 ) { 210 /*S*/console./*B*/log("omg"); 211 } 212 `); 213 assertBreakpoints(` 214 function * gen(){ 215 var foo = ( 216 (/*S*/console./*B*/log('before', /*B*/a())), 217 (yield console./*B*/log('mid', /*B*/b())), 218 (console./*B*/log('after', /*B*/a())) 219 ); 220 var foo2 = /*S*/a() + /*B*/b(); 221 /*S*/console./*B*/log(foo); 222 /*B*/} 223 var i = /*S*/0; 224 for (var foo of /*S*/gen()) { 225 /*S*/console./*B*/log(i++); 226 } 227 `); 228 assertBreakpoints(` 229 var fn = /*S*/() => { 230 /*S*/console./*B*/log("fn"); 231 /*S*/return /*B*/new Proxy({ prop: 42 }, { 232 deleteProperty() { 233 /*S*/console./*B*/log("delete"); 234 /*B*/} 235 }); 236 /*B*/}; 237 `); 238 assertBreakpoints(` 239 var fn = /*S*/async (arg) => { 240 /*S*/console./*B*/log("fn"); 241 /*B*/}; 242 `); 243 assertBreakpoints(` 244 var fn = /*S*/arg => { 245 /*S*/console./*B*/log("fn"); 246 /*B*/}; 247 `); 248 assertBreakpoints(` 249 var fn = /*S*/async arg => { 250 /*S*/console./*B*/log("fn"); 251 /*B*/}; 252 `); 253 assertBreakpoints(` 254 var fn = /*S*/(arg) => /*S*/console./*B*/log("fn"); 255 var fn = /*S*/async (arg) => /*S*/console./*B*/log("fn"); 256 var fn = /*S*/arg => /*S*/console./*B*/log("fn"); 257 var fn = /*S*/async arg => /*S*/console./*B*/log("fn"); 258 `); 259 assertBreakpoints(` 260 if ((/*S*/delete /*B*/fn().prop) + /*B*/b()) { 261 /*S*/console./*B*/log("foo"); 262 } 263 `); 264 assertBreakpoints(` 265 for (var j = /*S*/0; (/*S*/o.a) < 3; (/*S*/j++, /*B*/a(), /*B*/b())) { 266 /*S*/console./*B*/log(i); 267 } 268 `); 269 assertBreakpoints(` 270 function fn2( 271 [a, b] = (/*B*/a(), /*B*/b()) 272 ) { 273 /*S*/a(); 274 /*S*/b(); 275 /*B*/} 276 277 ({ a, b } = (/*S*/a(), /*B*/b())); 278 `); 279 assertBreakpoints(` 280 /*S*/o.a + "42" + /*B*/a() + /*B*/b(); 281 `); 282 assertBreakpoints(` 283 /*S*/a(); 284 /*S*/o./*B*/a(/*B*/b()); 285 `); 286 assertBreakpoints(` 287 (/*S*/{}[obj.a] = 42 + /*B*/a()); 288 `); 289 assertBreakpoints(` 290 var { 291 foo = o.a 292 } = /*S*/{}; 293 `); 294 assertBreakpoints(` 295 var ack = /*S*/[ 296 o.a, 297 o.b, 298 /*B*/a(), 299 /*B*/a(), 300 /*B*/a(), 301 /*B*/a(), 302 /*B*/a(), 303 /*B*/a(), 304 /*B*/a(), 305 ]; 306 `); 307 308 function assertBreakpoints(expected) { 309 const input = expected.replace(/\/\*[BS]\*\//g, ""); 310 311 var global = newGlobal({ newCompartment: true }); 312 var dbg = Debugger(global); 313 dbg.onDebuggerStatement = function(frame) { 314 const fScript = frame.environment.parent.getVariable("f").script; 315 316 let positions = []; 317 (function recurse(script) { 318 const bps = script.getPossibleBreakpoints(); 319 const offsets = script.getPossibleBreakpointOffsets(); 320 321 assertEq(offsets.length, bps.length); 322 for (let i = 0; i < bps.length; i++) { 323 assertEq(offsets[i], bps[i].offset); 324 } 325 326 positions = positions.concat(bps); 327 script.getChildScripts().forEach(recurse); 328 })(fScript); 329 330 const result = annotateOffsets(input, positions); 331 assertEq(result, expected + "/*B*/"); 332 }; 333 334 global.eval(`function f(){${input}} debugger;`); 335 } 336 337 function annotateOffsets(code, positions) { 338 const offsetLookup = createOffsetLookup(code); 339 340 positions = positions.slice(); 341 positions.sort((a, b) => { 342 const lineDiff = a.lineNumber - b.lineNumber; 343 return lineDiff === 0 ? a.columnNumber - b.columnNumber : lineDiff; 344 }); 345 positions.reverse(); 346 347 let output = ""; 348 let last = code.length; 349 for (const { lineNumber, columnNumber, isStepStart } of positions) { 350 const offset = offsetLookup(lineNumber, columnNumber); 351 352 output = 353 "/*" + 354 (isStepStart ? "S" : "B") + 355 "*/" + 356 code.slice(offset, last) + 357 output; 358 last = offset; 359 } 360 return code.slice(0, last) + output; 361 } 362 363 function createOffsetLookup(code) { 364 const lines = code.split(/(\r?\n|\r|\u2028|\u2029)/g); 365 const lineOffsets = []; 366 367 let count = 0; 368 for (const [i, str] of lines.entries()) { 369 if (i % 2 === 0) { 370 lineOffsets[i / 2] = count; 371 } 372 count += str.length; 373 } 374 375 return function(line, column) { 376 // Lines from getAllColumnOffsets are 1-based. 377 line = line - 1; 378 379 if (!lineOffsets.hasOwnProperty(line)) { 380 throw new Error("Unknown line " + line + " column " + column); 381 } 382 return lineOffsets[line] + column - 1; 383 }; 384 }