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
34var DebuggerScript = {};
35
36/** @type {!Map<!ScopeType, string>} */
37DebuggerScript._scopeTypeNames = new Map();
38DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global");
39DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local");
40DebuggerScript._scopeTypeNames.set(ScopeType.With, "with");
41DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure");
42DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch");
43DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block");
44DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script");
45DebuggerScript._scopeTypeNames.set(ScopeType.Eval, "eval");
46DebuggerScript._scopeTypeNames.set(ScopeType.Module, "module");
47
48/**
49 * @param {function()} fun
50 * @return {?Array<!Scope>}
51 */
52DebuggerScript.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 */
80DebuggerScript.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 */
109DebuggerScript.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 */
124DebuggerScript.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 */
134DebuggerScript.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 */
152DebuggerScript.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 */
184DebuggerScript.clearBreakpoints = function(execState)
185{
186    Debug.clearAllBreakPoints();
187}
188
189/**
190 * @param {!Array<!BreakPoint>|undefined} breakpoints
191 */
192DebuggerScript.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 */
213DebuggerScript._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 */
457DebuggerScript._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
497return DebuggerScript;
498})();
499