/* * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ "use strict"; (function () { var DebuggerScript = {}; /** @type {!Map} */ DebuggerScript._scopeTypeNames = new Map(); DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global"); DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local"); DebuggerScript._scopeTypeNames.set(ScopeType.With, "with"); DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure"); DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch"); DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block"); DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script"); DebuggerScript._scopeTypeNames.set(ScopeType.Eval, "eval"); DebuggerScript._scopeTypeNames.set(ScopeType.Module, "module"); /** * @param {function()} fun * @return {?Array} */ DebuggerScript.getFunctionScopes = function(fun) { var mirror = MakeMirror(fun); if (!mirror.isFunction()) return null; var functionMirror = /** @type {!FunctionMirror} */(mirror); var count = functionMirror.scopeCount(); if (count == 0) return null; var result = []; for (var i = 0; i < count; i++) { var scopeDetails = functionMirror.scope(i).details(); var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); if (!scopeObject) continue; result.push({ type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), object: scopeObject, name: scopeDetails.name() || "" }); } return result; } /** * @param {Object} gen * @return {?Array} */ DebuggerScript.getGeneratorScopes = function(gen) { var mirror = MakeMirror(gen); if (!mirror.isGenerator()) return null; var generatorMirror = /** @type {!GeneratorMirror} */(mirror); var count = generatorMirror.scopeCount(); if (count == 0) return null; var result = []; for (var i = 0; i < count; i++) { var scopeDetails = generatorMirror.scope(i).details(); var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()); if (!scopeObject) continue; result.push({ type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())), object: scopeObject, name: scopeDetails.name() || "" }); } return result; } /** * @param {!ExecutionState} execState * @param {!BreakpointInfo} info * @return {string|undefined} */ DebuggerScript.setBreakpoint = function(execState, info) { var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, Debug.BreakPositionAlignment.BreakPosition); var locations = Debug.findBreakPointActualLocations(breakId); if (!locations.length) return undefined; info.lineNumber = locations[0].line; info.columnNumber = locations[0].column; return breakId.toString(); } /** * @param {!ExecutionState} execState * @param {!{breakpointId: number}} info */ DebuggerScript.removeBreakpoint = function(execState, info) { Debug.findBreakPoint(info.breakpointId, true); } /** * @param {!ExecutionState} execState * @param {number} limit * @return {!Array} */ DebuggerScript.currentCallFrames = function(execState, limit) { var frames = []; for (var i = 0; i < execState.frameCount() && (!limit || i < limit); ++i) frames.push(DebuggerScript._frameMirrorToJSCallFrame(execState.frame(i))); return frames; } // Returns array in form: // [ 0, ] in case of success // or [ 1, , , , ] in case of compile error, numbers are 1-based. // or throws exception with message. /** * @param {number} scriptId * @param {string} newSource * @param {boolean} preview * @return {!Array<*>} */ DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) { var scripts = Debug.scripts(); var scriptToEdit = null; for (var i = 0; i < scripts.length; i++) { if (scripts[i].id == scriptId) { scriptToEdit = scripts[i]; break; } } if (!scriptToEdit) throw("Script not found"); var changeLog = []; try { var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); return [0, result.stack_modified]; } catch (e) { if (e instanceof Debug.LiveEdit.Failure && "details" in e) { var details = /** @type {!LiveEditErrorDetails} */(e.details); if (details.type === "liveedit_compile_error") { var startPosition = details.position.start; return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; } } throw e; } } /** * @param {!ExecutionState} execState */ DebuggerScript.clearBreakpoints = function(execState) { Debug.clearAllBreakPoints(); } /** * @param {!Array|undefined} breakpoints */ DebuggerScript.getBreakpointNumbers = function(breakpoints) { var numbers = []; if (!breakpoints) return numbers; for (var i = 0; i < breakpoints.length; i++) { var breakpoint = breakpoints[i]; var scriptBreakPoint = breakpoint.script_break_point(); numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); } return numbers; } // NOTE: This function is performance critical, as it can be run on every // statement that generates an async event (like addEventListener) to support // asynchronous call stacks. Thus, when possible, initialize the data lazily. /** * @param {!FrameMirror} frameMirror * @return {!JavaScriptCallFrame} */ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror) { // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). // The frameMirror and scopeMirror can be accessed only while paused on the debugger. var frameDetails = frameMirror.details(); var funcObject = frameDetails.func(); var scriptObject = frameDetails.script(); var sourcePosition = frameDetails.sourcePosition(); var thisObject = frameDetails.receiver(); var isAtReturn = !!frameDetails.isAtReturn(); var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; var scopeMirrors = frameMirror.allScopes(false); /** @type {!Array} */ var scopeTypes = new Array(scopeMirrors.length); /** @type {?Array} */ var scopeObjects = new Array(scopeMirrors.length); /** @type {!Array} */ var scopeNames = new Array(scopeMirrors.length); /** @type {?Array} */ var scopeStartPositions = new Array(scopeMirrors.length); /** @type {?Array} */ var scopeEndPositions = new Array(scopeMirrors.length); /** @type {?Array} */ var scopeFunctions = new Array(scopeMirrors.length); for (var i = 0; i < scopeMirrors.length; ++i) { var scopeDetails = scopeMirrors[i].details(); scopeTypes[i] = scopeDetails.type(); scopeObjects[i] = scopeDetails.object(); scopeNames[i] = scopeDetails.name(); scopeStartPositions[i] = scopeDetails.startPosition ? scopeDetails.startPosition() : 0; scopeEndPositions[i] = scopeDetails.endPosition ? scopeDetails.endPosition() : 0; scopeFunctions[i] = scopeDetails.func ? scopeDetails.func() : null; } // Calculated lazily. var scopeChain; var funcMirror; var scriptMirror; var location; /** @type {!Array} */ var scopeStartLocations; /** @type {!Array} */ var scopeEndLocations; var details; /** * @param {!ScriptMirror|undefined} script * @param {number} pos * @return {?RawLocation} */ function createLocation(script, pos) { if (!script) return null; var location = script.locationFromPosition(pos, true); return { "lineNumber": location.line, "columnNumber": location.column, "scriptId": String(script.id()) } } /** * @return {!Array} */ function ensureScopeChain() { if (!scopeChain) { scopeChain = []; scopeStartLocations = []; scopeEndLocations = []; for (var i = 0, j = 0; i < scopeObjects.length; ++i) { var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]); if (scopeObject) { scopeTypes[j] = scopeTypes[i]; scopeNames[j] = scopeNames[i]; scopeChain[j] = scopeObject; var funcMirror = scopeFunctions ? MakeMirror(scopeFunctions[i]) : null; if (!funcMirror || !funcMirror.isFunction()) funcMirror = new UnresolvedFunctionMirror(funcObject); var script = /** @type {!FunctionMirror} */(funcMirror).script(); scopeStartLocations[j] = createLocation(script, scopeStartPositions[i]); scopeEndLocations[j] = createLocation(script, scopeEndPositions[i]); ++j; } } scopeTypes.length = scopeChain.length; scopeNames.length = scopeChain.length; scopeObjects = null; // Free for GC. scopeFunctions = null; scopeStartPositions = null; scopeEndPositions = null; } return scopeChain; } /** * @return {!JavaScriptCallFrameDetails} */ function lazyDetails() { if (!details) { var scopeObjects = ensureScopeChain(); var script = ensureScriptMirror(); /** @type {!Array} */ var scopes = []; for (var i = 0; i < scopeObjects.length; ++i) { var scope = { "type": /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeTypes[i])), "object": scopeObjects[i], }; if (scopeNames[i]) scope.name = scopeNames[i]; if (scopeStartLocations[i]) scope.startLocation = /** @type {!RawLocation} */(scopeStartLocations[i]); if (scopeEndLocations[i]) scope.endLocation = /** @type {!RawLocation} */(scopeEndLocations[i]); scopes.push(scope); } details = { "functionName": ensureFuncMirror().debugName(), "location": { "lineNumber": ensureLocation().line, "columnNumber": ensureLocation().column, "scriptId": String(script.id()) }, "this": thisObject, "scopeChain": scopes }; var functionLocation = ensureFuncMirror().sourceLocation(); if (functionLocation) { details.functionLocation = { "lineNumber": functionLocation.line, "columnNumber": functionLocation.column, "scriptId": String(script.id()) }; } if (isAtReturn) details.returnValue = returnValue; } return details; } /** * @return {!FunctionMirror} */ function ensureFuncMirror() { if (!funcMirror) { funcMirror = MakeMirror(funcObject); if (!funcMirror.isFunction()) funcMirror = new UnresolvedFunctionMirror(funcObject); } return /** @type {!FunctionMirror} */(funcMirror); } /** * @return {!ScriptMirror} */ function ensureScriptMirror() { if (!scriptMirror) { scriptMirror = MakeMirror(scriptObject); } return /** @type {!ScriptMirror} */(scriptMirror); } /** * @return {!{line: number, column: number}} */ function ensureLocation() { if (!location) { var script = ensureScriptMirror(); location = script.locationFromPosition(sourcePosition, true); if (!location) location = { line: 0, column: 0 }; } return location; } /** * @return {number} */ function contextId() { var mirror = ensureFuncMirror(); var context = mirror.context(); if (context && context.data()) return Number(context.data()); return 0; } /** * @param {string} expression * @param {boolean} throwOnSideEffect * @return {*} */ function evaluate(expression, throwOnSideEffect) { return frameMirror.evaluate(expression, throwOnSideEffect).value(); } /** @return {undefined} */ function restart() { return frameMirror.restart(); } /** * @param {number} scopeNumber * @param {string} variableName * @param {*} newValue */ function setVariableValue(scopeNumber, variableName, newValue) { var scopeMirror = frameMirror.scope(scopeNumber); if (!scopeMirror) throw new Error("Incorrect scope index"); scopeMirror.setVariableValue(variableName, newValue); } return { "contextId": contextId, "thisObject": thisObject, "evaluate": evaluate, "restart": restart, "setVariableValue": setVariableValue, "isAtReturn": isAtReturn, "details": lazyDetails }; } /** * @param {number} scopeType * @param {!Object} scopeObject * @return {!Object|undefined} */ DebuggerScript._buildScopeObject = function(scopeType, scopeObject) { var result; switch (scopeType) { case ScopeType.Local: case ScopeType.Closure: case ScopeType.Catch: case ScopeType.Block: case ScopeType.Script: case ScopeType.Eval: case ScopeType.Module: // For transient objects we create a "persistent" copy that contains // the same properties. // Reset scope object prototype to null so that the proto properties // don't appear in the local scope section. var properties = /** @type {!ObjectMirror} */(MakeMirror(scopeObject)).properties(); // Almost always Script scope will be empty, so just filter out that noise. // Also drop empty Block, Eval and Script scopes, should we get any. if (!properties.length && (scopeType === ScopeType.Script || scopeType === ScopeType.Block || scopeType === ScopeType.Eval || scopeType === ScopeType.Module)) { break; } result = { __proto__: null }; for (var j = 0; j < properties.length; j++) { var name = properties[j].name(); if (name.length === 0 || name.charAt(0) === ".") continue; // Skip internal variables like ".arguments" and variables with empty name result[name] = properties[j].value_; } break; case ScopeType.Global: case ScopeType.With: result = scopeObject; break; } return result; } return DebuggerScript; })();