Home | History | Annotate | Download | only in v8
      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 
     31 (function () {
     32 
     33 var DebuggerScript = {};
     34 
     35 DebuggerScript.PauseOnExceptionsState = {
     36     DontPauseOnExceptions: 0,
     37     PauseOnAllExceptions: 1,
     38     PauseOnUncaughtExceptions: 2
     39 };
     40 
     41 DebuggerScript.ScopeInfoDetails = {
     42     AllScopes: 0,
     43     FastAsyncScopes: 1,
     44     NoScopes: 2
     45 };
     46 
     47 DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions;
     48 Debug.clearBreakOnException();
     49 Debug.clearBreakOnUncaughtException();
     50 
     51 DebuggerScript.getAfterCompileScript = function(eventData)
     52 {
     53     return DebuggerScript._formatScript(eventData.script_.script_);
     54 }
     55 
     56 DebuggerScript.getWorkerScripts = function()
     57 {
     58     var result = [];
     59     var scripts = Debug.scripts();
     60     for (var i = 0; i < scripts.length; ++i) {
     61         var script = scripts[i];
     62         // Workers don't share same V8 heap now so there is no need to complicate stuff with
     63         // the context id like we do to discriminate between scripts from different pages.
     64         // However we need to filter out v8 native scripts.
     65         if (script.context_data && script.context_data === "worker")
     66             result.push(DebuggerScript._formatScript(script));
     67     }
     68     return result;
     69 }
     70 
     71 DebuggerScript.getFunctionScopes = function(fun)
     72 {
     73     var mirror = MakeMirror(fun);
     74     var count = mirror.scopeCount();
     75     if (count == 0)
     76         return null;
     77     var result = [];
     78     for (var i = 0; i < count; i++) {
     79         var scopeDetails = mirror.scope(i).details();
     80         result[i] = {
     81             type: scopeDetails.type(),
     82             object: DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object())
     83         };
     84     }
     85     return result;
     86 }
     87 
     88 DebuggerScript.getCollectionEntries = function(object)
     89 {
     90     var mirror = MakeMirror(object, true /* transient */);
     91     if (mirror.isMap())
     92         return mirror.entries();
     93     if (mirror.isSet()) {
     94         var result = [];
     95         var values = mirror.values();
     96         for (var i = 0; i < values.length; ++i)
     97             result.push({ value: values[i] });
     98         return result;
     99     }
    100 }
    101 
    102 DebuggerScript.getInternalProperties = function(value)
    103 {
    104     var properties = ObjectMirror.GetInternalProperties(value);
    105     var result = [];
    106     for (var i = 0; i < properties.length; i++) {
    107         var mirror = properties[i];
    108         result.push({
    109             name: mirror.name(),
    110             value: mirror.value().value()
    111         });
    112     }
    113     return result;
    114 }
    115 
    116 DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue)
    117 {
    118     var mirror = MakeMirror(functionValue);
    119     if (!mirror.isFunction())
    120         throw new Error("Function value has incorrect type");
    121     return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue);
    122 }
    123 
    124 DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue)
    125 {
    126     var scopeMirror = scopeHolder.scope(scopeIndex);
    127     if (!scopeMirror)
    128         throw new Error("Incorrect scope index");
    129     scopeMirror.setVariableValue(variableName, newValue);
    130     return undefined;
    131 }
    132 
    133 DebuggerScript.getScripts = function(contextData)
    134 {
    135     var result = [];
    136 
    137     if (!contextData)
    138         return result;
    139     var comma = contextData.indexOf(",");
    140     if (comma === -1)
    141         return result;
    142     // Context data is a string in the following format:
    143     // ("page"|"injected")","<page id>
    144     var idSuffix = contextData.substring(comma); // including the comma
    145 
    146     var scripts = Debug.scripts();
    147     for (var i = 0; i < scripts.length; ++i) {
    148         var script = scripts[i];
    149         if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1)
    150             result.push(DebuggerScript._formatScript(script));
    151     }
    152     return result;
    153 }
    154 
    155 DebuggerScript._formatScript = function(script)
    156 {
    157     var lineEnds = script.line_ends;
    158     var lineCount = lineEnds.length;
    159     var endLine = script.line_offset + lineCount - 1;
    160     var endColumn;
    161     // V8 will not count last line if script source ends with \n.
    162     if (script.source[script.source.length - 1] === '\n') {
    163         endLine += 1;
    164         endColumn = 0;
    165     } else {
    166         if (lineCount === 1)
    167             endColumn = script.source.length + script.column_offset;
    168         else
    169             endColumn = script.source.length - (lineEnds[lineCount - 2] + 1);
    170     }
    171 
    172     return {
    173         id: script.id,
    174         name: script.nameOrSourceURL(),
    175         sourceURL: script.source_url,
    176         sourceMappingURL: script.source_mapping_url,
    177         source: script.source,
    178         startLine: script.line_offset,
    179         startColumn: script.column_offset,
    180         endLine: endLine,
    181         endColumn: endColumn,
    182         isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0
    183     };
    184 }
    185 
    186 DebuggerScript.setBreakpoint = function(execState, info)
    187 {
    188     var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement;
    189     var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment);
    190 
    191     var locations = Debug.findBreakPointActualLocations(breakId);
    192     if (!locations.length)
    193         return undefined;
    194     info.lineNumber = locations[0].line;
    195     info.columnNumber = locations[0].column;
    196     return breakId.toString();
    197 }
    198 
    199 DebuggerScript.removeBreakpoint = function(execState, info)
    200 {
    201     Debug.findBreakPoint(info.breakpointId, true);
    202 }
    203 
    204 DebuggerScript.pauseOnExceptionsState = function()
    205 {
    206     return DebuggerScript._pauseOnExceptionsState;
    207 }
    208 
    209 DebuggerScript.setPauseOnExceptionsState = function(newState)
    210 {
    211     DebuggerScript._pauseOnExceptionsState = newState;
    212 
    213     if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState)
    214         Debug.setBreakOnException();
    215     else
    216         Debug.clearBreakOnException();
    217 
    218     if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState)
    219         Debug.setBreakOnUncaughtException();
    220     else
    221         Debug.clearBreakOnUncaughtException();
    222 }
    223 
    224 DebuggerScript.frameCount = function(execState)
    225 {
    226     return execState.frameCount();
    227 }
    228 
    229 DebuggerScript.currentCallFrame = function(execState, data)
    230 {
    231     var maximumLimit = data >> 2;
    232     var scopeDetailsLevel = data & 3;
    233 
    234     var frameCount = execState.frameCount();
    235     if (maximumLimit && maximumLimit < frameCount)
    236         frameCount = maximumLimit;
    237     var topFrame = undefined;
    238     for (var i = frameCount - 1; i >= 0; i--) {
    239         var frameMirror = execState.frame(i);
    240         topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel);
    241     }
    242     return topFrame;
    243 }
    244 
    245 DebuggerScript.currentCallFrameByIndex = function(execState, index)
    246 {
    247     if (index < 0)
    248         return undefined;
    249     var frameCount = execState.frameCount();
    250     if (index >= frameCount)
    251         return undefined;
    252     return DebuggerScript._frameMirrorToJSCallFrame(execState.frame(index), undefined, DebuggerScript.ScopeInfoDetails.NoScopes);
    253 }
    254 
    255 DebuggerScript.stepIntoStatement = function(execState)
    256 {
    257     execState.prepareStep(Debug.StepAction.StepIn, 1);
    258 }
    259 
    260 DebuggerScript.stepOverStatement = function(execState, callFrame)
    261 {
    262     execState.prepareStep(Debug.StepAction.StepNext, 1);
    263 }
    264 
    265 DebuggerScript.stepOutOfFunction = function(execState, callFrame)
    266 {
    267     execState.prepareStep(Debug.StepAction.StepOut, 1);
    268 }
    269 
    270 // Returns array in form:
    271 //      [ 0, <v8_result_report> ] in case of success
    272 //   or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based.
    273 // or throws exception with message.
    274 DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview)
    275 {
    276     var scripts = Debug.scripts();
    277     var scriptToEdit = null;
    278     for (var i = 0; i < scripts.length; i++) {
    279         if (scripts[i].id == scriptId) {
    280             scriptToEdit = scripts[i];
    281             break;
    282         }
    283     }
    284     if (!scriptToEdit)
    285         throw("Script not found");
    286 
    287     var changeLog = [];
    288     try {
    289         var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog);
    290         return [0, result];
    291     } catch (e) {
    292         if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
    293             var details = e.details;
    294             if (details.type === "liveedit_compile_error") {
    295                 var startPosition = details.position.start;
    296                 return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)];
    297             }
    298         }
    299         throw e;
    300     }
    301 }
    302 
    303 DebuggerScript.clearBreakpoints = function(execState, info)
    304 {
    305     Debug.clearAllBreakPoints();
    306 }
    307 
    308 DebuggerScript.setBreakpointsActivated = function(execState, info)
    309 {
    310     Debug.debuggerFlags().breakPointsActive.setValue(info.enabled);
    311 }
    312 
    313 DebuggerScript.getScriptSource = function(eventData)
    314 {
    315     return eventData.script().source();
    316 }
    317 
    318 DebuggerScript.setScriptSource = function(eventData, source)
    319 {
    320     if (eventData.script().data() === "injected-script")
    321         return;
    322     eventData.script().setSource(source);
    323 }
    324 
    325 DebuggerScript.getScriptName = function(eventData)
    326 {
    327     return eventData.script().script_.nameOrSourceURL();
    328 }
    329 
    330 DebuggerScript.getBreakpointNumbers = function(eventData)
    331 {
    332     var breakpoints = eventData.breakPointsHit();
    333     var numbers = [];
    334     if (!breakpoints)
    335         return numbers;
    336 
    337     for (var i = 0; i < breakpoints.length; i++) {
    338         var breakpoint = breakpoints[i];
    339         var scriptBreakPoint = breakpoint.script_break_point();
    340         numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number());
    341     }
    342     return numbers;
    343 }
    344 
    345 DebuggerScript.isEvalCompilation = function(eventData)
    346 {
    347     var script = eventData.script();
    348     return (script.compilationType() === Debug.ScriptCompilationType.Eval);
    349 }
    350 
    351 // NOTE: This function is performance critical, as it can be run on every
    352 // statement that generates an async event (like addEventListener) to support
    353 // asynchronous call stacks. Thus, when possible, initialize the data lazily.
    354 DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel)
    355 {
    356     // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id).
    357     // The frameMirror and scopeMirror can be accessed only while paused on the debugger.
    358     var frameDetails = frameMirror.details();
    359 
    360     var funcObject = frameDetails.func();
    361     var sourcePosition = frameDetails.sourcePosition();
    362     var thisObject = frameDetails.receiver();
    363 
    364     var isAtReturn = !!frameDetails.isAtReturn();
    365     var returnValue = isAtReturn ? frameDetails.returnValue() : undefined;
    366 
    367     var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes));
    368     var scopeTypes = new Array(scopeMirrors.length);
    369     var scopeObjects = new Array(scopeMirrors.length);
    370     for (var i = 0; i < scopeMirrors.length; ++i) {
    371         var scopeDetails = scopeMirrors[i].details();
    372         scopeTypes[i] = scopeDetails.type();
    373         scopeObjects[i] = scopeDetails.object();
    374     }
    375 
    376     // Calculated lazily.
    377     var scopeChain;
    378     var funcMirror;
    379     var location;
    380 
    381     function lazyScopeChain()
    382     {
    383         if (!scopeChain) {
    384             scopeChain = [];
    385             for (var i = 0; i < scopeObjects.length; ++i)
    386                 scopeChain.push(DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]));
    387             scopeObjects = null; // Free for GC.
    388         }
    389         return scopeChain;
    390     }
    391 
    392     function ensureFuncMirror()
    393     {
    394         if (!funcMirror) {
    395             funcMirror = MakeMirror(funcObject);
    396             if (!funcMirror.isFunction())
    397                 funcMirror = new UnresolvedFunctionMirror(funcObject);
    398         }
    399         return funcMirror;
    400     }
    401 
    402     function ensureLocation()
    403     {
    404         if (!location) {
    405             var script = ensureFuncMirror().script();
    406             if (script)
    407                 location = script.locationFromPosition(sourcePosition, true);
    408             if (!location)
    409                 location = { line: 0, column: 0 };
    410         }
    411         return location;
    412     }
    413 
    414     function line()
    415     {
    416         return ensureLocation().line;
    417     }
    418 
    419     function column()
    420     {
    421         return ensureLocation().column;
    422     }
    423 
    424     function sourceID()
    425     {
    426         var script = ensureFuncMirror().script();
    427         return script && script.id();
    428     }
    429 
    430     function scriptName()
    431     {
    432         var script = ensureFuncMirror().script();
    433         return script && script.name();
    434     }
    435 
    436     function functionName()
    437     {
    438         var func = ensureFuncMirror();
    439         if (!func.resolved())
    440             return undefined;
    441         var displayName;
    442         var valueMirror = func.property("displayName").value();
    443         if (valueMirror && valueMirror.isString())
    444             displayName = valueMirror.value();
    445         return displayName || func.name() || func.inferredName();
    446     }
    447 
    448     function evaluate(expression)
    449     {
    450         return frameMirror.evaluate(expression, false).value();
    451     }
    452 
    453     function restart()
    454     {
    455         return Debug.LiveEdit.RestartFrame(frameMirror);
    456     }
    457 
    458     function setVariableValue(scopeNumber, variableName, newValue)
    459     {
    460         return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue);
    461     }
    462 
    463     function stepInPositions()
    464     {
    465         var stepInPositionsV8 = frameMirror.stepInPositions();
    466         var stepInPositionsProtocol;
    467         if (stepInPositionsV8) {
    468             stepInPositionsProtocol = [];
    469             var script = ensureFuncMirror().script();
    470             if (script) {
    471                 var scriptId = String(script.id());
    472                 for (var i = 0; i < stepInPositionsV8.length; i++) {
    473                     var item = {
    474                         scriptId: scriptId,
    475                         lineNumber: stepInPositionsV8[i].position.line,
    476                         columnNumber: stepInPositionsV8[i].position.column
    477                     };
    478                     stepInPositionsProtocol.push(item);
    479                 }
    480             }
    481         }
    482         return JSON.stringify(stepInPositionsProtocol);
    483     }
    484 
    485     return {
    486         "sourceID": sourceID,
    487         "line": line,
    488         "column": column,
    489         "scriptName": scriptName,
    490         "functionName": functionName,
    491         "thisObject": thisObject,
    492         "scopeChain": lazyScopeChain,
    493         "scopeType": scopeTypes,
    494         "evaluate": evaluate,
    495         "caller": callerFrame,
    496         "restart": restart,
    497         "setVariableValue": setVariableValue,
    498         "stepInPositions": stepInPositions,
    499         "isAtReturn": isAtReturn,
    500         "returnValue": returnValue
    501     };
    502 }
    503 
    504 DebuggerScript._buildScopeObject = function(scopeType, scopeObject)
    505 {
    506     var result;
    507     switch (scopeType) {
    508     case ScopeType.Local:
    509     case ScopeType.Closure:
    510     case ScopeType.Catch:
    511         // For transient objects we create a "persistent" copy that contains
    512         // the same properties.
    513         // Reset scope object prototype to null so that the proto properties
    514         // don't appear in the local scope section.
    515         result = { __proto__: null };
    516         var properties = MakeMirror(scopeObject, true /* transient */).properties();
    517         for (var j = 0; j < properties.length; j++) {
    518             var name = properties[j].name();
    519             if (name.charAt(0) === ".")
    520                 continue; // Skip internal variables like ".arguments"
    521             result[name] = properties[j].value_;
    522         }
    523         break;
    524     case ScopeType.Global:
    525     case ScopeType.With:
    526         result = scopeObject;
    527         break;
    528     case ScopeType.Block:
    529         // Unsupported yet. Mustn't be reachable.
    530         break;
    531     }
    532     return result;
    533 }
    534 
    535 DebuggerScript.getPromiseDetails = function(eventData)
    536 {
    537     return {
    538         "promise": eventData.promise().value(),
    539         "parentPromise": eventData.parentPromise().value(),
    540         "status": eventData.status()
    541     };
    542 }
    543 
    544 // We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it.
    545 ToggleMirrorCache(false);
    546 
    547 return DebuggerScript;
    548 })();
    549