Home | History | Annotate | Download | only in inspector
      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