NativeStackTrace.hx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package haxe;
  2. import js.Syntax;
  3. import js.lib.Error;
  4. import haxe.CallStack.StackItem;
  5. // https://v8.dev/docs/stack-trace-api
  6. @:native("Error")
  7. private extern class V8Error {
  8. static var prepareStackTrace:(error:Error, structuredStackTrace:Array<V8CallSite>)->Any;
  9. }
  10. typedef V8CallSite = {
  11. function getFunctionName():String;
  12. function getFileName():String;
  13. function getLineNumber():Int;
  14. function getColumnNumber():Int;
  15. }
  16. /**
  17. Do not use manually.
  18. **/
  19. @:dox(hide)
  20. @:noCompletion
  21. @:allow(haxe.Exception)
  22. class NativeStackTrace {
  23. static var lastError:Error;
  24. // support for source-map-support module
  25. @:noCompletion
  26. public static var wrapCallSite:V8CallSite->V8CallSite;
  27. @:ifFeature('haxe.NativeStackTrace.exceptionStack')
  28. static public inline function saveStack(e:Error):Void {
  29. lastError = e;
  30. }
  31. static public function callStack():Any {
  32. var e:Null<Error> = new Error('');
  33. var stack = tryHaxeStack(e);
  34. //Internet Explorer provides call stack only if error was thrown
  35. if(Syntax.typeof(stack) == "undefined") {
  36. try throw e catch(e:Exception) {}
  37. stack = e.stack;
  38. }
  39. return normalize(stack, 2);
  40. }
  41. static public function exceptionStack():Any {
  42. return normalize(tryHaxeStack(lastError));
  43. }
  44. static public function toHaxe(s:Null<Any>, skip:Int = 0):Array<StackItem> {
  45. if (s == null) {
  46. return [];
  47. } else if (Syntax.typeof(s) == "string") {
  48. // Return the raw lines in browsers that don't support prepareStackTrace
  49. var stack:Array<String> = (s:String).split("\n");
  50. if (stack[0] == "Error")
  51. stack.shift();
  52. var m = [];
  53. for (i in 0...stack.length) {
  54. if(skip > i) continue;
  55. var line = stack[i];
  56. var matched:Null<Array<String>> = Syntax.code('{0}.match(/^ at ([A-Za-z0-9_. ]+) \\(([^)]+):([0-9]+):([0-9]+)\\)$/)', line);
  57. if (matched != null) {
  58. var path = matched[1].split(".");
  59. if(path[0] == "$hxClasses") {
  60. path.shift();
  61. }
  62. var meth = path.pop();
  63. var file = matched[2];
  64. var line = Std.parseInt(matched[3]);
  65. var column = Std.parseInt(matched[4]);
  66. m.push(FilePos(meth == "Anonymous function" ? LocalFunction() : meth == "Global code" ? null : Method(path.join("."), meth), file, line,
  67. column));
  68. } else {
  69. m.push(Module(StringTools.trim(line))); // A little weird, but better than nothing
  70. }
  71. }
  72. return m;
  73. } else if(skip > 0 && Syntax.code('Array.isArray({0})', s)) {
  74. return (s:Array<StackItem>).slice(skip);
  75. } else {
  76. return cast s;
  77. }
  78. }
  79. static function tryHaxeStack(e:Null<Error>):Any {
  80. if (e == null) {
  81. return [];
  82. }
  83. // https://v8.dev/docs/stack-trace-api
  84. var oldValue = V8Error.prepareStackTrace;
  85. V8Error.prepareStackTrace = prepareHxStackTrace;
  86. var stack = e.stack;
  87. V8Error.prepareStackTrace = oldValue;
  88. return stack;
  89. }
  90. static function prepareHxStackTrace(e:Error, callsites:Array<V8CallSite>):Any {
  91. var stack = [];
  92. for (site in callsites) {
  93. if (wrapCallSite != null)
  94. site = wrapCallSite(site);
  95. var method = null;
  96. var fullName = site.getFunctionName();
  97. if (fullName != null) {
  98. var idx = fullName.lastIndexOf(".");
  99. if (idx >= 0) {
  100. var className = fullName.substring(0, idx);
  101. var methodName = fullName.substring(idx + 1);
  102. method = Method(className, methodName);
  103. } else {
  104. method = Method(null, fullName);
  105. }
  106. }
  107. var fileName = site.getFileName();
  108. var fileAddr = fileName == null ? -1 : fileName.indexOf("file:");
  109. if (wrapCallSite != null && fileAddr > 0)
  110. fileName = fileName.substring(fileAddr + 6);
  111. stack.push(FilePos(method, fileName, site.getLineNumber(), site.getColumnNumber()));
  112. }
  113. return stack;
  114. }
  115. static function normalize(stack:Any, skipItems:Int = 0):Any {
  116. if(Syntax.code('Array.isArray({0})', stack) && skipItems > 0) {
  117. return (stack:Array<StackItem>).slice(skipItems);
  118. } else if(Syntax.typeof(stack) == "string") {
  119. switch (stack:String).substring(0, 6) {
  120. case 'Error:' | 'Error\n': skipItems += 1;
  121. case _:
  122. }
  123. return skipLines(stack, skipItems);
  124. } else {
  125. //nothing we can do
  126. return stack;
  127. }
  128. }
  129. static function skipLines(stack:String, skip:Int, pos:Int = 0):String {
  130. return if(skip > 0) {
  131. pos = stack.indexOf('\n', pos);
  132. return pos < 0 ? '' : skipLines(stack, --skip, pos + 1);
  133. } else {
  134. return stack.substring(pos);
  135. }
  136. }
  137. }