1 /* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 "use strict"; 31 32 (function () { 33 34 var DebuggerScript = {}; 35 36 /** @type {!Map<!ScopeType, string>} */ 37 DebuggerScript._scopeTypeNames = new Map(); 38 DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global"); 39 DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local"); 40 DebuggerScript._scopeTypeNames.set(ScopeType.With, "with"); 41 DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure"); 42 DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch"); 43 DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block"); 44 DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script"); 45 DebuggerScript._scopeTypeNames.set(ScopeType.Eval, "eval"); 46 DebuggerScript._scopeTypeNames.set(ScopeType.Module, "module"); 47 48 /** 49 * @param {function()} fun 50 * @return {?Array<!Scope>} 51 */ 52 DebuggerScript.getFunctionScopes = function(fun) 53 { 54 var mirror = MakeMirror(fun); 55 if (!mirror.isFunction()) 56 return null; 57 var functionMirror = /** @type {!FunctionMirror} */(mirror); 58 var count = functionMirror.scopeCount(); 59 if (count == 0) 60 return null; 61 var result = []; 62 for (var i = 0; i < count; i++) { 63 var scopeDetails = functionMirror.scope(i).details(); 64 var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); 65 if (!scopeObject) 66 continue; 67 result.push({ 68 type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), 69 object: scopeObject, 70 name: scopeDetails.name() || "" 71 }); 72 } 73 return result; 74 } 75 76 /** 77 * @param {Object} gen 78 * @return {?Array<!Scope>} 79 */ 80 DebuggerScript.getGeneratorScopes = function(gen) 81 { 82 var mirror = MakeMirror(gen); 83 if (!mirror.isGenerator()) 84 return null; 85 var generatorMirror = /** @type {!GeneratorMirror} */(mirror); 86 var count = generatorMirror.scopeCount(); 87 if (count == 0) 88 return null; 89 var result = []; 90 for (var i = 0; i < count; i++) { 91 var scopeDetails = generatorMirror.scope(i).details(); 92 var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); 93 if (!scopeObject) 94 continue; 95 result.push({ 96 type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), 97 object: scopeObject, 98 name: scopeDetails.name() || "" 99 }); 100 } 101 return result; 102 } 103 104 /** 105 * @param {!ExecutionState} execState 106 * @param {!BreakpointInfo} info 107 * @return {string|undefined} 108 */ 109 DebuggerScript.setBreakpoint = function(execState, info) 110 { 111 var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, Debug.BreakPositionAlignment.BreakPosition); 112 var locations = Debug.findBreakPointActualLocations(breakId); 113 if (!locations.length) 114 return undefined; 115 info.lineNumber = locations[0].line; 116 info.columnNumber = locations[0].column; 117 return breakId.toString(); 118 } 119 120 /** 121 * @param {!ExecutionState} execState 122 * @param {!{breakpointId: number}} info 123 */ 124 DebuggerScript.removeBreakpoint = function(execState, info) 125 { 126 Debug.findBreakPoint(info.breakpointId, true); 127 } 128 129 /** 130 * @param {!ExecutionState} execState 131 * @param {number} limit 132 * @return {!Array<!JavaScriptCallFrame>} 133 */ 134 DebuggerScript.currentCallFrames = function(execState, limit) 135 { 136 var frames = []; 137 for (var i = 0; i < execState.frameCount() && (!limit || i < limit); ++i) 138 frames.push(DebuggerScript._frameMirrorToJSCallFrame(execState.frame(i))); 139 return frames; 140 } 141 142 // Returns array in form: 143 // [ 0, <v8_result_report> ] in case of success 144 // or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based. 145 // or throws exception with message. 146 /** 147 * @param {number} scriptId 148 * @param {string} newSource 149 * @param {boolean} preview 150 * @return {!Array<*>} 151 */ 152 DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) 153 { 154 var scripts = Debug.scripts(); 155 var scriptToEdit = null; 156 for (var i = 0; i < scripts.length; i++) { 157 if (scripts[i].id == scriptId) { 158 scriptToEdit = scripts[i]; 159 break; 160 } 161 } 162 if (!scriptToEdit) 163 throw("Script not found"); 164 165 var changeLog = []; 166 try { 167 var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); 168 return [0, result.stack_modified]; 169 } catch (e) { 170 if (e instanceof Debug.LiveEdit.Failure && "details" in e) { 171 var details = /** @type {!LiveEditErrorDetails} */(e.details); 172 if (details.type === "liveedit_compile_error") { 173 var startPosition = details.position.start; 174 return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; 175 } 176 } 177 throw e; 178 } 179 } 180 181 /** 182 * @param {!ExecutionState} execState 183 */ 184 DebuggerScript.clearBreakpoints = function(execState) 185 { 186 Debug.clearAllBreakPoints(); 187 } 188 189 /** 190 * @param {!Array<!BreakPoint>|undefined} breakpoints 191 */ 192 DebuggerScript.getBreakpointNumbers = function(breakpoints) 193 { 194 var numbers = []; 195 if (!breakpoints) 196 return numbers; 197 198 for (var i = 0; i < breakpoints.length; i++) { 199 var breakpoint = breakpoints[i]; 200 var scriptBreakPoint = breakpoint.script_break_point(); 201 numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); 202 } 203 return numbers; 204 } 205 206 // NOTE: This function is performance critical, as it can be run on every 207 // statement that generates an async event (like addEventListener) to support 208 // asynchronous call stacks. Thus, when possible, initialize the data lazily. 209 /** 210 * @param {!FrameMirror} frameMirror 211 * @return {!JavaScriptCallFrame} 212 */ 213 DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror) 214 { 215 // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). 216 // The frameMirror and scopeMirror can be accessed only while paused on the debugger. 217 var frameDetails = frameMirror.details(); 218 219 var funcObject = frameDetails.func(); 220 var scriptObject = frameDetails.script(); 221 var sourcePosition = frameDetails.sourcePosition(); 222 var thisObject = frameDetails.receiver(); 223 224 var isAtReturn = !!frameDetails.isAtReturn(); 225 var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; 226 227 var scopeMirrors = frameMirror.allScopes(false); 228 /** @type {!Array<number>} */ 229 var scopeTypes = new Array(scopeMirrors.length); 230 /** @type {?Array<!Object>} */ 231 var scopeObjects = new Array(scopeMirrors.length); 232 /** @type {!Array<string|undefined>} */ 233 var scopeNames = new Array(scopeMirrors.length); 234 /** @type {?Array<number>} */ 235 var scopeStartPositions = new Array(scopeMirrors.length); 236 /** @type {?Array<number>} */ 237 var scopeEndPositions = new Array(scopeMirrors.length); 238 /** @type {?Array<function()|null>} */ 239 var scopeFunctions = new Array(scopeMirrors.length); 240 for (var i = 0; i < scopeMirrors.length; ++i) { 241 var scopeDetails = scopeMirrors[i].details(); 242 scopeTypes[i] = scopeDetails.type(); 243 scopeObjects[i] = scopeDetails.object(); 244 scopeNames[i] = scopeDetails.name(); 245 scopeStartPositions[i] = scopeDetails.startPosition ? scopeDetails.startPosition() : 0; 246 scopeEndPositions[i] = scopeDetails.endPosition ? scopeDetails.endPosition() : 0; 247 scopeFunctions[i] = scopeDetails.func ? scopeDetails.func() : null; 248 } 249 250 // Calculated lazily. 251 var scopeChain; 252 var funcMirror; 253 var scriptMirror; 254 var location; 255 /** @type {!Array<?RawLocation>} */ 256 var scopeStartLocations; 257 /** @type {!Array<?RawLocation>} */ 258 var scopeEndLocations; 259 var details; 260 261 /** 262 * @param {!ScriptMirror|undefined} script 263 * @param {number} pos 264 * @return {?RawLocation} 265 */ 266 function createLocation(script, pos) 267 { 268 if (!script) 269 return null; 270 271 var location = script.locationFromPosition(pos, true); 272 return { 273 "lineNumber": location.line, 274 "columnNumber": location.column, 275 "scriptId": String(script.id()) 276 } 277 } 278 279 /** 280 * @return {!Array<!Object>} 281 */ 282 function ensureScopeChain() 283 { 284 if (!scopeChain) { 285 scopeChain = []; 286 scopeStartLocations = []; 287 scopeEndLocations = []; 288 for (var i = 0, j = 0; i < scopeObjects.length; ++i) { 289 var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); 290 if (scopeObject) { 291 scopeTypes[j] = scopeTypes[i]; 292 scopeNames[j] = scopeNames[i]; 293 scopeChain[j] = scopeObject; 294 295 var funcMirror = scopeFunctions ? MakeMirror(scopeFunctions[i]) : null; 296 if (!funcMirror || !funcMirror.isFunction()) 297 funcMirror = new UnresolvedFunctionMirror(funcObject); 298 299 var script = /** @type {!FunctionMirror} */(funcMirror).script(); 300 scopeStartLocations[j] = createLocation(script, scopeStartPositions[i]); 301 scopeEndLocations[j] = createLocation(script, scopeEndPositions[i]); 302 ++j; 303 } 304 } 305 scopeTypes.length = scopeChain.length; 306 scopeNames.length = scopeChain.length; 307 scopeObjects = null; // Free for GC. 308 scopeFunctions = null; 309 scopeStartPositions = null; 310 scopeEndPositions = null; 311 } 312 return scopeChain; 313 } 314 315 /** 316 * @return {!JavaScriptCallFrameDetails} 317 */ 318 function lazyDetails() 319 { 320 if (!details) { 321 var scopeObjects = ensureScopeChain(); 322 var script = ensureScriptMirror(); 323 /** @type {!Array<Scope>} */ 324 var scopes = []; 325 for (var i = 0; i < scopeObjects.length; ++i) { 326 var scope = { 327 "type": /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeTypes[i])), 328 "object": scopeObjects[i], 329 }; 330 if (scopeNames[i]) 331 scope.name = scopeNames[i]; 332 if (scopeStartLocations[i]) 333 scope.startLocation = /** @type {!RawLocation} */(scopeStartLocations[i]); 334 if (scopeEndLocations[i]) 335 scope.endLocation = /** @type {!RawLocation} */(scopeEndLocations[i]); 336 scopes.push(scope); 337 } 338 details = { 339 "functionName": ensureFuncMirror().debugName(), 340 "location": { 341 "lineNumber": ensureLocation().line, 342 "columnNumber": ensureLocation().column, 343 "scriptId": String(script.id()) 344 }, 345 "this": thisObject, 346 "scopeChain": scopes 347 }; 348 var functionLocation = ensureFuncMirror().sourceLocation(); 349 if (functionLocation) { 350 details.functionLocation = { 351 "lineNumber": functionLocation.line, 352 "columnNumber": functionLocation.column, 353 "scriptId": String(script.id()) 354 }; 355 } 356 if (isAtReturn) 357 details.returnValue = returnValue; 358 } 359 return details; 360 } 361 362 /** 363 * @return {!FunctionMirror} 364 */ 365 function ensureFuncMirror() 366 { 367 if (!funcMirror) { 368 funcMirror = MakeMirror(funcObject); 369 if (!funcMirror.isFunction()) 370 funcMirror = new UnresolvedFunctionMirror(funcObject); 371 } 372 return /** @type {!FunctionMirror} */(funcMirror); 373 } 374 375 /** 376 * @return {!ScriptMirror} 377 */ 378 function ensureScriptMirror() 379 { 380 if (!scriptMirror) { 381 scriptMirror = MakeMirror(scriptObject); 382 } 383 return /** @type {!ScriptMirror} */(scriptMirror); 384 } 385 386 /** 387 * @return {!{line: number, column: number}} 388 */ 389 function ensureLocation() 390 { 391 if (!location) { 392 var script = ensureScriptMirror(); 393 location = script.locationFromPosition(sourcePosition, true); 394 if (!location) 395 location = { line: 0, column: 0 }; 396 } 397 return location; 398 } 399 400 /** 401 * @return {number} 402 */ 403 function contextId() 404 { 405 var mirror = ensureFuncMirror(); 406 var context = mirror.context(); 407 if (context && context.data()) 408 return Number(context.data()); 409 return 0; 410 } 411 412 /** 413 * @param {string} expression 414 * @param {boolean} throwOnSideEffect 415 * @return {*} 416 */ 417 function evaluate(expression, throwOnSideEffect) 418 { 419 return frameMirror.evaluate(expression, throwOnSideEffect).value(); 420 } 421 422 /** @return {undefined} */ 423 function restart() 424 { 425 return frameMirror.restart(); 426 } 427 428 /** 429 * @param {number} scopeNumber 430 * @param {string} variableName 431 * @param {*} newValue 432 */ 433 function setVariableValue(scopeNumber, variableName, newValue) 434 { 435 var scopeMirror = frameMirror.scope(scopeNumber); 436 if (!scopeMirror) 437 throw new Error("Incorrect scope index"); 438 scopeMirror.setVariableValue(variableName, newValue); 439 } 440 441 return { 442 "contextId": contextId, 443 "thisObject": thisObject, 444 "evaluate": evaluate, 445 "restart": restart, 446 "setVariableValue": setVariableValue, 447 "isAtReturn": isAtReturn, 448 "details": lazyDetails 449 }; 450 } 451 452 /** 453 * @param {number} scopeType 454 * @param {!Object} scopeObject 455 * @return {!Object|undefined} 456 */ 457 DebuggerScript._buildScopeObject = function(scopeType, scopeObject) 458 { 459 var result; 460 switch (scopeType) { 461 case ScopeType.Local: 462 case ScopeType.Closure: 463 case ScopeType.Catch: 464 case ScopeType.Block: 465 case ScopeType.Script: 466 case ScopeType.Eval: 467 case ScopeType.Module: 468 // For transient objects we create a "persistent" copy that contains 469 // the same properties. 470 // Reset scope object prototype to null so that the proto properties 471 // don't appear in the local scope section. 472 var properties = /** @type {!ObjectMirror} */(MakeMirror(scopeObject)).properties(); 473 // Almost always Script scope will be empty, so just filter out that noise. 474 // Also drop empty Block, Eval and Script scopes, should we get any. 475 if (!properties.length && (scopeType === ScopeType.Script || 476 scopeType === ScopeType.Block || 477 scopeType === ScopeType.Eval || 478 scopeType === ScopeType.Module)) { 479 break; 480 } 481 result = { __proto__: null }; 482 for (var j = 0; j < properties.length; j++) { 483 var name = properties[j].name(); 484 if (name.length === 0 || name.charAt(0) === ".") 485 continue; // Skip internal variables like ".arguments" and variables with empty name 486 result[name] = properties[j].value_; 487 } 488 break; 489 case ScopeType.Global: 490 case ScopeType.With: 491 result = scopeObject; 492 break; 493 } 494 return result; 495 } 496 497 return DebuggerScript; 498 })(); 499