150 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			150 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| 
								 | 
							
								package haxe;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import js.Syntax;
							 | 
						||
| 
								 | 
							
								import js.lib.Error;
							 | 
						||
| 
								 | 
							
								import haxe.CallStack.StackItem;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// https://v8.dev/docs/stack-trace-api
							 | 
						||
| 
								 | 
							
								@:native("Error")
							 | 
						||
| 
								 | 
							
								private extern class V8Error {
							 | 
						||
| 
								 | 
							
									static var prepareStackTrace:(error:Error, structuredStackTrace:Array<V8CallSite>)->Any;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								typedef V8CallSite = {
							 | 
						||
| 
								 | 
							
									function getFunctionName():String;
							 | 
						||
| 
								 | 
							
									function getFileName():String;
							 | 
						||
| 
								 | 
							
									function getLineNumber():Int;
							 | 
						||
| 
								 | 
							
									function getColumnNumber():Int;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
									Do not use manually.
							 | 
						||
| 
								 | 
							
								**/
							 | 
						||
| 
								 | 
							
								@:dox(hide)
							 | 
						||
| 
								 | 
							
								@:noCompletion
							 | 
						||
| 
								 | 
							
								@:allow(haxe.Exception)
							 | 
						||
| 
								 | 
							
								class NativeStackTrace {
							 | 
						||
| 
								 | 
							
									static var lastError:Error;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// support for source-map-support module
							 | 
						||
| 
								 | 
							
									@:noCompletion
							 | 
						||
| 
								 | 
							
									public static var wrapCallSite:V8CallSite->V8CallSite;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									@:ifFeature('haxe.NativeStackTrace.exceptionStack')
							 | 
						||
| 
								 | 
							
									static public inline function saveStack(e:Error):Void {
							 | 
						||
| 
								 | 
							
										lastError = e;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static public function callStack():Any {
							 | 
						||
| 
								 | 
							
										var e:Null<Error> = new Error('');
							 | 
						||
| 
								 | 
							
										var stack = tryHaxeStack(e);
							 | 
						||
| 
								 | 
							
										//Internet Explorer provides call stack only if error was thrown
							 | 
						||
| 
								 | 
							
										if(Syntax.typeof(stack) == "undefined") {
							 | 
						||
| 
								 | 
							
											try throw e catch(e:Exception) {}
							 | 
						||
| 
								 | 
							
											stack = e.stack;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return normalize(stack, 2);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static public function exceptionStack():Any {
							 | 
						||
| 
								 | 
							
										return normalize(tryHaxeStack(lastError));
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static public function toHaxe(s:Null<Any>, skip:Int = 0):Array<StackItem> {
							 | 
						||
| 
								 | 
							
										if (s == null) {
							 | 
						||
| 
								 | 
							
											return [];
							 | 
						||
| 
								 | 
							
										} else if (Syntax.typeof(s) == "string") {
							 | 
						||
| 
								 | 
							
											// Return the raw lines in browsers that don't support prepareStackTrace
							 | 
						||
| 
								 | 
							
											var stack:Array<String> = (s:String).split("\n");
							 | 
						||
| 
								 | 
							
											if (stack[0] == "Error")
							 | 
						||
| 
								 | 
							
												stack.shift();
							 | 
						||
| 
								 | 
							
											var m = [];
							 | 
						||
| 
								 | 
							
											for (i in 0...stack.length) {
							 | 
						||
| 
								 | 
							
												if(skip > i) continue;
							 | 
						||
| 
								 | 
							
												var line = stack[i];
							 | 
						||
| 
								 | 
							
												var matched:Null<Array<String>> = Syntax.code('{0}.match(/^    at ([A-Za-z0-9_. ]+) \\(([^)]+):([0-9]+):([0-9]+)\\)$/)', line);
							 | 
						||
| 
								 | 
							
												if (matched != null) {
							 | 
						||
| 
								 | 
							
													var path = matched[1].split(".");
							 | 
						||
| 
								 | 
							
													if(path[0] == "$hxClasses") {
							 | 
						||
| 
								 | 
							
														path.shift();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													var meth = path.pop();
							 | 
						||
| 
								 | 
							
													var file = matched[2];
							 | 
						||
| 
								 | 
							
													var line = Std.parseInt(matched[3]);
							 | 
						||
| 
								 | 
							
													var column = Std.parseInt(matched[4]);
							 | 
						||
| 
								 | 
							
													m.push(FilePos(meth == "Anonymous function" ? LocalFunction() : meth == "Global code" ? null : Method(path.join("."), meth), file, line,
							 | 
						||
| 
								 | 
							
														column));
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													m.push(Module(StringTools.trim(line))); // A little weird, but better than nothing
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											return m;
							 | 
						||
| 
								 | 
							
										} else if(skip > 0 && Syntax.code('Array.isArray({0})', s)) {
							 | 
						||
| 
								 | 
							
											return (s:Array<StackItem>).slice(skip);
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											return cast s;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function tryHaxeStack(e:Null<Error>):Any {
							 | 
						||
| 
								 | 
							
										if (e == null) {
							 | 
						||
| 
								 | 
							
											return [];
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										// https://v8.dev/docs/stack-trace-api
							 | 
						||
| 
								 | 
							
										var oldValue = V8Error.prepareStackTrace;
							 | 
						||
| 
								 | 
							
										V8Error.prepareStackTrace = prepareHxStackTrace;
							 | 
						||
| 
								 | 
							
										var stack = e.stack;
							 | 
						||
| 
								 | 
							
										V8Error.prepareStackTrace = oldValue;
							 | 
						||
| 
								 | 
							
										return stack;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function prepareHxStackTrace(e:Error, callsites:Array<V8CallSite>):Any {
							 | 
						||
| 
								 | 
							
										var stack = [];
							 | 
						||
| 
								 | 
							
										for (site in callsites) {
							 | 
						||
| 
								 | 
							
											if (wrapCallSite != null)
							 | 
						||
| 
								 | 
							
												site = wrapCallSite(site);
							 | 
						||
| 
								 | 
							
											var method = null;
							 | 
						||
| 
								 | 
							
											var fullName = site.getFunctionName();
							 | 
						||
| 
								 | 
							
											if (fullName != null) {
							 | 
						||
| 
								 | 
							
												var idx = fullName.lastIndexOf(".");
							 | 
						||
| 
								 | 
							
												if (idx >= 0) {
							 | 
						||
| 
								 | 
							
													var className = fullName.substring(0, idx);
							 | 
						||
| 
								 | 
							
													var methodName = fullName.substring(idx + 1);
							 | 
						||
| 
								 | 
							
													method = Method(className, methodName);
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													method = Method(null, fullName);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											var fileName = site.getFileName();
							 | 
						||
| 
								 | 
							
											var fileAddr = fileName == null ? -1 : fileName.indexOf("file:");
							 | 
						||
| 
								 | 
							
											if (wrapCallSite != null && fileAddr > 0)
							 | 
						||
| 
								 | 
							
												fileName = fileName.substring(fileAddr + 6);
							 | 
						||
| 
								 | 
							
											stack.push(FilePos(method, fileName, site.getLineNumber(), site.getColumnNumber()));
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return stack;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function normalize(stack:Any, skipItems:Int = 0):Any {
							 | 
						||
| 
								 | 
							
										if(Syntax.code('Array.isArray({0})', stack) && skipItems > 0) {
							 | 
						||
| 
								 | 
							
											return (stack:Array<StackItem>).slice(skipItems);
							 | 
						||
| 
								 | 
							
										} else if(Syntax.typeof(stack) == "string") {
							 | 
						||
| 
								 | 
							
											switch (stack:String).substring(0, 6) {
							 | 
						||
| 
								 | 
							
												case 'Error:' | 'Error\n': skipItems += 1;
							 | 
						||
| 
								 | 
							
												case _:
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											return skipLines(stack, skipItems);
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											//nothing we can do
							 | 
						||
| 
								 | 
							
											return stack;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function skipLines(stack:String, skip:Int, pos:Int = 0):String {
							 | 
						||
| 
								 | 
							
										return if(skip > 0) {
							 | 
						||
| 
								 | 
							
											pos = stack.indexOf('\n', pos);
							 | 
						||
| 
								 | 
							
											return pos < 0 ? '' : skipLines(stack, --skip, pos + 1);
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											return stack.substring(pos);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |