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 34var DebuggerScript = {}; 35 36/** 37 * @param {?CompileEvent} eventData 38 */ 39DebuggerScript.getAfterCompileScript = function(eventData) 40{ 41 var script = eventData.script().value(); 42 if (!script.is_debugger_script) 43 return script; 44 return null; 45} 46 47/** @type {!Map<!ScopeType, string>} */ 48DebuggerScript._scopeTypeNames = new Map(); 49DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global"); 50DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local"); 51DebuggerScript._scopeTypeNames.set(ScopeType.With, "with"); 52DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure"); 53DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch"); 54DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block"); 55DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script"); 56 57/** 58 * @param {function()} fun 59 * @return {?Array<!Scope>} 60 */ 61DebuggerScript.getFunctionScopes = function(fun) 62{ 63 var mirror = MakeMirror(fun); 64 if (!mirror.isFunction()) 65 return null; 66 var functionMirror = /** @type {!FunctionMirror} */(mirror); 67 var count = functionMirror.scopeCount(); 68 if (count == 0) 69 return null; 70 var result = []; 71 for (var i = 0; i < count; i++) { 72 var scopeDetails = functionMirror.scope(i).details(); 73 var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); 74 if (!scopeObject) 75 continue; 76 result.push({ 77 type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), 78 object: scopeObject, 79 name: scopeDetails.name() || "" 80 }); 81 } 82 return result; 83} 84 85/** 86 * @param {Object} object 87 * @return {?RawLocation} 88 */ 89DebuggerScript.getGeneratorObjectLocation = function(object) 90{ 91 var mirror = MakeMirror(object, true /* transient */); 92 if (!mirror.isGenerator()) 93 return null; 94 var generatorMirror = /** @type {!GeneratorMirror} */(mirror); 95 var funcMirror = generatorMirror.func(); 96 if (!funcMirror.resolved()) 97 return null; 98 var location = generatorMirror.sourceLocation() || funcMirror.sourceLocation(); 99 var script = funcMirror.script(); 100 if (script && location) { 101 return { 102 scriptId: "" + script.id(), 103 lineNumber: location.line, 104 columnNumber: location.column 105 }; 106 } 107 return null; 108} 109 110/** 111 * @param {Object} object 112 * @return {!Array<!{value: *}>|undefined} 113 */ 114DebuggerScript.getCollectionEntries = function(object) 115{ 116 var mirror = MakeMirror(object, true /* transient */); 117 if (mirror.isMap()) 118 return /** @type {!MapMirror} */(mirror).entries(); 119 if (mirror.isSet() || mirror.isIterator()) { 120 var result = []; 121 var values = mirror.isSet() ? /** @type {!SetMirror} */(mirror).values() : /** @type {!IteratorMirror} */(mirror).preview(); 122 for (var i = 0; i < values.length; ++i) 123 result.push({ value: values[i] }); 124 return result; 125 } 126} 127 128/** 129 * @param {string|undefined} contextData 130 * @return {number} 131 */ 132DebuggerScript._executionContextId = function(contextData) 133{ 134 if (!contextData) 135 return 0; 136 var match = contextData.match(/^[^,]*,([^,]*),.*$/); 137 if (!match) 138 return 0; 139 return parseInt(match[1], 10) || 0; 140} 141 142/** 143 * @param {!ExecutionState} execState 144 * @param {!BreakpointInfo} info 145 * @return {string|undefined} 146 */ 147DebuggerScript.setBreakpoint = function(execState, info) 148{ 149 var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, Debug.BreakPositionAlignment.Statement); 150 var locations = Debug.findBreakPointActualLocations(breakId); 151 if (!locations.length) 152 return undefined; 153 info.lineNumber = locations[0].line; 154 info.columnNumber = locations[0].column; 155 return breakId.toString(); 156} 157 158/** 159 * @param {!ExecutionState} execState 160 * @param {!{breakpointId: number}} info 161 */ 162DebuggerScript.removeBreakpoint = function(execState, info) 163{ 164 Debug.findBreakPoint(info.breakpointId, true); 165} 166 167/** 168 * @param {!ExecutionState} execState 169 * @param {number} limit 170 * @return {!Array<!JavaScriptCallFrame>} 171 */ 172DebuggerScript.currentCallFrames = function(execState, limit) 173{ 174 var frames = []; 175 for (var i = 0; i < execState.frameCount() && (!limit || i < limit); ++i) 176 frames.push(DebuggerScript._frameMirrorToJSCallFrame(execState.frame(i))); 177 return frames; 178} 179 180// Returns array in form: 181// [ 0, <v8_result_report> ] in case of success 182// or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based. 183// or throws exception with message. 184/** 185 * @param {number} scriptId 186 * @param {string} newSource 187 * @param {boolean} preview 188 * @return {!Array<*>} 189 */ 190DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) 191{ 192 var scripts = Debug.scripts(); 193 var scriptToEdit = null; 194 for (var i = 0; i < scripts.length; i++) { 195 if (scripts[i].id == scriptId) { 196 scriptToEdit = scripts[i]; 197 break; 198 } 199 } 200 if (!scriptToEdit) 201 throw("Script not found"); 202 203 var changeLog = []; 204 try { 205 var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); 206 return [0, result.stack_modified]; 207 } catch (e) { 208 if (e instanceof Debug.LiveEdit.Failure && "details" in e) { 209 var details = /** @type {!LiveEditErrorDetails} */(e.details); 210 if (details.type === "liveedit_compile_error") { 211 var startPosition = details.position.start; 212 return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; 213 } 214 } 215 throw e; 216 } 217} 218 219/** 220 * @param {!ExecutionState} execState 221 */ 222DebuggerScript.clearBreakpoints = function(execState) 223{ 224 Debug.clearAllBreakPoints(); 225} 226 227/** 228 * @param {!ExecutionState} execState 229 * @param {!{enabled: boolean}} info 230 */ 231DebuggerScript.setBreakpointsActivated = function(execState, info) 232{ 233 Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); 234} 235 236/** 237 * @param {!BreakEvent} eventData 238 */ 239DebuggerScript.getBreakpointNumbers = function(eventData) 240{ 241 var breakpoints = eventData.breakPointsHit(); 242 var numbers = []; 243 if (!breakpoints) 244 return numbers; 245 246 for (var i = 0; i < breakpoints.length; i++) { 247 var breakpoint = breakpoints[i]; 248 var scriptBreakPoint = breakpoint.script_break_point(); 249 numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); 250 } 251 return numbers; 252} 253 254// NOTE: This function is performance critical, as it can be run on every 255// statement that generates an async event (like addEventListener) to support 256// asynchronous call stacks. Thus, when possible, initialize the data lazily. 257/** 258 * @param {!FrameMirror} frameMirror 259 * @return {!JavaScriptCallFrame} 260 */ 261DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror) 262{ 263 // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). 264 // The frameMirror and scopeMirror can be accessed only while paused on the debugger. 265 var frameDetails = frameMirror.details(); 266 267 var funcObject = frameDetails.func(); 268 var scriptObject = frameDetails.script(); 269 var sourcePosition = frameDetails.sourcePosition(); 270 var thisObject = frameDetails.receiver(); 271 272 var isAtReturn = !!frameDetails.isAtReturn(); 273 var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; 274 275 var scopeMirrors = frameMirror.allScopes(false); 276 /** @type {!Array<number>} */ 277 var scopeTypes = new Array(scopeMirrors.length); 278 /** @type {?Array<!Object>} */ 279 var scopeObjects = new Array(scopeMirrors.length); 280 /** @type {!Array<string|undefined>} */ 281 var scopeNames = new Array(scopeMirrors.length); 282 /** @type {?Array<number>} */ 283 var scopeStartPositions = new Array(scopeMirrors.length); 284 /** @type {?Array<number>} */ 285 var scopeEndPositions = new Array(scopeMirrors.length); 286 /** @type {?Array<function()|null>} */ 287 var scopeFunctions = new Array(scopeMirrors.length); 288 for (var i = 0; i < scopeMirrors.length; ++i) { 289 var scopeDetails = scopeMirrors[i].details(); 290 scopeTypes[i] = scopeDetails.type(); 291 scopeObjects[i] = scopeDetails.object(); 292 scopeNames[i] = scopeDetails.name(); 293 scopeStartPositions[i] = scopeDetails.startPosition ? scopeDetails.startPosition() : 0; 294 scopeEndPositions[i] = scopeDetails.endPosition ? scopeDetails.endPosition() : 0; 295 scopeFunctions[i] = scopeDetails.func ? scopeDetails.func() : null; 296 } 297 298 // Calculated lazily. 299 var scopeChain; 300 var funcMirror; 301 var scriptMirror; 302 var location; 303 /** @type {!Array<?RawLocation>} */ 304 var scopeStartLocations; 305 /** @type {!Array<?RawLocation>} */ 306 var scopeEndLocations; 307 var details; 308 309 /** 310 * @param {!ScriptMirror|undefined} script 311 * @param {number} pos 312 * @return {?RawLocation} 313 */ 314 function createLocation(script, pos) 315 { 316 if (!script) 317 return null; 318 319 var location = script.locationFromPosition(pos, true); 320 return { 321 "lineNumber": location.line, 322 "columnNumber": location.column, 323 "scriptId": String(script.id()) 324 } 325 } 326 327 /** 328 * @return {!Array<!Object>} 329 */ 330 function ensureScopeChain() 331 { 332 if (!scopeChain) { 333 scopeChain = []; 334 scopeStartLocations = []; 335 scopeEndLocations = []; 336 for (var i = 0, j = 0; i < scopeObjects.length; ++i) { 337 var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); 338 if (scopeObject) { 339 scopeTypes[j] = scopeTypes[i]; 340 scopeNames[j] = scopeNames[i]; 341 scopeChain[j] = scopeObject; 342 343 var funcMirror = scopeFunctions ? MakeMirror(scopeFunctions[i]) : null; 344 if (!funcMirror || !funcMirror.isFunction()) 345 funcMirror = new UnresolvedFunctionMirror(funcObject); 346 347 var script = /** @type {!FunctionMirror} */(funcMirror).script(); 348 scopeStartLocations[j] = createLocation(script, scopeStartPositions[i]); 349 scopeEndLocations[j] = createLocation(script, scopeEndPositions[i]); 350 ++j; 351 } 352 } 353 scopeTypes.length = scopeChain.length; 354 scopeNames.length = scopeChain.length; 355 scopeObjects = null; // Free for GC. 356 scopeFunctions = null; 357 scopeStartPositions = null; 358 scopeEndPositions = null; 359 } 360 return scopeChain; 361 } 362 363 /** 364 * @return {!JavaScriptCallFrameDetails} 365 */ 366 function lazyDetails() 367 { 368 if (!details) { 369 var scopeObjects = ensureScopeChain(); 370 var script = ensureScriptMirror(); 371 /** @type {!Array<Scope>} */ 372 var scopes = []; 373 for (var i = 0; i < scopeObjects.length; ++i) { 374 var scope = { 375 "type": /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeTypes[i])), 376 "object": scopeObjects[i], 377 }; 378 if (scopeNames[i]) 379 scope.name = scopeNames[i]; 380 if (scopeStartLocations[i]) 381 scope.startLocation = /** @type {!RawLocation} */(scopeStartLocations[i]); 382 if (scopeEndLocations[i]) 383 scope.endLocation = /** @type {!RawLocation} */(scopeEndLocations[i]); 384 scopes.push(scope); 385 } 386 details = { 387 "functionName": ensureFuncMirror().debugName(), 388 "location": { 389 "lineNumber": line(), 390 "columnNumber": column(), 391 "scriptId": String(script.id()) 392 }, 393 "this": thisObject, 394 "scopeChain": scopes 395 }; 396 var functionLocation = ensureFuncMirror().sourceLocation(); 397 if (functionLocation) { 398 details.functionLocation = { 399 "lineNumber": functionLocation.line, 400 "columnNumber": functionLocation.column, 401 "scriptId": String(script.id()) 402 }; 403 } 404 if (isAtReturn) 405 details.returnValue = returnValue; 406 } 407 return details; 408 } 409 410 /** 411 * @return {!FunctionMirror} 412 */ 413 function ensureFuncMirror() 414 { 415 if (!funcMirror) { 416 funcMirror = MakeMirror(funcObject); 417 if (!funcMirror.isFunction()) 418 funcMirror = new UnresolvedFunctionMirror(funcObject); 419 } 420 return /** @type {!FunctionMirror} */(funcMirror); 421 } 422 423 /** 424 * @return {!ScriptMirror} 425 */ 426 function ensureScriptMirror() 427 { 428 if (!scriptMirror) { 429 scriptMirror = MakeMirror(scriptObject); 430 } 431 return /** @type {!ScriptMirror} */(scriptMirror); 432 } 433 434 /** 435 * @return {!{line: number, column: number}} 436 */ 437 function ensureLocation() 438 { 439 if (!location) { 440 var script = ensureScriptMirror(); 441 location = script.locationFromPosition(sourcePosition, true); 442 if (!location) 443 location = { line: 0, column: 0 }; 444 } 445 return location; 446 } 447 448 /** 449 * @return {number} 450 */ 451 function line() 452 { 453 return ensureLocation().line; 454 } 455 456 /** 457 * @return {number} 458 */ 459 function column() 460 { 461 return ensureLocation().column; 462 } 463 464 /** 465 * @return {number} 466 */ 467 function contextId() 468 { 469 var mirror = ensureFuncMirror(); 470 // Old V8 do not have context() function on these objects 471 if (!mirror.context) 472 return DebuggerScript._executionContextId(mirror.script().value().context_data); 473 var context = mirror.context(); 474 if (context) 475 return DebuggerScript._executionContextId(context.data()); 476 return 0; 477 } 478 479 /** 480 * @return {number} 481 */ 482 function sourceID() 483 { 484 var script = ensureScriptMirror(); 485 return script.id(); 486 } 487 488 /** 489 * @param {string} expression 490 * @return {*} 491 */ 492 function evaluate(expression) 493 { 494 return frameMirror.evaluate(expression, false).value(); 495 } 496 497 /** @return {undefined} */ 498 function restart() 499 { 500 return frameMirror.restart(); 501 } 502 503 /** 504 * @param {number} scopeNumber 505 * @param {string} variableName 506 * @param {*} newValue 507 */ 508 function setVariableValue(scopeNumber, variableName, newValue) 509 { 510 var scopeMirror = frameMirror.scope(scopeNumber); 511 if (!scopeMirror) 512 throw new Error("Incorrect scope index"); 513 scopeMirror.setVariableValue(variableName, newValue); 514 } 515 516 return { 517 "sourceID": sourceID, 518 "line": line, 519 "column": column, 520 "contextId": contextId, 521 "thisObject": thisObject, 522 "evaluate": evaluate, 523 "restart": restart, 524 "setVariableValue": setVariableValue, 525 "isAtReturn": isAtReturn, 526 "details": lazyDetails 527 }; 528} 529 530/** 531 * @param {number} scopeType 532 * @param {!Object} scopeObject 533 * @return {!Object|undefined} 534 */ 535DebuggerScript._buildScopeObject = function(scopeType, scopeObject) 536{ 537 var result; 538 switch (scopeType) { 539 case ScopeType.Local: 540 case ScopeType.Closure: 541 case ScopeType.Catch: 542 case ScopeType.Block: 543 case ScopeType.Script: 544 // For transient objects we create a "persistent" copy that contains 545 // the same properties. 546 // Reset scope object prototype to null so that the proto properties 547 // don't appear in the local scope section. 548 var properties = /** @type {!ObjectMirror} */(MakeMirror(scopeObject, true /* transient */)).properties(); 549 // Almost always Script scope will be empty, so just filter out that noise. 550 // Also drop empty Block scopes, should we get any. 551 if (!properties.length && (scopeType === ScopeType.Script || scopeType === ScopeType.Block)) 552 break; 553 result = { __proto__: null }; 554 for (var j = 0; j < properties.length; j++) { 555 var name = properties[j].name(); 556 if (name.length === 0 || name.charAt(0) === ".") 557 continue; // Skip internal variables like ".arguments" and variables with empty name 558 result[name] = properties[j].value_; 559 } 560 break; 561 case ScopeType.Global: 562 case ScopeType.With: 563 result = scopeObject; 564 break; 565 } 566 return result; 567} 568 569// We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. 570ToggleMirrorCache(false); 571 572return DebuggerScript; 573})(); 574