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