Permalink
Browse files

Allow nested trycatchs; lazy build stack-trace

  • Loading branch information...
CrabBot committed Aug 23, 2012
1 parent 6c05543 commit 7a905fba53a8e1714b832b3343d3bcdb0ed5667a
Showing with 401 additions and 272 deletions.
  1. +24 −4 lib/formatStackTrace.js
  2. +32 −32 lib/hook.js
  3. +82 −88 lib/trycatch.js
  4. +42 −28 package.json
  5. +36 −0 test/basic.test.js
  6. +54 −40 test/event-emitter.test.js
  7. +88 −0 test/nested.test.js
  8. +43 −80 test/throw-string.test.js
View
@@ -25,7 +25,12 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module.exports = FormatStackTrace;
-function FormatStackTrace(error, frames) {
+
+var path = require('path'),
+ d = path.join('/'),
+ node_modules = d + 'node_modules' + d;
+
+function FormatStackTrace(error, frames, filter, colors) {
var lines = [];
try {
lines.push(error.toString());
@@ -40,7 +45,7 @@ function FormatStackTrace(error, frames) {
var frame = frames[i];
var line;
try {
- line = FormatSourcePosition(frame);
+ line = FormatSourcePosition(frame, filter);
} catch (e) {
try {
line = "<error: " + e + ">";
@@ -49,12 +54,24 @@ function FormatStackTrace(error, frames) {
line = "<error>";
}
}
- lines.push(" at " + line);
+ if (line !== undefined) {
+ line = " at " + line;
+
+ if (colors) {
+ if (line.indexOf(node_modules) >= 0) {
+ line = colors.cyan(line);
+ } else if (line.indexOf(d) >= 0) {
+ line = colors.red(line);
+ }
+ }
+
+ lines.push(line);
+ }
}
return lines.join("\n");
}
-function FormatSourcePosition(frame) {
+function FormatSourcePosition(frame, filter) {
var fileLocation = "";
if (frame.isNative()) {
fileLocation = "native";
@@ -63,6 +80,9 @@ function FormatSourcePosition(frame) {
} else {
var fileName = frame.getFileName();
if (fileName) {
+ for(var i=0, l=filter.length; i<l; i++) {
+ if (fileName.indexOf(filter[i]) !== -1) return;
+ }
fileLocation += fileName;
var lineNumber = frame.getLineNumber();
if (lineNumber != null) {
View
@@ -3,59 +3,59 @@
* @param {function} wrap The function to return the new callback
*/
module.exports = function hook(wrap) {
- if (alreadyRequired) throw new Error("This should only be required and used once");
- alreadyRequired = true;
+ if (alreadyRequired) throw new Error("This should only be required and used once")
+ alreadyRequired = true
// Wrap setTimeout and setInterval
- ["setTimeout", "setInterval"].forEach(function (name) {
- var original = this[name];
+ ;["setTimeout", "setInterval"].forEach(function (name) {
+ var original = this[name]
this[name] = function (callback) {
- arguments[0] = wrap(callback, name);
- return original.apply(this, arguments);
- };
- });
+ arguments[0] = wrap(callback, name)
+ return original.apply(this, arguments)
+ }
+ })
// Wrap process.nextTick
- var nextTick = process.nextTick;
+ var nextTick = process.nextTick
process.nextTick = function wrappedNextTick(callback) {
- arguments[0] = wrap(callback, 'process.nextTick');
- return nextTick.apply(this, arguments);
- };
+ arguments[0] = wrap(callback, 'process.nextTick')
+ return nextTick.apply(this, arguments)
+ }
// Wrap FS module async functions
- var FS = require('fs');
+ var FS = require('fs')
Object.keys(FS).forEach(function (name) {
// If it has a *Sync counterpart, it's probably async
- if (!FS.hasOwnProperty(name + "Sync")) return;
- var original = FS[name];
+ if (!FS.hasOwnProperty(name + "Sync")) return
+ var original = FS[name]
FS[name] = function () {
- var i = arguments.length - 1;
+ var i = arguments.length - 1
if (typeof arguments[i] === 'function') {
- arguments[i] = wrap(arguments[i], 'fs.'+name);
+ arguments[i] = wrap(arguments[i], 'fs.'+name)
}
- return original.apply(this, arguments);
- };
- });
+ return original.apply(this, arguments)
+ }
+ })
// Wrap EventEmitters
- var EventEmitter = require('events').EventEmitter;
- var onEvent = EventEmitter.prototype.on;
+ var EventEmitter = require('events').EventEmitter
+ var onEvent = EventEmitter.prototype.on
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, callback) {
- var newCallback = wrap(callback, 'EventEmitter.on');
+ var newCallback = wrap(callback, 'EventEmitter.on')
if (newCallback !== callback) {
- callback.wrappedCallback = newCallback;
- arguments[1] = newCallback;
+ callback.wrappedCallback = newCallback
+ arguments[1] = newCallback
}
- return onEvent.apply(this, arguments);
- };
- var removeEvent = EventEmitter.prototype.removeListener;
+ return onEvent.apply(this, arguments)
+ }
+ var removeEvent = EventEmitter.prototype.removeListener
EventEmitter.prototype.removeListener = function (type, callback) {
if (callback && callback.hasOwnProperty("wrappedCallback")) {
- arguments[1] = callback.wrappedCallback;
+ arguments[1] = callback.wrappedCallback
}
- return removeEvent.apply(this, arguments);
- };
+ return removeEvent.apply(this, arguments)
+ }
}
-var alreadyRequired;
+var alreadyRequired
View
@@ -1,120 +1,114 @@
-module.exports = trycatch;
+module.exports = trycatch
+
// use colors module, if available
-try { trycatch.colors = require('colors'); } catch(err) {}
-var FormatStackTrace = require('./formatStackTrace');
-var path = require('path');
-var d = path.join('/');
+try { trycatch.colors = require('colors') } catch(err) {}
+var FormatStackTrace = require('./formatStackTrace'),
+ filename1 = __filename,
+ filename2 = require.resolve('./hook')
+// findToken fails when _TOKEN_ deeper than Error.stackTraceLimit
+Error.stackTraceLimit = Infinity
-// findToken fails for stack traces deeper Error.stackTraceLimit => Error.stackTraceLimit = Infinity
-// Make configurable?
-Error.stackTraceLimit = Infinity;
-// The event-source hooks allows tokens & new stacks to be linked
-// called as shim
-require('./hook')(generateShim);
+// Replace built-in async functions, shim callbacks
+require('./hook')(generateShim)
-// generate a new callback shim for shim'd async function (e.g., fs.stats)
+// Generate a new callback wrapped in _TOKEN_ with Error to trace back
function generateShim(next, name, location) {
- var self;
- var res = findToken();
- if (!res) return next;
- var token = res.token;
- var stack = res.stack;
-
- // _TOKEN_ is the new callback and calls the real callback, next()
+ if (typeof next !== 'function') return next
+
+ // _TOKEN_ is the new callback and calls the real callback, next()
function _TOKEN_() {
try {
- return next.apply(self, arguments);
- } catch (err) {
- if (!(err instanceof Error)) {
- err = new Error(''+err);
- }
- if (err.stack.split('_TOKEN_').length > 2) {
- throw err;
- }
-
- var catchFn;
- token = _TOKEN_;
- err.stack = filterInternalFrames(err.stack);
- while(token.token) {
- if (token.stack) {
- err.stack += '\n ----------------------------------------\n' +
- ' at '+token.orig+'\n' +
- token.stack.substring(token.stack.indexOf("\n") + 1)
- }
- catchFn = token = token.token;
- }
-
- catchFn(err);
+ return next.apply(this, arguments)
+ } catch (e) {
+ handleError(e, _TOKEN_, false)
}
}
- _TOKEN_.orig = name;
- _TOKEN_.stack = stack;
- _TOKEN_.token = token;
+ _TOKEN_.orig = name
+ _TOKEN_.error = new Error
- return function() {
- self = this;
- return _TOKEN_.apply(token, arguments);
- };
+ return _TOKEN_
}
-// Tags a stack and all decendent stacks with a token
+function handleError(err, token, recursive) {
+ var origin
+
+ if (!recursive) {
+ if (!err.token) {
+ // Newly created Error
+ err = err instanceof Error ? err : new Error(''+err)
+ err = getFilteredError(err)
+ err.originalStack = err.stack
+ } else {
+ token = err.token
+ }
+ }
+
+ while(token.error) {
+ // stackSearch returns an object {token, stack} in place of error.stack String
+ origin = getFilteredError(token.error, stackSearch).stack
+ if (!origin) throw err
+
+ if (!token.catchFn && origin.stack) {
+ err.stack += '\n ----------------------------------------\n' +
+ ' at '+token.orig+'\n' +
+ origin.stack.substring(origin.stack.indexOf("\n") + 1)
+ }
+ token = origin.token
+ if (token.catchFn) break
+ }
+
+ if (typeof token.catchFn === 'function') {
+ try {
+ err.token = token
+ token.catchFn.call(null, err, token)
+ } catch(e2) {
+ handleError(e2, token, true)
+ }
+ }
+}
+
+// Create origin _TOKEN_ for stack termination
function trycatch(tryFn, catchFn) {
function _TOKEN_() {
- tryFn();
+ tryFn()
}
- _TOKEN_.token = catchFn;
+ _TOKEN_.catchFn = catchFn
+ _TOKEN_.error = new Error
+ _TOKEN_.orig = 'trycatch'
+
try {
- _TOKEN_();
+ _TOKEN_()
} catch (err) {
- err.stack = filterInternalFrames(err.stack);
- catchFn(err);
+ catchFn(getFilteredError(err))
}
}
-// Looks for a token in the current stack using the V8 stack trace API
-function findToken(err) {
- if (!err) err = new Error();
- var original = Error.prepareStackTrace;
- // stackSearch returns a function object instead of the string expected from the built-in
- Error.prepareStackTrace = stackSearch;
- var res = err.stack;
- Error.prepareStackTrace = original;
- err.stack = res && res.stack;
- return res;
+function getFilteredError(err, fn) {
+ if (typeof fn !== 'function') {
+ fn = function(error, structuredStackTrace) {
+ return FormatStackTrace(error, structuredStackTrace, [filename1, filename2], trycatch.colors)
+ }
+ }
+ if (!err) err = new Error;
+ var old = Error.prepareStackTrace
+ Error.prepareStackTrace = fn
+ err.stack = err.stack
+ Error.prepareStackTrace = old
+ return err
}
function stackSearch(error, structuredStackTrace) {
- if (!structuredStackTrace) return;
+ if (!structuredStackTrace) return
- for (var fn, i = 0, l = structuredStackTrace.length; i < l; i++) {
- fn = structuredStackTrace[i].fun;
+ for (var fn, i=0, l=structuredStackTrace.length; i<l; i++) {
+ fn = structuredStackTrace[i].fun
if (fn.name === '_TOKEN_') {
return {
token: fn,
- stack: filterInternalFrames(FormatStackTrace(error, structuredStackTrace))
+ stack: FormatStackTrace(error, structuredStackTrace, [filename1, filename2], trycatch.colors)
}
}
}
}
-
-var filename1 = __filename;
-var filename2 = require.resolve('./hook');
-function filterInternalFrames(frames) {
- var ret = [];
- ret = frames.split("\n").filter(function(frame) {
- return frame.indexOf(filename1) < 0 && frame.indexOf(filename2) < 0;
- });
- if (trycatch.colors) {
- ret = ret.map(function(frame) {
- if (frame.indexOf(d + 'node_modules' + d) >= 0) {
- frame = trycatch.colors.cyan(frame);
- } else if (frame.indexOf(d) >= 0) {
- frame = trycatch.colors.red(frame);
- }
- return frame;
- });
- }
- return ret.join("\n");
-}
Oops, something went wrong.

0 comments on commit 7a905fb

Please sign in to comment.