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/**
37 * @param {?CompileEvent} eventData
38 */
39DebuggerScript.getAfterCompileScript = function(eventData)
40{
41    var script = eventData.script().value();
42    if (!script.is_debugger_script)
43        return script;
44    return null;
45}
46
47/** @type {!Map<!ScopeType, string>} */
48DebuggerScript._scopeTypeNames = new Map();
49DebuggerScript._scopeTypeNames.set(ScopeType.Global, "global");
50DebuggerScript._scopeTypeNames.set(ScopeType.Local, "local");
51DebuggerScript._scopeTypeNames.set(ScopeType.With, "with");
52DebuggerScript._scopeTypeNames.set(ScopeType.Closure, "closure");
53DebuggerScript._scopeTypeNames.set(ScopeType.Catch, "catch");
54DebuggerScript._scopeTypeNames.set(ScopeType.Block, "block");
55DebuggerScript._scopeTypeNames.set(ScopeType.Script, "script");
56
57/**
58 * @param {function()} fun
59 * @return {?Array<!Scope>}
60 */
61DebuggerScript.getFunctionScopes = function(fun)
62{
63    var mirror = MakeMirror(fun);
64    if (!mirror.isFunction())
65        return null;
66    var functionMirror = /** @type {!FunctionMirror} */(mirror);
67    var count = functionMirror.scopeCount();
68    if (count == 0)
69        return null;
70    var result = [];
71    for (var i = 0; i < count; i++) {
72        var scopeDetails = functionMirror.scope(i).details();
73        var scopeObject = DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object());
74        if (!scopeObject)
75            continue;
76        result.push({
77            type: /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeDetails.type())),
78            object: scopeObject,
79            name: scopeDetails.name() || ""
80        });
81    }
82    return result;
83}
84
85/**
86 * @param {Object} object
87 * @return {?RawLocation}
88 */
89DebuggerScript.getGeneratorObjectLocation = function(object)
90{
91    var mirror = MakeMirror(object, true /* transient */);
92    if (!mirror.isGenerator())
93        return null;
94    var generatorMirror = /** @type {!GeneratorMirror} */(mirror);
95    var funcMirror = generatorMirror.func();
96    if (!funcMirror.resolved())
97        return null;
98    var location = generatorMirror.sourceLocation() || funcMirror.sourceLocation();
99    var script = funcMirror.script();
100    if (script && location) {
101        return {
102            scriptId: "" + script.id(),
103            lineNumber: location.line,
104            columnNumber: location.column
105        };
106    }
107    return null;
108}
109
110/**
111 * @param {Object} object
112 * @return {!Array<!{value: *}>|undefined}
113 */
114DebuggerScript.getCollectionEntries = function(object)
115{
116    var mirror = MakeMirror(object, true /* transient */);
117    if (mirror.isMap())
118        return /** @type {!MapMirror} */(mirror).entries();
119    if (mirror.isSet() || mirror.isIterator()) {
120        var result = [];
121        var values = mirror.isSet() ? /** @type {!SetMirror} */(mirror).values() : /** @type {!IteratorMirror} */(mirror).preview();
122        for (var i = 0; i < values.length; ++i)
123            result.push({ value: values[i] });
124        return result;
125    }
126}
127
128/**
129 * @param {string|undefined} contextData
130 * @return {number}
131 */
132DebuggerScript._executionContextId = function(contextData)
133{
134    if (!contextData)
135        return 0;
136    var match = contextData.match(/^[^,]*,([^,]*),.*$/);
137    if (!match)
138        return 0;
139    return parseInt(match[1], 10) || 0;
140}
141
142/**
143 * @param {!ExecutionState} execState
144 * @param {!BreakpointInfo} info
145 * @return {string|undefined}
146 */
147DebuggerScript.setBreakpoint = function(execState, info)
148{
149    var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, Debug.BreakPositionAlignment.Statement);
150    var locations = Debug.findBreakPointActualLocations(breakId);
151    if (!locations.length)
152        return undefined;
153    info.lineNumber = locations[0].line;
154    info.columnNumber = locations[0].column;
155    return breakId.toString();
156}
157
158/**
159 * @param {!ExecutionState} execState
160 * @param {!{breakpointId: number}} info
161 */
162DebuggerScript.removeBreakpoint = function(execState, info)
163{
164    Debug.findBreakPoint(info.breakpointId, true);
165}
166
167/**
168 * @param {!ExecutionState} execState
169 * @param {number} limit
170 * @return {!Array<!JavaScriptCallFrame>}
171 */
172DebuggerScript.currentCallFrames = function(execState, limit)
173{
174    var frames = [];
175    for (var i = 0; i < execState.frameCount() && (!limit || i < limit); ++i)
176        frames.push(DebuggerScript._frameMirrorToJSCallFrame(execState.frame(i)));
177    return frames;
178}
179
180// Returns array in form:
181//      [ 0, <v8_result_report> ] in case of success
182//   or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based.
183// or throws exception with message.
184/**
185 * @param {number} scriptId
186 * @param {string} newSource
187 * @param {boolean} preview
188 * @return {!Array<*>}
189 */
190DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview)
191{
192    var scripts = Debug.scripts();
193    var scriptToEdit = null;
194    for (var i = 0; i < scripts.length; i++) {
195        if (scripts[i].id == scriptId) {
196            scriptToEdit = scripts[i];
197            break;
198        }
199    }
200    if (!scriptToEdit)
201        throw("Script not found");
202
203    var changeLog = [];
204    try {
205        var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog);
206        return [0, result.stack_modified];
207    } catch (e) {
208        if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
209            var details = /** @type {!LiveEditErrorDetails} */(e.details);
210            if (details.type === "liveedit_compile_error") {
211                var startPosition = details.position.start;
212                return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)];
213            }
214        }
215        throw e;
216    }
217}
218
219/**
220 * @param {!ExecutionState} execState
221 */
222DebuggerScript.clearBreakpoints = function(execState)
223{
224    Debug.clearAllBreakPoints();
225}
226
227/**
228 * @param {!ExecutionState} execState
229 * @param {!{enabled: boolean}} info
230 */
231DebuggerScript.setBreakpointsActivated = function(execState, info)
232{
233    Debug.debuggerFlags().breakPointsActive.setValue(info.enabled);
234}
235
236/**
237 * @param {!BreakEvent} eventData
238 */
239DebuggerScript.getBreakpointNumbers = function(eventData)
240{
241    var breakpoints = eventData.breakPointsHit();
242    var numbers = [];
243    if (!breakpoints)
244        return numbers;
245
246    for (var i = 0; i < breakpoints.length; i++) {
247        var breakpoint = breakpoints[i];
248        var scriptBreakPoint = breakpoint.script_break_point();
249        numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number());
250    }
251    return numbers;
252}
253
254// NOTE: This function is performance critical, as it can be run on every
255// statement that generates an async event (like addEventListener) to support
256// asynchronous call stacks. Thus, when possible, initialize the data lazily.
257/**
258 * @param {!FrameMirror} frameMirror
259 * @return {!JavaScriptCallFrame}
260 */
261DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror)
262{
263    // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id).
264    // The frameMirror and scopeMirror can be accessed only while paused on the debugger.
265    var frameDetails = frameMirror.details();
266
267    var funcObject = frameDetails.func();
268    var scriptObject = frameDetails.script();
269    var sourcePosition = frameDetails.sourcePosition();
270    var thisObject = frameDetails.receiver();
271
272    var isAtReturn = !!frameDetails.isAtReturn();
273    var returnValue = isAtReturn ? frameDetails.returnValue() : undefined;
274
275    var scopeMirrors = frameMirror.allScopes(false);
276    /** @type {!Array<number>} */
277    var scopeTypes = new Array(scopeMirrors.length);
278    /** @type {?Array<!Object>} */
279    var scopeObjects = new Array(scopeMirrors.length);
280    /** @type {!Array<string|undefined>} */
281    var scopeNames = new Array(scopeMirrors.length);
282    /** @type {?Array<number>} */
283    var scopeStartPositions = new Array(scopeMirrors.length);
284    /** @type {?Array<number>} */
285    var scopeEndPositions = new Array(scopeMirrors.length);
286    /** @type {?Array<function()|null>} */
287    var scopeFunctions = new Array(scopeMirrors.length);
288    for (var i = 0; i < scopeMirrors.length; ++i) {
289        var scopeDetails = scopeMirrors[i].details();
290        scopeTypes[i] = scopeDetails.type();
291        scopeObjects[i] = scopeDetails.object();
292        scopeNames[i] = scopeDetails.name();
293        scopeStartPositions[i] = scopeDetails.startPosition ? scopeDetails.startPosition() : 0;
294        scopeEndPositions[i] = scopeDetails.endPosition ? scopeDetails.endPosition() : 0;
295        scopeFunctions[i] = scopeDetails.func ? scopeDetails.func() : null;
296    }
297
298    // Calculated lazily.
299    var scopeChain;
300    var funcMirror;
301    var scriptMirror;
302    var location;
303    /** @type {!Array<?RawLocation>} */
304    var scopeStartLocations;
305    /** @type {!Array<?RawLocation>} */
306    var scopeEndLocations;
307    var details;
308
309    /**
310     * @param {!ScriptMirror|undefined} script
311     * @param {number} pos
312     * @return {?RawLocation}
313     */
314    function createLocation(script, pos)
315    {
316        if (!script)
317            return null;
318
319        var location = script.locationFromPosition(pos, true);
320        return {
321            "lineNumber": location.line,
322            "columnNumber": location.column,
323            "scriptId": String(script.id())
324        }
325    }
326
327    /**
328     * @return {!Array<!Object>}
329     */
330    function ensureScopeChain()
331    {
332        if (!scopeChain) {
333            scopeChain = [];
334            scopeStartLocations = [];
335            scopeEndLocations = [];
336            for (var i = 0, j = 0; i < scopeObjects.length; ++i) {
337                var scopeObject = DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i]);
338                if (scopeObject) {
339                    scopeTypes[j] = scopeTypes[i];
340                    scopeNames[j] = scopeNames[i];
341                    scopeChain[j] = scopeObject;
342
343                    var funcMirror = scopeFunctions ? MakeMirror(scopeFunctions[i]) : null;
344                    if (!funcMirror || !funcMirror.isFunction())
345                        funcMirror = new UnresolvedFunctionMirror(funcObject);
346
347                    var script = /** @type {!FunctionMirror} */(funcMirror).script();
348                    scopeStartLocations[j] = createLocation(script, scopeStartPositions[i]);
349                    scopeEndLocations[j] = createLocation(script, scopeEndPositions[i]);
350                    ++j;
351                }
352            }
353            scopeTypes.length = scopeChain.length;
354            scopeNames.length = scopeChain.length;
355            scopeObjects = null; // Free for GC.
356            scopeFunctions = null;
357            scopeStartPositions = null;
358            scopeEndPositions = null;
359        }
360        return scopeChain;
361    }
362
363    /**
364     * @return {!JavaScriptCallFrameDetails}
365     */
366    function lazyDetails()
367    {
368        if (!details) {
369            var scopeObjects = ensureScopeChain();
370            var script = ensureScriptMirror();
371            /** @type {!Array<Scope>} */
372            var scopes = [];
373            for (var i = 0; i < scopeObjects.length; ++i) {
374                var scope = {
375                    "type": /** @type {string} */(DebuggerScript._scopeTypeNames.get(scopeTypes[i])),
376                    "object": scopeObjects[i],
377                };
378                if (scopeNames[i])
379                    scope.name = scopeNames[i];
380                if (scopeStartLocations[i])
381                    scope.startLocation = /** @type {!RawLocation} */(scopeStartLocations[i]);
382                if (scopeEndLocations[i])
383                    scope.endLocation = /** @type {!RawLocation} */(scopeEndLocations[i]);
384                scopes.push(scope);
385            }
386            details = {
387                "functionName": ensureFuncMirror().debugName(),
388                "location": {
389                    "lineNumber": line(),
390                    "columnNumber": column(),
391                    "scriptId": String(script.id())
392                },
393                "this": thisObject,
394                "scopeChain": scopes
395            };
396            var functionLocation = ensureFuncMirror().sourceLocation();
397            if (functionLocation) {
398                details.functionLocation = {
399                    "lineNumber": functionLocation.line,
400                    "columnNumber": functionLocation.column,
401                    "scriptId": String(script.id())
402                };
403            }
404            if (isAtReturn)
405                details.returnValue = returnValue;
406        }
407        return details;
408    }
409
410    /**
411     * @return {!FunctionMirror}
412     */
413    function ensureFuncMirror()
414    {
415        if (!funcMirror) {
416            funcMirror = MakeMirror(funcObject);
417            if (!funcMirror.isFunction())
418                funcMirror = new UnresolvedFunctionMirror(funcObject);
419        }
420        return /** @type {!FunctionMirror} */(funcMirror);
421    }
422
423    /**
424     * @return {!ScriptMirror}
425     */
426    function ensureScriptMirror()
427    {
428        if (!scriptMirror) {
429            scriptMirror = MakeMirror(scriptObject);
430        }
431        return /** @type {!ScriptMirror} */(scriptMirror);
432    }
433
434    /**
435     * @return {!{line: number, column: number}}
436     */
437    function ensureLocation()
438    {
439        if (!location) {
440            var script = ensureScriptMirror();
441            location = script.locationFromPosition(sourcePosition, true);
442            if (!location)
443                location = { line: 0, column: 0 };
444        }
445        return location;
446    }
447
448    /**
449     * @return {number}
450     */
451    function line()
452    {
453        return ensureLocation().line;
454    }
455
456    /**
457     * @return {number}
458     */
459    function column()
460    {
461        return ensureLocation().column;
462    }
463
464    /**
465     * @return {number}
466     */
467    function contextId()
468    {
469        var mirror = ensureFuncMirror();
470        // Old V8 do not have context() function on these objects
471        if (!mirror.context)
472            return DebuggerScript._executionContextId(mirror.script().value().context_data);
473        var context = mirror.context();
474        if (context)
475            return DebuggerScript._executionContextId(context.data());
476        return 0;
477    }
478
479    /**
480     * @return {number}
481     */
482    function sourceID()
483    {
484        var script = ensureScriptMirror();
485        return script.id();
486    }
487
488    /**
489     * @param {string} expression
490     * @return {*}
491     */
492    function evaluate(expression)
493    {
494        return frameMirror.evaluate(expression, false).value();
495    }
496
497    /** @return {undefined} */
498    function restart()
499    {
500        return frameMirror.restart();
501    }
502
503    /**
504     * @param {number} scopeNumber
505     * @param {string} variableName
506     * @param {*} newValue
507     */
508    function setVariableValue(scopeNumber, variableName, newValue)
509    {
510        var scopeMirror = frameMirror.scope(scopeNumber);
511        if (!scopeMirror)
512            throw new Error("Incorrect scope index");
513        scopeMirror.setVariableValue(variableName, newValue);
514    }
515
516    return {
517        "sourceID": sourceID,
518        "line": line,
519        "column": column,
520        "contextId": contextId,
521        "thisObject": thisObject,
522        "evaluate": evaluate,
523        "restart": restart,
524        "setVariableValue": setVariableValue,
525        "isAtReturn": isAtReturn,
526        "details": lazyDetails
527    };
528}
529
530/**
531 * @param {number} scopeType
532 * @param {!Object} scopeObject
533 * @return {!Object|undefined}
534 */
535DebuggerScript._buildScopeObject = function(scopeType, scopeObject)
536{
537    var result;
538    switch (scopeType) {
539    case ScopeType.Local:
540    case ScopeType.Closure:
541    case ScopeType.Catch:
542    case ScopeType.Block:
543    case ScopeType.Script:
544        // For transient objects we create a "persistent" copy that contains
545        // the same properties.
546        // Reset scope object prototype to null so that the proto properties
547        // don't appear in the local scope section.
548        var properties = /** @type {!ObjectMirror} */(MakeMirror(scopeObject, true /* transient */)).properties();
549        // Almost always Script scope will be empty, so just filter out that noise.
550        // Also drop empty Block scopes, should we get any.
551        if (!properties.length && (scopeType === ScopeType.Script || scopeType === ScopeType.Block))
552            break;
553        result = { __proto__: null };
554        for (var j = 0; j < properties.length; j++) {
555            var name = properties[j].name();
556            if (name.length === 0 || name.charAt(0) === ".")
557                continue; // Skip internal variables like ".arguments" and variables with empty name
558            result[name] = properties[j].value_;
559        }
560        break;
561    case ScopeType.Global:
562    case ScopeType.With:
563        result = scopeObject;
564        break;
565    }
566    return result;
567}
568
569// We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it.
570ToggleMirrorCache(false);
571
572return DebuggerScript;
573})();
574