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); | ||
|  | 		} | ||
|  | 	} | ||
|  | } |