Browse Source

Merge pull request #3838 from nadako/js_exception_stack

haxe.CallStack.exceptionStack for js
Dan Korostelev 10 years ago
parent
commit
f0488acd69
6 changed files with 94 additions and 27 deletions
  1. 23 0
      filters.ml
  2. 11 0
      genjs.ml
  3. 42 25
      std/haxe/CallStack.hx
  4. 11 0
      std/js/Boot.hx
  5. 2 2
      tests/optimization/src/TestJs.hx
  6. 5 0
      tests/unit/src/unitstd/haxe/CallStack.unit.hx

+ 23 - 0
filters.ml

@@ -139,6 +139,27 @@ let rec add_final_return e =
 			{ e with eexpr = TFunction f }
 		| _ -> e
 
+let rec wrap_js_exceptions com e =
+	let terr = List.find (fun mt -> match mt with TClassDecl {cl_path = ["js";"_Boot"],"HaxeError"} -> true | _ -> false) com.types in
+	let cerr = match terr with TClassDecl c -> c | _ -> assert false in
+
+	let rec is_error = function
+	| TInst ({cl_path = (["js"],"Error")},_) -> true
+	| TInst ({cl_super = Some (csup,tl)}, _) -> is_error (TInst (csup,tl))
+	| _ -> false
+	in
+
+	let rec loop e =
+		match e.eexpr with
+		| TThrow eerr when not (is_error eerr.etype) ->
+			let ewrap = { eerr with eexpr = TNew (cerr,[],[eerr]) } in
+			{ e with eexpr = TThrow ewrap }
+		| _ ->
+			Type.map_expr loop e
+	in
+
+	loop e
+
 (* -------------------------------------------------------------------------- *)
 (* CHECK LOCAL VARS INIT *)
 
@@ -1088,6 +1109,7 @@ let run com tctx main =
 		let filters = [
 			Optimizer.sanitize com;
 			if com.config.pf_add_final_return then add_final_return else (fun e -> e);
+			if com.platform = Js then wrap_js_exceptions com else (fun e -> e);
 			rename_local_vars tctx;
 		] in
 		List.iter (run_expression_filters tctx filters) new_types;
@@ -1113,6 +1135,7 @@ let run com tctx main =
 			captured_vars com;
 			promote_complex_rhs com;
 			if com.config.pf_add_final_return then add_final_return else (fun e -> e);
+			if com.platform = Js then wrap_js_exceptions com else (fun e -> e);
 			rename_local_vars tctx;
 		] in
 		List.iter (run_expression_filters tctx filters) new_types;

+ 11 - 0
genjs.ml

@@ -663,6 +663,17 @@ and gen_expr ctx e =
 		let bend = open_block ctx in
 		let last = ref false in
 		let else_block = ref false in
+
+		if (has_feature ctx "haxe.CallStack.exceptionStack") then begin
+			newline ctx;
+			print ctx "%s.lastException = %s" (ctx.type_accessor (TClassDecl { null_class with cl_path = ["haxe"],"CallStack" })) vname
+		end;
+
+		if (has_feature ctx "js.Boot.HaxeError") then begin
+			newline ctx;
+			print ctx "if (%s instanceof %s) %s = %s.val" vname (ctx.type_accessor (TClassDecl { null_class with cl_path = ["js";"_Boot"],"HaxeError" })) vname vname;
+		end;
+
 		List.iter (fun (v,e) ->
 			if !last then () else
 			let t = (match follow v.v_type with

+ 42 - 25
std/haxe/CallStack.hx

@@ -36,6 +36,38 @@ enum StackItem {
 	Get informations about the call stack.
 **/
 class CallStack {
+	#if js
+	static var lastException:js.Error;
+
+	static function getStack(e:js.Error):Array<StackItem> {
+		// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+		var oldValue = (untyped Error).prepareStackTrace;
+		(untyped Error).prepareStackTrace = function (error, callsites :Array<Dynamic>) {
+			var stack = [];
+			for (site in callsites) {
+				if (wrapCallSite != null) site = wrapCallSite(site);
+				var method = null;
+				var fullName :String = site.getFunctionName();
+				if (fullName != null) {
+					var idx = fullName.lastIndexOf(".");
+					if (idx >= 0) {
+						var className = fullName.substr(0, idx);
+						var methodName = fullName.substr(idx+1);
+						method = Method(className, methodName);
+					}
+				}
+				stack.push(FilePos(method, site.getFileName(), site.getLineNumber()));
+			}
+			return stack;
+		}
+		var a = makeStack(e.stack);
+		(untyped Error).prepareStackTrace = oldValue;
+		return a;
+	}
+
+	// support for source-map-support module
+	public static var wrapCallSite:Dynamic->Dynamic;
+	#end
 
 	/**
 		Return the call stack elements, or an empty array if not available.
@@ -55,33 +87,14 @@ class CallStack {
 			var s:Array<String> = untyped __global__.__hxcpp_get_call_stack(true);
 			return makeStack(s);
 		#elseif js
-			// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
-			var oldValue = (untyped Error).prepareStackTrace;
-			(untyped Error).prepareStackTrace = function (error, callsites :Array<Dynamic>) {
-				var stack = [];
-				for (site in callsites) {
-					var method = null;
-					var fullName :String = site.getFunctionName();
-					if (fullName != null) {
-						var idx = fullName.lastIndexOf(".");
-						if (idx >= 0) {
-							var className = fullName.substr(0, idx);
-							var methodName = fullName.substr(idx+1);
-							method = Method(className, methodName);
-						}
-					}
-					stack.push(FilePos(method, site.getFileName(), site.getLineNumber()));
-				}
-				return stack;
-			}
 			try {
-				throw untyped __new__("Error");
+				throw new js.Error();
 			} catch( e : Dynamic ) {
-				var a = makeStack(e.stack);
-				if( a != null ) a.shift(); // remove Stack.callStack()
-				(untyped Error).prepareStackTrace = oldValue;
+				var a = getStack(e);
+				a.shift(); // remove Stack.callStack()
 				return a;
 			}
+
 		#elseif java
 			var stack = [];
 			for ( el in java.lang.Thread.currentThread().getStackTrace() ) {
@@ -178,6 +191,8 @@ class CallStack {
 					stack.push(FilePos(null, elem._1, elem._2));
 			}
 			return stack;
+		#elseif js
+			return untyped __define_feature__("haxe.CallStack.exceptionStack", getStack(lastException));
 		#else
 			return []; // Unsupported
 		#end
@@ -282,7 +297,9 @@ class CallStack {
 			}
 			return m;
 		#elseif js
-			if ((untyped __js__("typeof"))(s) == "string") {
+			if (s == null) {
+				return [];
+			} else if ((untyped __js__("typeof"))(s) == "string") {
 				// Return the raw lines in browsers that don't support prepareStackTrace
 				var stack : Array<String> = s.split("\n");
 				if( stack[0] == "Error" ) stack.shift();
@@ -296,7 +313,7 @@ class CallStack {
 						var line = Std.parseInt(rie10.matched(3));
 						m.push(FilePos( meth == "Anonymous function" ? LocalFunction() : meth == "Global code" ? null : Method(path.join("."),meth), file, line ));
 					} else
-						m.push(Module(line)); // A little weird, but better than nothing
+						m.push(Module(StringTools.trim(line))); // A little weird, but better than nothing
 				}
 				return m;
 			} else {

+ 11 - 0
std/js/Boot.hx

@@ -21,6 +21,17 @@
  */
 package js;
 
+private class HaxeError extends js.Error {
+
+	var val:Dynamic;
+
+	public function new(val:Dynamic) {
+		super();
+		this.val = untyped __define_feature__("js.Boot.HaxeError", val);
+		untyped js.Error.captureStackTrace(this, HaxeError);
+	}
+}
+
 @:dox(hide)
 class Boot {
 

+ 2 - 2
tests/optimization/src/TestJs.hx

@@ -74,13 +74,13 @@ class TestJs {
 		forEach(function(x) trace(x + 2));
 	}
 
-	@:js('var a = "";var tmp;var __ex0 = a;var _g = __ex0.toLowerCase();switch(_g) {case "e":tmp = 0;break;default:throw false;}var e = tmp;')
+	@:js('var a = "";var tmp;var __ex0 = a;var _g = __ex0.toLowerCase();switch(_g) {case "e":tmp = 0;break;default:throw new Error();}var e = tmp;')
 	@:analyzer(no_const_propagation, no_local_dce)
 	static function testRValueSwitchWithExtractors() {
 		var a = "";
 		var e = switch (a) {
 			case _.toLowerCase() => "e": 0;
-			default: throw false;
+			default: throw new js.Error();
 		}
 	}
 

+ 5 - 0
tests/unit/src/unitstd/haxe/CallStack.unit.hx

@@ -0,0 +1,5 @@
+var stack = haxe.CallStack.callStack();
+Std.is(stack, Array) == true;
+
+var stack = haxe.CallStack.exceptionStack();
+Std.is(stack, Array) == true;