2 module.exports = function() {
3 var async = require("./async.js");
4 var util = require("./util.js");
5 var bluebirdFramePattern =
6 /[\\\/]bluebird[\\\/]js[\\\/](main|debug|zalgo|instrumented)/;
7 var stackFramePattern = null;
8 var formatStack = null;
9 var indentStackFrames = false;
12 function CapturedTrace(parent) {
13 this._parent = parent;
14 var length = this._length = 1 + (parent === undefined ? 0 : parent._length);
15 captureStackTrace(this, CapturedTrace);
16 if (length > 32) this.uncycle();
18 util.inherits(CapturedTrace, Error);
20 CapturedTrace.prototype.uncycle = function() {
21 var length = this._length;
22 if (length < 2) return;
24 var stackToIndex = {};
26 for (var i = 0, node = this; node !== undefined; ++i) {
30 length = this._length = i;
31 for (var i = length - 1; i >= 0; --i) {
32 var stack = nodes[i].stack;
33 if (stackToIndex[stack] === undefined) {
34 stackToIndex[stack] = i;
37 for (var i = 0; i < length; ++i) {
38 var currentStack = nodes[i].stack;
39 var index = stackToIndex[currentStack];
40 if (index !== undefined && index !== i) {
42 nodes[index - 1]._parent = undefined;
43 nodes[index - 1]._length = 1;
45 nodes[i]._parent = undefined;
47 var cycleEdgeNode = i > 0 ? nodes[i - 1] : this;
49 if (index < length - 1) {
50 cycleEdgeNode._parent = nodes[index + 1];
51 cycleEdgeNode._parent.uncycle();
52 cycleEdgeNode._length =
53 cycleEdgeNode._parent._length + 1;
55 cycleEdgeNode._parent = undefined;
56 cycleEdgeNode._length = 1;
58 var currentChildLength = cycleEdgeNode._length + 1;
59 for (var j = i - 2; j >= 0; --j) {
60 nodes[j]._length = currentChildLength;
68 CapturedTrace.prototype.parent = function() {
72 CapturedTrace.prototype.hasParent = function() {
73 return this._parent !== undefined;
76 CapturedTrace.prototype.attachExtraTrace = function(error) {
77 if (error.__stackCleaned__) return;
79 var parsed = CapturedTrace.parseStackAndMessage(error);
80 var message = parsed.message;
81 var stacks = [parsed.stack];
84 while (trace !== undefined) {
85 stacks.push(cleanStack(trace.stack.split("\n")));
86 trace = trace._parent;
88 removeCommonRoots(stacks);
89 removeDuplicateOrEmptyJumps(stacks);
90 util.notEnumerableProp(error, "stack", reconstructStack(message, stacks));
91 util.notEnumerableProp(error, "__stackCleaned__", true);
94 function reconstructStack(message, stacks) {
95 for (var i = 0; i < stacks.length - 1; ++i) {
96 stacks[i].push("From previous event:");
97 stacks[i] = stacks[i].join("\n");
99 if (i < stacks.length) {
100 stacks[i] = stacks[i].join("\n");
102 return message + "\n" + stacks.join("\n");
105 function removeDuplicateOrEmptyJumps(stacks) {
106 for (var i = 0; i < stacks.length; ++i) {
107 if (stacks[i].length === 0 ||
108 ((i + 1 < stacks.length) && stacks[i][0] === stacks[i+1][0])) {
115 function removeCommonRoots(stacks) {
116 var current = stacks[0];
117 for (var i = 1; i < stacks.length; ++i) {
118 var prev = stacks[i];
119 var currentLastIndex = current.length - 1;
120 var currentLastLine = current[currentLastIndex];
121 var commonRootMeetPoint = -1;
123 for (var j = prev.length - 1; j >= 0; --j) {
124 if (prev[j] === currentLastLine) {
125 commonRootMeetPoint = j;
130 for (var j = commonRootMeetPoint; j >= 0; --j) {
132 if (current[currentLastIndex] === line) {
143 function cleanStack(stack) {
145 for (var i = 0; i < stack.length; ++i) {
147 var isTraceLine = stackFramePattern.test(line) ||
148 " (No stack trace)" === line;
149 var isInternalFrame = isTraceLine && shouldIgnore(line);
150 if (isTraceLine && !isInternalFrame) {
151 if (indentStackFrames && line.charAt(0) !== " ") {
160 function stackFramesAsArray(error) {
161 var stack = error.stack.replace(/\s+$/g, "").split("\n");
162 for (var i = 0; i < stack.length; ++i) {
164 if (" (No stack trace)" === line || stackFramePattern.test(line)) {
169 stack = stack.slice(i);
174 CapturedTrace.parseStackAndMessage = function(error) {
175 var stack = error.stack;
176 var message = error.toString();
177 stack = typeof stack === "string" && stack.length > 0
178 ? stackFramesAsArray(error) : [" (No stack trace)"];
181 stack: cleanStack(stack)
185 CapturedTrace.formatAndLogError = function(error, title) {
186 if (typeof console !== "undefined") {
188 if (typeof error === "object" || typeof error === "function") {
189 var stack = error.stack;
190 message = title + formatStack(stack, error);
192 message = title + String(error);
194 if (typeof warn === "function") {
196 } else if (typeof console.log === "function" ||
197 typeof console.log === "object") {
198 console.log(message);
203 CapturedTrace.unhandledRejection = function (reason) {
204 CapturedTrace.formatAndLogError(reason, "^--- With additional stack trace: ");
207 CapturedTrace.isSupported = function () {
208 return typeof captureStackTrace === "function";
211 CapturedTrace.fireRejectionEvent =
212 function(name, localHandler, reason, promise) {
213 var localEventFired = false;
215 if (typeof localHandler === "function") {
216 localEventFired = true;
217 if (name === "rejectionHandled") {
218 localHandler(promise);
220 localHandler(reason, promise);
227 var globalEventFired = false;
229 globalEventFired = fireGlobalEvent(name, reason, promise);
231 globalEventFired = true;
235 var domEventFired = false;
238 domEventFired = fireDomEvent(name.toLowerCase(), {
243 domEventFired = true;
248 if (!globalEventFired && !localEventFired && !domEventFired &&
249 name === "unhandledRejection") {
250 CapturedTrace.formatAndLogError(reason, "Unhandled rejection ");
254 function formatNonError(obj) {
256 if (typeof obj === "function") {
258 (obj.name || "anonymous") +
261 str = obj.toString();
262 var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
263 if (ruselessToString.test(str)) {
265 var newStr = JSON.stringify(obj);
272 if (str.length === 0) {
273 str = "(empty array)";
276 return ("(<" + snip(str) + ">, no stack trace)");
281 if (str.length < maxChars) {
284 return str.substr(0, maxChars - 3) + "...";
287 var shouldIgnore = function() { return false; };
288 var parseLineInfoRegex = /[\/<\(]([^:\/]+):(\d+):(?:\d+)\)?\s*$/;
289 function parseLineInfo(line) {
290 var matches = line.match(parseLineInfoRegex);
293 fileName: matches[1],
294 line: parseInt(matches[2], 10)
298 CapturedTrace.setBounds = function(firstLineError, lastLineError) {
299 if (!CapturedTrace.isSupported()) return;
300 var firstStackLines = firstLineError.stack.split("\n");
301 var lastStackLines = lastLineError.stack.split("\n");
306 for (var i = 0; i < firstStackLines.length; ++i) {
307 var result = parseLineInfo(firstStackLines[i]);
309 firstFileName = result.fileName;
310 firstIndex = result.line;
314 for (var i = 0; i < lastStackLines.length; ++i) {
315 var result = parseLineInfo(lastStackLines[i]);
317 lastFileName = result.fileName;
318 lastIndex = result.line;
322 if (firstIndex < 0 || lastIndex < 0 || !firstFileName || !lastFileName ||
323 firstFileName !== lastFileName || firstIndex >= lastIndex) {
327 shouldIgnore = function(line) {
328 if (bluebirdFramePattern.test(line)) return true;
329 var info = parseLineInfo(line);
331 if (info.fileName === firstFileName &&
332 (firstIndex <= info.line && info.line <= lastIndex)) {
340 var captureStackTrace = (function stackDetection() {
341 var v8stackFramePattern = /^\s*at\s*/;
342 var v8stackFormatter = function(stack, error) {
343 if (typeof stack === "string") return stack;
345 if (error.name !== undefined &&
346 error.message !== undefined) {
347 return error.toString();
349 return formatNonError(error);
352 if (typeof Error.stackTraceLimit === "number" &&
353 typeof Error.captureStackTrace === "function") {
354 Error.stackTraceLimit = Error.stackTraceLimit + 6;
355 stackFramePattern = v8stackFramePattern;
356 formatStack = v8stackFormatter;
357 var captureStackTrace = Error.captureStackTrace;
359 shouldIgnore = function(line) {
360 return bluebirdFramePattern.test(line);
362 return function(receiver, ignoreUntil) {
363 Error.stackTraceLimit = Error.stackTraceLimit + 6;
364 captureStackTrace(receiver, ignoreUntil);
365 Error.stackTraceLimit = Error.stackTraceLimit - 6;
368 var err = new Error();
370 if (typeof err.stack === "string" &&
371 err.stack.split("\n")[0].indexOf("stackDetection@") >= 0) {
372 stackFramePattern = /@/;
373 formatStack = v8stackFormatter;
374 indentStackFrames = true;
375 return function captureStackTrace(o) {
376 o.stack = new Error().stack;
380 var hasStackAfterThrow;
381 try { throw new Error(); }
383 hasStackAfterThrow = ("stack" in e);
385 if (!("stack" in err) && hasStackAfterThrow &&
386 typeof Error.stackTraceLimit === "number") {
387 stackFramePattern = v8stackFramePattern;
388 formatStack = v8stackFormatter;
389 return function captureStackTrace(o) {
390 Error.stackTraceLimit = Error.stackTraceLimit + 6;
391 try { throw new Error(); }
392 catch(e) { o.stack = e.stack; }
393 Error.stackTraceLimit = Error.stackTraceLimit - 6;
397 formatStack = function(stack, error) {
398 if (typeof stack === "string") return stack;
400 if ((typeof error === "object" ||
401 typeof error === "function") &&
402 error.name !== undefined &&
403 error.message !== undefined) {
404 return error.toString();
406 return formatNonError(error);
414 var fireGlobalEvent = (function() {
416 return function(name, reason, promise) {
417 if (name === "rejectionHandled") {
418 return process.emit(name, promise);
420 return process.emit(name, reason, promise);
424 var customEventWorks = false;
425 var anyEventWorks = true;
427 var ev = new self.CustomEvent("test");
428 customEventWorks = ev instanceof CustomEvent;
430 if (!customEventWorks) {
432 var event = document.createEvent("CustomEvent");
433 event.initCustomEvent("testingtheevent", false, true, {});
434 self.dispatchEvent(event);
436 anyEventWorks = false;
440 fireDomEvent = function(type, detail) {
442 if (customEventWorks) {
443 event = new self.CustomEvent(type, {
448 } else if (self.dispatchEvent) {
449 event = document.createEvent("CustomEvent");
450 event.initCustomEvent(type, false, true, detail);
453 return event ? !self.dispatchEvent(event) : false;
457 var toWindowMethodNameMap = {};
458 toWindowMethodNameMap["unhandledRejection"] = ("on" +
459 "unhandledRejection").toLowerCase();
460 toWindowMethodNameMap["rejectionHandled"] = ("on" +
461 "rejectionHandled").toLowerCase();
463 return function(name, reason, promise) {
464 var methodName = toWindowMethodNameMap[name];
465 var method = self[methodName];
466 if (!method) return false;
467 if (name === "rejectionHandled") {
468 method.call(self, promise);
470 method.call(self, reason, promise);
477 if (typeof console !== "undefined" && typeof console.warn !== "undefined") {
478 warn = function (message) {
479 console.warn(message);
481 if (util.isNode && process.stderr.isTTY) {
482 warn = function(message) {
483 process.stderr.write("\u001b[31m" + message + "\u001b[39m\n");
485 } else if (!util.isNode && typeof (new Error().stack) === "string") {
486 warn = function(message) {
487 console.warn("%c" + message, "color: red");
492 return CapturedTrace;