Skip to content

Instantly share code, notes, and snippets.

@ptomato
Created November 18, 2013 05:39
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ptomato/7523058 to your computer and use it in GitHub Desktop.
Save ptomato/7523058 to your computer and use it in GitHub Desktop.
GJS debugger
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const System = imports.system;
let _breakpoints = 0;
function _getCurrentStack() {
try {
throw new Error();
} catch (e) {
let stack = e.stack.split('\n');
stack.pop(); // remove last newline
stack.shift(); // remove _getCurrentStack's own frame
return stack;
}
return []; // should never be reached
}
function _interpretStackFrame(frame) {
let [location, fileLine] = frame.split('@');
let [file, line] = fileLine.split(':');
if (location === '')
location = 'anonymous function';
return [location, file, line];
}
function _resolveNameInScope(ident, scope) {
let attrChain = ident.split('.');
let attrName = attrChain.pop();
let parentObj = attrChain.reduce(function (prev, curr) {
return prev[curr];
}, scope);
return [parentObj, attrName];
}
// inspired by Python's functools.wraps
// A decorator function has the following properties:
// - isDecorator: true
// - wraps: the inner function (may be another decorator)
// - decoratedFunc: the innermost function, inside of all decorators
// Additionally, a decorator function gives the inner function it wraps a
// "wrapper" property, pointing to the decorator
function _makeDecorator(innerFunc, decoratorFunc, otherProperties) {
decoratorFunc.isDecorator = true;
if (innerFunc.isDecorator) {
innerFunc.wrapper = decoratorFunc;
decoratorFunc.decoratedFunc = innerFunc.decoratedFunc;
} else {
decoratorFunc.decoratedFunc = innerFunc;
}
decoratorFunc.wraps = innerFunc;
// decoratorFunc.name = innerFunc.name; // "name" is read-only :-(
Lang.copyProperties(otherProperties, decoratorFunc);
return decoratorFunc;
}
// Returns whether @func, or any functions that @func decorates if it is a
// decorator, has a "tag" property equal to @tag.
function _isDecoratedWithTag(func, tag) {
for (; func.isDecorator; func = func.wraps) {
if (func.tag === tag)
return true;
}
return false;
}
function _prettyPrint(obj) {
let retval = obj.toString();
if (retval.length > 15)
retval = retval.substr(0, 12) + '...';
if (obj.length !== undefined)
retval += ' (length ' + obj.length + ')';
return retval;
}
// PUBLIC API
/**
* DEBUG:
*
* Defaults to true. Set to false at the beginning of your program to disable
* all debugging commands.
*/
let DEBUG = true;
/**
* traceDecorator:
* @func: The function to decorate
* @funcName: (optional) a string identifying @func.
*
* Decorates a function so that it prints out a message when the function is
* called (mentioning its arguments and its recursion depth) and when it returns
* (mentioning its return value.)
*
* If @funcName is not given, this will attempt to find @func's name through the
* "name" property, and if that fails, it will be called 'anonymous function'.
*
* The trace() function will attach this decorator to an existing function.
*/
function traceDecorator(func, funcName) {
if (!DEBUG || _isDecoratedWithTag(func, 'trace'))
return func;
if (funcName === undefined)
funcName = func.name || 'anonymous function';
return _makeDecorator(func, function () {
let depth = ++arguments.callee.recursionDepth;
let traceString = (funcName +
'(' + Array.map(arguments, _prettyPrint).join(', ') + ')');
if (depth > 1)
traceString += ' [' + depth + ']';
printerr('Entering', traceString);
let retval = func.apply(this, arguments);
printerr('Leaving', traceString, '->', _prettyPrint(retval));
arguments.callee.recursionDepth--;
return retval;
}, {
recursionDepth: 0,
tag: 'trace'
});
}
/**
* timeDecorator:
* @func: The function to decorate
* @funcName: (optional) a string identifying @func.
*
* Decorates a function so that it prints out a message when the function
* returns, saying how long the function took to execute.
*
* If @funcName is not given, this will attempt to find @func's name through the
* "name" property, and if that fails, it will be called 'anonymous function'.
*
* The time() function will attach this decorator to an existing function.
*/
function timeDecorator(func, funcName) {
if (!DEBUG || _isDecoratedWithTag(func, 'time'))
return func;
if (funcName === undefined)
funcName = func.name || 'anonymous function';
return _makeDecorator(func, function () {
let timer = GLib.get_monotonic_time();
let retval = func.apply(this, arguments);
let time = GLib.get_monotonic_time() - timer;
printerr(funcName, 'executed in', time, 'microseconds');
return retval;
}, {
tag: 'time'
});
}
/**
* breakBeforeDecorator:
* @func: The function to decorate
*
* Decorates a function so that it sets a System.breakpoint() before the
* function executes. The breakpoint will abort the program unless run under a
* debugger such as GDB, so be careful.
*
* The breakBefore() function will attach this decorator to an existing
* function.
*/
function breakBeforeDecorator(func) {
if (!DEBUG || _isDecoratedWithTag(func, 'breakBefore'))
return func;
return _makeDecorator(func, function () {
printerr('Breakpoint', arguments.callee.breakpointNum, 'reached');
System.breakpoint();
return func.apply(this, arguments);
}, {
breakpointNum: ++_breakpoints,
tag: 'breakBefore'
});
}
/**
* trace:
* @ident: a string consisting of dotted identifiers that resolves within @scope
* @scope: (optional) an object (the global object if not given)
*
* Put a trace (see traceDecorator()) on a function. Examples:
*
* let myObj = new MyClass();
* Debug.trace('myObj.myMethod');
* Debug.trace('print');
* let myArray = [];
* Debug.trace('push', myArray);
*
* Returns: the decorator function
*/
function trace(ident, scope) {
scope = (scope === undefined)? window : scope;
let [parentObj, attrName] = _resolveNameInScope(ident, scope);
parentObj[attrName] = traceDecorator(parentObj[attrName], attrName);
return parentObj[attrName];
}
/**
* time:
* @ident: a string consisting of dotted identifiers that resolves within @scope
* @scope: (optional) an object (the global object if not given)
*
* Time how long a function takes to execute (see timeDecorator().) See trace()
* for examples of the use of @ident and @scope.
*
* Returns: the decorator function
*/
function time(ident, scope) {
scope = (scope === undefined)? window : scope;
let [parentObj, attrName] = _resolveNameInScope(ident, scope);
parentObj[attrName] = timeDecorator(parentObj[attrName], attrName);
return parentObj[attrName];
}
/**
* breakBefore:
* @ident: a string consisting of dotted identifiers that resolves within @scope
* @scope: (optional) an object (the global object if not given)
*
* Set a breakpoint at the beginning of a function (see breakBeforeDecorator().)
* See trace() for examples of the use of @ident and @scope.
*
* Returns: the decorator function
*/
function breakBefore(ident, scope) {
scope = (scope === undefined)? window : scope;
let [parentObj, attrName] = _resolveNameInScope(ident, scope);
parentObj[attrName] = breakBeforeDecorator(parentObj[attrName]);
printerr('Breakpoint', parentObj[attrName].breakpointNum, 'set on', ident);
return parentObj[attrName];
}
const Lang = imports.lang;
const Debug = imports.debug;
const MyClass = new Lang.Class({
Name: 'MyClass',
_initialValue: 5,
myMethod: function (foo, bar) {
let buffer = [];
Debug.time('map', buffer);
for (let count = 0; count < foo; count++) {
buffer.push(this._initialValue);
}
return buffer.map(function (item) {
return item * bar;
});
},
myRecursiveMethod: function (foo) {
if (foo < 1)
return 1;
return foo * this.myRecursiveMethod(foo - 1);
}
});
let obj = new MyClass();
// Debug.DEBUG = false; // uncomment to turn off debugging
Debug.trace('obj.myRecursiveMethod');
Debug.trace('obj.myMethod');
Debug.time('obj.myMethod');
// Debug.breakBefore('print'); // run under gdb or lldb to see breakpoints
print(obj.myMethod(17355, 57291)[0]);
print(obj.myRecursiveMethod(5));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment