captured_trace.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. "use strict";
  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;
  10. var warn;
  11. function CapturedTrace(parent) {
  12. this._parent = parent;
  13. var length = this._length = 1 + (parent === undefined ? 0 : parent._length);
  14. captureStackTrace(this, CapturedTrace);
  15. if (length > 32) this.uncycle();
  16. }
  17. util.inherits(CapturedTrace, Error);
  18. CapturedTrace.prototype.uncycle = function() {
  19. var length = this._length;
  20. if (length < 2) return;
  21. var nodes = [];
  22. var stackToIndex = {};
  23. for (var i = 0, node = this; node !== undefined; ++i) {
  24. nodes.push(node);
  25. node = node._parent;
  26. }
  27. length = this._length = i;
  28. for (var i = length - 1; i >= 0; --i) {
  29. var stack = nodes[i].stack;
  30. if (stackToIndex[stack] === undefined) {
  31. stackToIndex[stack] = i;
  32. }
  33. }
  34. for (var i = 0; i < length; ++i) {
  35. var currentStack = nodes[i].stack;
  36. var index = stackToIndex[currentStack];
  37. if (index !== undefined && index !== i) {
  38. if (index > 0) {
  39. nodes[index - 1]._parent = undefined;
  40. nodes[index - 1]._length = 1;
  41. }
  42. nodes[i]._parent = undefined;
  43. nodes[i]._length = 1;
  44. var cycleEdgeNode = i > 0 ? nodes[i - 1] : this;
  45. if (index < length - 1) {
  46. cycleEdgeNode._parent = nodes[index + 1];
  47. cycleEdgeNode._parent.uncycle();
  48. cycleEdgeNode._length =
  49. cycleEdgeNode._parent._length + 1;
  50. } else {
  51. cycleEdgeNode._parent = undefined;
  52. cycleEdgeNode._length = 1;
  53. }
  54. var currentChildLength = cycleEdgeNode._length + 1;
  55. for (var j = i - 2; j >= 0; --j) {
  56. nodes[j]._length = currentChildLength;
  57. currentChildLength++;
  58. }
  59. return;
  60. }
  61. }
  62. };
  63. CapturedTrace.prototype.parent = function() {
  64. return this._parent;
  65. };
  66. CapturedTrace.prototype.hasParent = function() {
  67. return this._parent !== undefined;
  68. };
  69. CapturedTrace.prototype.attachExtraTrace = function(error) {
  70. if (error.__stackCleaned__) return;
  71. this.uncycle();
  72. var parsed = CapturedTrace.parseStackAndMessage(error);
  73. var message = parsed.message;
  74. var stacks = [parsed.stack];
  75. var trace = this;
  76. while (trace !== undefined) {
  77. stacks.push(cleanStack(trace.stack.split("\n")));
  78. trace = trace._parent;
  79. }
  80. removeCommonRoots(stacks);
  81. removeDuplicateOrEmptyJumps(stacks);
  82. util.notEnumerableProp(error, "stack", reconstructStack(message, stacks));
  83. util.notEnumerableProp(error, "__stackCleaned__", true);
  84. };
  85. function reconstructStack(message, stacks) {
  86. for (var i = 0; i < stacks.length - 1; ++i) {
  87. stacks[i].push("From previous event:");
  88. stacks[i] = stacks[i].join("\n");
  89. }
  90. if (i < stacks.length) {
  91. stacks[i] = stacks[i].join("\n");
  92. }
  93. return message + "\n" + stacks.join("\n");
  94. }
  95. function removeDuplicateOrEmptyJumps(stacks) {
  96. for (var i = 0; i < stacks.length; ++i) {
  97. if (stacks[i].length === 0 ||
  98. ((i + 1 < stacks.length) && stacks[i][0] === stacks[i+1][0])) {
  99. stacks.splice(i, 1);
  100. i--;
  101. }
  102. }
  103. }
  104. function removeCommonRoots(stacks) {
  105. var current = stacks[0];
  106. for (var i = 1; i < stacks.length; ++i) {
  107. var prev = stacks[i];
  108. var currentLastIndex = current.length - 1;
  109. var currentLastLine = current[currentLastIndex];
  110. var commonRootMeetPoint = -1;
  111. for (var j = prev.length - 1; j >= 0; --j) {
  112. if (prev[j] === currentLastLine) {
  113. commonRootMeetPoint = j;
  114. break;
  115. }
  116. }
  117. for (var j = commonRootMeetPoint; j >= 0; --j) {
  118. var line = prev[j];
  119. if (current[currentLastIndex] === line) {
  120. current.pop();
  121. currentLastIndex--;
  122. } else {
  123. break;
  124. }
  125. }
  126. current = prev;
  127. }
  128. }
  129. function cleanStack(stack) {
  130. var ret = [];
  131. for (var i = 0; i < stack.length; ++i) {
  132. var line = stack[i];
  133. var isTraceLine = stackFramePattern.test(line) ||
  134. " (No stack trace)" === line;
  135. var isInternalFrame = isTraceLine && shouldIgnore(line);
  136. if (isTraceLine && !isInternalFrame) {
  137. if (indentStackFrames && line.charAt(0) !== " ") {
  138. line = " " + line;
  139. }
  140. ret.push(line);
  141. }
  142. }
  143. return ret;
  144. }
  145. function stackFramesAsArray(error) {
  146. var stack = error.stack.replace(/\s+$/g, "").split("\n");
  147. for (var i = 0; i < stack.length; ++i) {
  148. var line = stack[i];
  149. if (" (No stack trace)" === line || stackFramePattern.test(line)) {
  150. break;
  151. }
  152. }
  153. if (i > 0) {
  154. stack = stack.slice(i);
  155. }
  156. return stack;
  157. }
  158. CapturedTrace.parseStackAndMessage = function(error) {
  159. var stack = error.stack;
  160. var message = error.toString();
  161. stack = typeof stack === "string" && stack.length > 0
  162. ? stackFramesAsArray(error) : [" (No stack trace)"];
  163. return {
  164. message: message,
  165. stack: cleanStack(stack)
  166. };
  167. };
  168. CapturedTrace.formatAndLogError = function(error, title) {
  169. if (typeof console !== "undefined") {
  170. var message;
  171. if (typeof error === "object" || typeof error === "function") {
  172. var stack = error.stack;
  173. message = title + formatStack(stack, error);
  174. } else {
  175. message = title + String(error);
  176. }
  177. if (typeof warn === "function") {
  178. warn(message);
  179. } else if (typeof console.log === "function" ||
  180. typeof console.log === "object") {
  181. console.log(message);
  182. }
  183. }
  184. };
  185. CapturedTrace.unhandledRejection = function (reason) {
  186. CapturedTrace.formatAndLogError(reason, "^--- With additional stack trace: ");
  187. };
  188. CapturedTrace.isSupported = function () {
  189. return typeof captureStackTrace === "function";
  190. };
  191. CapturedTrace.fireRejectionEvent =
  192. function(name, localHandler, reason, promise) {
  193. var localEventFired = false;
  194. try {
  195. if (typeof localHandler === "function") {
  196. localEventFired = true;
  197. if (name === "rejectionHandled") {
  198. localHandler(promise);
  199. } else {
  200. localHandler(reason, promise);
  201. }
  202. }
  203. } catch (e) {
  204. async.throwLater(e);
  205. }
  206. var globalEventFired = false;
  207. try {
  208. globalEventFired = fireGlobalEvent(name, reason, promise);
  209. } catch (e) {
  210. globalEventFired = true;
  211. async.throwLater(e);
  212. }
  213. var domEventFired = false;
  214. if (fireDomEvent) {
  215. try {
  216. domEventFired = fireDomEvent(name.toLowerCase(), {
  217. reason: reason,
  218. promise: promise
  219. });
  220. } catch (e) {
  221. domEventFired = true;
  222. async.throwLater(e);
  223. }
  224. }
  225. if (!globalEventFired && !localEventFired && !domEventFired &&
  226. name === "unhandledRejection") {
  227. CapturedTrace.formatAndLogError(reason, "Unhandled rejection ");
  228. }
  229. };
  230. function formatNonError(obj) {
  231. var str;
  232. if (typeof obj === "function") {
  233. str = "[function " +
  234. (obj.name || "anonymous") +
  235. "]";
  236. } else {
  237. str = obj.toString();
  238. var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
  239. if (ruselessToString.test(str)) {
  240. try {
  241. var newStr = JSON.stringify(obj);
  242. str = newStr;
  243. }
  244. catch(e) {
  245. }
  246. }
  247. if (str.length === 0) {
  248. str = "(empty array)";
  249. }
  250. }
  251. return ("(<" + snip(str) + ">, no stack trace)");
  252. }
  253. function snip(str) {
  254. var maxChars = 41;
  255. if (str.length < maxChars) {
  256. return str;
  257. }
  258. return str.substr(0, maxChars - 3) + "...";
  259. }
  260. var shouldIgnore = function() { return false; };
  261. var parseLineInfoRegex = /[\/<\(]([^:\/]+):(\d+):(?:\d+)\)?\s*$/;
  262. function parseLineInfo(line) {
  263. var matches = line.match(parseLineInfoRegex);
  264. if (matches) {
  265. return {
  266. fileName: matches[1],
  267. line: parseInt(matches[2], 10)
  268. };
  269. }
  270. }
  271. CapturedTrace.setBounds = function(firstLineError, lastLineError) {
  272. if (!CapturedTrace.isSupported()) return;
  273. var firstStackLines = firstLineError.stack.split("\n");
  274. var lastStackLines = lastLineError.stack.split("\n");
  275. var firstIndex = -1;
  276. var lastIndex = -1;
  277. var firstFileName;
  278. var lastFileName;
  279. for (var i = 0; i < firstStackLines.length; ++i) {
  280. var result = parseLineInfo(firstStackLines[i]);
  281. if (result) {
  282. firstFileName = result.fileName;
  283. firstIndex = result.line;
  284. break;
  285. }
  286. }
  287. for (var i = 0; i < lastStackLines.length; ++i) {
  288. var result = parseLineInfo(lastStackLines[i]);
  289. if (result) {
  290. lastFileName = result.fileName;
  291. lastIndex = result.line;
  292. break;
  293. }
  294. }
  295. if (firstIndex < 0 || lastIndex < 0 || !firstFileName || !lastFileName ||
  296. firstFileName !== lastFileName || firstIndex >= lastIndex) {
  297. return;
  298. }
  299. shouldIgnore = function(line) {
  300. if (bluebirdFramePattern.test(line)) return true;
  301. var info = parseLineInfo(line);
  302. if (info) {
  303. if (info.fileName === firstFileName &&
  304. (firstIndex <= info.line && info.line <= lastIndex)) {
  305. return true;
  306. }
  307. }
  308. return false;
  309. };
  310. };
  311. var captureStackTrace = (function stackDetection() {
  312. var v8stackFramePattern = /^\s*at\s*/;
  313. var v8stackFormatter = function(stack, error) {
  314. if (typeof stack === "string") return stack;
  315. if (error.name !== undefined &&
  316. error.message !== undefined) {
  317. return error.toString();
  318. }
  319. return formatNonError(error);
  320. };
  321. if (typeof Error.stackTraceLimit === "number" &&
  322. typeof Error.captureStackTrace === "function") {
  323. Error.stackTraceLimit = Error.stackTraceLimit + 6;
  324. stackFramePattern = v8stackFramePattern;
  325. formatStack = v8stackFormatter;
  326. var captureStackTrace = Error.captureStackTrace;
  327. shouldIgnore = function(line) {
  328. return bluebirdFramePattern.test(line);
  329. };
  330. return function(receiver, ignoreUntil) {
  331. Error.stackTraceLimit = Error.stackTraceLimit + 6;
  332. captureStackTrace(receiver, ignoreUntil);
  333. Error.stackTraceLimit = Error.stackTraceLimit - 6;
  334. };
  335. }
  336. var err = new Error();
  337. if (typeof err.stack === "string" &&
  338. err.stack.split("\n")[0].indexOf("stackDetection@") >= 0) {
  339. stackFramePattern = /@/;
  340. formatStack = v8stackFormatter;
  341. indentStackFrames = true;
  342. return function captureStackTrace(o) {
  343. o.stack = new Error().stack;
  344. };
  345. }
  346. var hasStackAfterThrow;
  347. try { throw new Error(); }
  348. catch(e) {
  349. hasStackAfterThrow = ("stack" in e);
  350. }
  351. if (!("stack" in err) && hasStackAfterThrow) {
  352. stackFramePattern = v8stackFramePattern;
  353. formatStack = v8stackFormatter;
  354. return function captureStackTrace(o) {
  355. Error.stackTraceLimit = Error.stackTraceLimit + 6;
  356. try { throw new Error(); }
  357. catch(e) { o.stack = e.stack; }
  358. Error.stackTraceLimit = Error.stackTraceLimit - 6;
  359. };
  360. }
  361. formatStack = function(stack, error) {
  362. if (typeof stack === "string") return stack;
  363. if ((typeof error === "object" ||
  364. typeof error === "function") &&
  365. error.name !== undefined &&
  366. error.message !== undefined) {
  367. return error.toString();
  368. }
  369. return formatNonError(error);
  370. };
  371. return null;
  372. })([]);
  373. var fireDomEvent;
  374. var fireGlobalEvent = (function() {
  375. if (util.isNode) {
  376. return function(name, reason, promise) {
  377. if (name === "rejectionHandled") {
  378. return process.emit(name, promise);
  379. } else {
  380. return process.emit(name, reason, promise);
  381. }
  382. };
  383. } else {
  384. var customEventWorks = false;
  385. var anyEventWorks = true;
  386. try {
  387. var ev = new self.CustomEvent("test");
  388. customEventWorks = ev instanceof CustomEvent;
  389. } catch (e) {}
  390. if (!customEventWorks) {
  391. try {
  392. var event = document.createEvent("CustomEvent");
  393. event.initCustomEvent("testingtheevent", false, true, {});
  394. self.dispatchEvent(event);
  395. } catch (e) {
  396. anyEventWorks = false;
  397. }
  398. }
  399. if (anyEventWorks) {
  400. fireDomEvent = function(type, detail) {
  401. var event;
  402. if (customEventWorks) {
  403. event = new self.CustomEvent(type, {
  404. detail: detail,
  405. bubbles: false,
  406. cancelable: true
  407. });
  408. } else if (self.dispatchEvent) {
  409. event = document.createEvent("CustomEvent");
  410. event.initCustomEvent(type, false, true, detail);
  411. }
  412. return event ? !self.dispatchEvent(event) : false;
  413. };
  414. }
  415. var toWindowMethodNameMap = {};
  416. toWindowMethodNameMap["unhandledRejection"] = ("on" +
  417. "unhandledRejection").toLowerCase();
  418. toWindowMethodNameMap["rejectionHandled"] = ("on" +
  419. "rejectionHandled").toLowerCase();
  420. return function(name, reason, promise) {
  421. var methodName = toWindowMethodNameMap[name];
  422. var method = self[methodName];
  423. if (!method) return false;
  424. if (name === "rejectionHandled") {
  425. method.call(self, promise);
  426. } else {
  427. method.call(self, reason, promise);
  428. }
  429. return true;
  430. };
  431. }
  432. })();
  433. if (typeof console !== "undefined" && typeof console.warn !== "undefined") {
  434. warn = function (message) {
  435. console.warn(message);
  436. };
  437. if (util.isNode && process.stderr.isTTY) {
  438. warn = function(message) {
  439. process.stderr.write("\u001b[31m" + message + "\u001b[39m\n");
  440. };
  441. } else if (!util.isNode && typeof (new Error().stack) === "string") {
  442. warn = function(message) {
  443. console.warn("%c" + message, "color: red");
  444. };
  445. }
  446. }
  447. return CapturedTrace;
  448. };