blob: 4ddc9a7d21209514e13e74aaaec080cf0cfbcf05 [file] [log] [blame]
/*
* 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<!ScopeType, string>} */
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<!Scope>}
*/
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<!Scope>}
*/
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.Statement);
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<!JavaScriptCallFrame>}
*/
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, <v8_result_report> ] in case of success
// or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] 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<!BreakPoint>|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<number>} */
var scopeTypes = new Array(scopeMirrors.length);
/** @type {?Array<!Object>} */
var scopeObjects = new Array(scopeMirrors.length);
/** @type {!Array<string|undefined>} */
var scopeNames = new Array(scopeMirrors.length);
/** @type {?Array<number>} */
var scopeStartPositions = new Array(scopeMirrors.length);
/** @type {?Array<number>} */
var scopeEndPositions = new Array(scopeMirrors.length);
/** @type {?Array<function()|null>} */
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<?RawLocation>} */
var scopeStartLocations;
/** @type {!Array<?RawLocation>} */
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<!Object>}
*/
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<Scope>} */
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;
})();