Jelajahi Sumber

Cross-target exception handling (#9124)

Aleksandr Kuzmenko 5 tahun lalu
induk
melakukan
509b5d5f07
72 mengubah file dengan 3194 tambahan dan 1585 penghapusan
  1. 6 0
      src-json/meta.json
  2. 100 0
      src/context/common.ml
  3. 5 0
      src/core/texpr.ml
  4. 541 0
      src/filters/exceptions.ml
  5. 5 9
      src/filters/filters.ml
  6. 0 212
      src/filters/jsExceptions.ml
  7. 0 187
      src/filters/tryCatchWrapper.ml
  8. 5 3
      src/generators/genjava.ml
  9. 20 1
      src/generators/genjs.ml
  10. 24 108
      src/generators/genjvm.ml
  11. 6 53
      src/generators/genlua.ml
  12. 22 107
      src/generators/genphp7.ml
  13. 8 73
      src/generators/genpy.ml
  14. 2 2
      src/generators/genswf9.ml
  15. 4 1
      src/generators/jvm/jvmSignature.ml
  16. 0 1
      src/macro/eval/evalHash.ml
  17. 4 4
      src/macro/eval/evalStdLib.ml
  18. 1 1
      src/optimization/dce.ml
  19. 6 2
      src/typing/typer.ml
  20. 81 0
      std/cpp/_std/haxe/Exception.hx
  21. 42 0
      std/cpp/_std/haxe/NativeStackTrace.hx
  22. 2 2
      std/cpp/cppia/HostClasses.hx
  23. 0 1
      std/cs/Boot.hx
  24. 0 1
      std/cs/_std/Std.hx
  25. 114 0
      std/cs/_std/haxe/Exception.hx
  26. 60 0
      std/cs/_std/haxe/NativeStackTrace.hx
  27. 0 63
      std/cs/internal/Exceptions.hx
  28. 84 0
      std/eval/_std/haxe/Exception.hx
  29. 32 0
      std/eval/_std/haxe/NativeStackTrace.hx
  30. 96 0
      std/flash/_std/haxe/Exception.hx
  31. 69 0
      std/flash/_std/haxe/NativeStackTrace.hx
  32. 93 337
      std/haxe/CallStack.hx
  33. 110 0
      std/haxe/Exception.hx
  34. 15 0
      std/haxe/NativeStackTrace.hx
  35. 38 0
      std/haxe/ValueException.hx
  36. 79 0
      std/hl/_std/haxe/Exception.hx
  37. 58 0
      std/hl/_std/haxe/NativeStackTrace.hx
  38. 0 1
      std/java/Boot.hx
  39. 0 1
      std/java/_std/Std.hx
  40. 108 0
      std/java/_std/haxe/Exception.hx
  41. 55 0
      std/java/_std/haxe/NativeStackTrace.hx
  42. 0 90
      std/java/internal/Exceptions.hx
  43. 5 5
      std/java/internal/Runtime.hx
  44. 1 21
      std/js/Boot.hx
  45. 153 0
      std/js/_std/haxe/Exception.hx
  46. 137 0
      std/js/_std/haxe/NativeStackTrace.hx
  47. 0 60
      std/jvm/Exception.hx
  48. 0 1
      std/jvm/Jvm.hx
  49. 81 0
      std/lua/_std/haxe/Exception.hx
  50. 54 0
      std/lua/_std/haxe/NativeStackTrace.hx
  51. 81 0
      std/neko/_std/haxe/Exception.hx
  52. 51 0
      std/neko/_std/haxe/NativeStackTrace.hx
  53. 2 16
      std/php/Boot.hx
  54. 1 1
      std/php/Throwable.hx
  55. 0 165
      std/php/_std/haxe/CallStack.hx
  56. 99 0
      std/php/_std/haxe/Exception.hx
  57. 115 0
      std/php/_std/haxe/NativeStackTrace.hx
  58. 0 1
      std/python/Boot.hx
  59. 90 0
      std/python/_std/haxe/Exception.hx
  60. 46 0
      std/python/_std/haxe/NativeStackTrace.hx
  61. 0 37
      std/python/internal/HxException.hx
  62. 2 0
      tests/misc/projects/Issue8303/MainCatch.hx
  63. 1 1
      tests/misc/projects/Issue8303/compile-fail.hxml
  64. 16 1
      tests/misc/projects/Issue8303/compile-fail.hxml.stderr
  65. 1 1
      tests/misc/projects/Issue8303/compile.hxml
  66. 7 7
      tests/optimization/src/TestJs.hx
  67. 2 2
      tests/optimization/src/TestTreGeneration.hx
  68. 2 0
      tests/runci/targets/Flash.hx
  69. 336 0
      tests/unit/src/unit/TestExceptions.hx
  70. 1 0
      tests/unit/src/unit/TestMain.hx
  71. 2 2
      tests/unit/src/unit/issues/Issue4644.hx
  72. 13 4
      tests/unit/src/unitstd/haxe/CallStack.unit.hx

+ 6 - 0
src-json/meta.json

@@ -1253,5 +1253,11 @@
 		"metadata": ":void",
 		"doc": "Use Cpp native `void` return type.",
 		"platforms": ["cpp"]
+	},
+	{
+		"name": "NeedsExceptionStack",
+		"metadata": ":needsExceptionStack",
+		"doc": "Internally used for some of auto-generated `catch` vars",
+		"internal": true
 	}
 ]

+ 100 - 0
src/context/common.ml

@@ -79,6 +79,21 @@ type capture_policy =
 	(** similar to wrap ref, but will only apply to the locals that are declared in loops *)
 	| CPLoopVars
 
+type exceptions_config = {
+	(* Base types which may be thrown from Haxe code without wrapping. *)
+	ec_native_throws : path list;
+	(* Base types which may be caught from Haxe code without wrapping. *)
+	ec_native_catches : path list;
+	(* Path of a native class or interface, which can be used for wildcard catches. *)
+	ec_wildcard_catch : path;
+	(*
+		Path of a native base class or interface, which can be thrown.
+		This type is used to cast `haxe.Exception.thrown(v)` calls to.
+		For example `throw 123` is compiled to `throw (cast Exception.thrown(123):ec_base_throw)`
+	*)
+	ec_base_throw : path;
+}
+
 type platform_config = {
 	(** has a static type system, with not-nullable basic types (Int/Float/Bool) *)
 	pf_static : bool;
@@ -106,6 +121,8 @@ type platform_config = {
 	pf_supports_threads : bool;
 	(** target supports Unicode **)
 	pf_supports_unicode : bool;
+	(** exceptions handling config **)
+	pf_exceptions : exceptions_config;
 }
 
 class compiler_callbacks = object(self)
@@ -320,6 +337,12 @@ let default_config =
 		pf_this_before_super = true;
 		pf_supports_threads = false;
 		pf_supports_unicode = true;
+		pf_exceptions = {
+			ec_native_throws = [];
+			ec_native_catches = [];
+			ec_wildcard_catch = ([],"Dynamic");
+			ec_base_throw = ([],"Dynamic");
+		}
 	}
 
 let get_config com =
@@ -335,6 +358,15 @@ let get_config com =
 			pf_capture_policy = CPLoopVars;
 			pf_reserved_type_paths = [([],"Object");([],"Error")];
 			pf_this_before_super = (get_es_version com) < 6; (* cannot access `this` before `super()` when generating ES6 classes *)
+			pf_exceptions = {
+				ec_native_throws = [
+					["js";"lib"],"Error";
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [];
+				ec_wildcard_catch = ([],"Dynamic");
+				ec_base_throw = ([],"Dynamic");
+			}
 		}
 	| Lua ->
 		{
@@ -359,12 +391,36 @@ let get_config com =
 			pf_capture_policy = CPLoopVars;
 			pf_can_skip_non_nullable_argument = false;
 			pf_reserved_type_paths = [([],"Object");([],"Error")];
+			pf_exceptions = {
+				ec_native_throws = [
+					["flash";"errors"],"Error";
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [
+					["flash";"errors"],"Error";
+					["haxe"],"Exception";
+				];
+				ec_wildcard_catch = ([],"Dynamic");
+				ec_base_throw = ([],"Dynamic");
+			}
 		}
 	| Php ->
 		{
 			default_config with
 			pf_static = false;
 			pf_uses_utf16 = false;
+			pf_exceptions = {
+				ec_native_throws = [
+					["php"],"Throwable";
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [
+					["php"],"Throwable";
+					["haxe"],"Exception";
+				];
+				ec_wildcard_catch = (["php"],"Throwable");
+				ec_base_throw = (["php"],"Throwable");
+			}
 		}
 	| Cpp ->
 		{
@@ -382,6 +438,18 @@ let get_config com =
 			pf_pad_nulls = true;
 			pf_overload = true;
 			pf_supports_threads = true;
+			pf_exceptions = {
+				ec_native_throws = [
+					["cs";"system"],"Exception";
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [
+					["cs";"system"],"Exception";
+					["haxe"],"Exception";
+				];
+				ec_wildcard_catch = (["cs";"system"],"Exception");
+				ec_base_throw = (["cs";"system"],"Exception");
+			}
 		}
 	| Java ->
 		{
@@ -391,6 +459,18 @@ let get_config com =
 			pf_overload = true;
 			pf_supports_threads = true;
 			pf_this_before_super = false;
+			pf_exceptions = {
+				ec_native_throws = [
+					["java";"lang"],"RuntimeException";
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [
+					["java";"lang"],"Throwable";
+					["haxe"],"Exception";
+				];
+				ec_wildcard_catch = (["java";"lang"],"Throwable");
+				ec_base_throw = (["java";"lang"],"RuntimeException");
+			}
 		}
 	| Python ->
 		{
@@ -398,6 +478,16 @@ let get_config com =
 			pf_static = false;
 			pf_capture_policy = CPLoopVars;
 			pf_uses_utf16 = false;
+			pf_exceptions = {
+				ec_native_throws = [
+					["python";"Exceptions"],"BaseException";
+				];
+				ec_native_catches = [
+					["python";"Exceptions"],"BaseException";
+				];
+				ec_wildcard_catch = ["python";"Exceptions"],"BaseException";
+				ec_base_throw = ["python";"Exceptions"],"BaseException";
+			}
 		}
 	| Hl ->
 		{
@@ -405,6 +495,16 @@ let get_config com =
 			pf_capture_policy = CPWrapRef;
 			pf_pad_nulls = true;
 			pf_supports_threads = true;
+			pf_exceptions = {
+				ec_native_throws = [
+					["haxe"],"Exception";
+				];
+				ec_native_catches = [
+					["haxe"],"Exception";
+				];
+				ec_wildcard_catch = ([],"Dynamic");
+				ec_base_throw = ([],"Dynamic");
+			}
 		}
 	| Eval ->
 		{

+ 5 - 0
src/core/texpr.ml

@@ -517,6 +517,11 @@ module Builder = struct
 	let mk_parent e =
 		mk (TParenthesis e) e.etype e.epos
 
+	let ensure_parent e =
+		match e.eexpr with
+		| TParenthesis _ -> e
+		| _ -> mk_parent e
+
 	let mk_return e =
 		mk (TReturn (Some e)) t_dynamic e.epos
 

+ 541 - 0
src/filters/exceptions.ml

@@ -0,0 +1,541 @@
+open Globals
+open Ast
+open Type
+open Common
+open Typecore
+open TyperBase
+open Fields
+open Error
+
+let haxe_exception_type_path = (["haxe"],"Exception")
+
+type context = {
+	typer : typer;
+	basic : basic_types;
+	config : exceptions_config;
+	wildcard_catch_type : Type.t;
+	base_throw_type : Type.t;
+	haxe_exception_class : tclass;
+	haxe_exception_type : Type.t;
+	haxe_native_stack_trace : tclass;
+}
+
+let is_dynamic t =
+	match Abstract.follow_with_abstracts t with
+	| TAbstract({ a_path = [],"Dynamic" }, _) -> true
+	| t -> t == t_dynamic
+
+(**
+	Generate `haxe.Exception.method_name(args)`
+*)
+let haxe_exception_static_call ctx method_name args p =
+	let method_field =
+		try PMap.find method_name ctx.haxe_exception_class.cl_statics
+		with Not_found -> error ("haxe.Exception has no field " ^ method_name) p
+	in
+	let return_type =
+		match follow method_field.cf_type with
+		| TFun(_,t) -> t
+		| _ -> error ("haxe.Exception." ^ method_name ^ " is not a function and cannot be called") p
+	in
+	make_static_call ctx.typer ctx.haxe_exception_class method_field (fun t -> t) args return_type p
+
+(**
+	Generate `haxe_exception.method_name(args)`
+*)
+let haxe_exception_instance_call ctx haxe_exception method_name args p =
+	match quick_field haxe_exception.etype method_name with
+	| FInstance (_,_,cf) as faccess ->
+		let efield = { eexpr = TField(haxe_exception,faccess); etype = cf.cf_type; epos = p } in
+		let rt =
+			match follow cf.cf_type with
+			| TFun(_,t) -> t
+			| _ ->
+				error ((s_type (print_context()) haxe_exception.etype) ^ "." ^ method_name ^ " is not a function and cannot be called") p
+		in
+		make_call ctx.typer efield args rt p
+	| _ -> error ((s_type (print_context()) haxe_exception.etype) ^ "." ^ method_name ^ " is expected to be an instance method") p
+
+(**
+	Generate `Std.isOfType(e, t)`
+*)
+let std_is ctx e t p =
+	let t = follow t in
+	let std_cls =
+		match Typeload.load_type_raise ctx.typer ([],"Std") "Std" p with
+		| TClassDecl cls -> cls
+		| _ -> error "Std is expected to be a class" p
+	in
+	let isOfType_field =
+		try PMap.find "isOfType" std_cls.cl_statics
+		with Not_found -> error ("Std has no field isOfType") p
+	in
+	let return_type =
+		match follow isOfType_field.cf_type with
+		| TFun(_,t) -> t
+		| _ -> error ("Std.isOfType is not a function and cannot be called") p
+	in
+	let type_expr = { eexpr = TTypeExpr(module_type_of_type t); etype = t; epos = null_pos } in
+	make_static_call ctx.typer std_cls isOfType_field (fun t -> t) [e; type_expr] return_type p
+
+(**
+	Check if type path of `t` exists in `lst`
+*)
+let is_in_list t lst =
+	match Abstract.follow_with_abstracts t with
+	| TInst(cls,_) ->
+		let rec check cls =
+			List.mem cls.cl_path lst
+			|| List.exists (fun (cls,_) -> check cls) cls.cl_implements
+			|| Option.map_default (fun (cls,_) -> check cls) false cls.cl_super
+		in
+		(match follow t with
+		| TInst (cls, _) -> check cls
+		| _ -> false
+		)
+	| TAbstract({ a_path = path },_)
+	| TEnum({ e_path = path },_) ->
+		List.mem path lst
+	| _ -> false
+
+(**
+	Check if `t` can be thrown without wrapping.
+*)
+let rec is_native_throw cfg t =
+	is_in_list t cfg.ec_native_throws
+
+(**
+	Check if `t` can be caught without wrapping.
+*)
+let rec is_native_catch cfg t =
+	is_in_list t cfg.ec_native_catches
+
+(**
+	Check if `cls` is or extends (if `check_parent=true`) `haxe.Exception`
+*)
+let rec is_haxe_exception_class ?(check_parent=true) cls =
+	cls.cl_path = haxe_exception_type_path
+	|| (check_parent && match cls.cl_super with
+		| None -> false
+		| Some (cls, _) -> is_haxe_exception_class ~check_parent cls
+	)
+
+(**
+	Check if `t` is or extends `haxe.Exception`
+*)
+let is_haxe_exception ?(check_parent=true) (t:Type.t) =
+	match Abstract.follow_with_abstracts t with
+		| TInst (cls, _) -> is_haxe_exception_class ~check_parent cls
+		| _ -> false
+
+(**
+	Check if `v` variable is used in `e` expression
+*)
+let rec is_var_used v e =
+	match e.eexpr with
+	| TLocal v2 -> v == v2
+	| _ -> check_expr (is_var_used v) e
+
+(**
+	Check if `e` contains any throws or try..catches.
+*)
+let rec contains_throw_or_try e =
+	match e.eexpr with
+	| TThrow _ | TTry _ -> true
+	| _ -> check_expr contains_throw_or_try e
+
+(**
+	Returns `true` if `e` has to be wrapped with `haxe.Exception.thrown(e)`
+	to be thrown.
+*)
+let requires_wrapped_throw cfg e =
+	(*
+		Check if `e` is of `haxe.Exception` type directly (not a descendant),
+		but not a `new haxe.Exception(...)` expression.
+		In this case we delegate the decision to `haxe.Exception.thrown(e)`.
+		Because it could happen to be a wrapper for a wildcard catch.
+	*)
+	let is_stored_haxe_exception() =
+		is_haxe_exception ~check_parent:false e.etype
+		&& match e.eexpr with
+			| TNew(_,_,_) -> false
+			| _ -> true
+	in
+	is_stored_haxe_exception()
+	|| (not (is_native_throw cfg e.etype) && not (is_haxe_exception e.etype))
+
+(**
+	Generate a throw of a native exception.
+*)
+let throw_native ctx e_thrown t p =
+	let e_native =
+		if requires_wrapped_throw ctx.config e_thrown then
+			let thrown = haxe_exception_static_call ctx "thrown" [e_thrown] p in
+			if is_dynamic ctx.base_throw_type then thrown
+			else mk_cast thrown ctx.base_throw_type p
+		else
+			e_thrown
+	in
+	mk (TThrow e_native) t p
+
+let set_needs_exception_stack v =
+	if not (Meta.has Meta.NeedsExceptionStack v.v_meta) then
+		v.v_meta <- (Meta.NeedsExceptionStack,[],null_pos) :: v.v_meta
+
+(**
+	Transform user-written `catches` to a set of catches, which would not require
+	special handling in the target generator.
+
+	For example:
+	```
+	} catch(e:SomeNativeError) {
+		doStuff();
+	} catch(e:String) {
+		trace(e);
+	}
+	```
+	is transformed into
+	```
+	} catch(e:SomeNativeError) {
+		doStuff();
+	} catch(etmp:WildCardNativeException) {
+		var ehx:haxe.Exception = haxe.Exception.caught(etmp);
+		if(Std.isOfType(ehx.unwrap(), String)) {
+			var e:String = ehx.unwrap();
+			trace(e);
+		} else {
+			throw etmp;
+		}
+	}
+	```
+*)
+let catch_native ctx catches t p =
+	let rec transform = function
+		| [] -> []
+		(* Keep catches for native exceptions intact *)
+		| (v,_) as current :: rest when (is_native_catch ctx.config v.v_type)
+			(*
+				In case haxe.Exception extends native exception on current target.
+				We don't want it to be generated as a native catch.
+			*)
+			&& not (fast_eq ctx.haxe_exception_type (follow v.v_type)) ->
+			current :: (transform rest)
+		(* Everything else falls into `if(Std.is(e, ExceptionType)`-fest *)
+		| rest ->
+			let catch_var = gen_local ctx.typer ctx.wildcard_catch_type null_pos in
+			let catch_local = mk (TLocal catch_var) catch_var.v_type null_pos in
+			let body =
+				let haxe_exception_var = gen_local ctx.typer ctx.haxe_exception_type null_pos in
+				let haxe_exception_local = mk (TLocal haxe_exception_var) haxe_exception_var.v_type null_pos in
+				let unwrapped_var = gen_local ctx.typer t_dynamic null_pos in
+				let unwrapped_local = mk (TLocal unwrapped_var) unwrapped_var.v_type null_pos in
+				let needs_haxe_exception = ref false
+				and needs_unwrap = ref false in
+				let get_haxe_exception() =
+					needs_haxe_exception := true;
+					haxe_exception_local
+				and unwrap() =
+					needs_haxe_exception := true;
+					needs_unwrap := true;
+					unwrapped_local;
+				in
+				let catch_var_used = ref false in
+				let rec transform = function
+					| (v, body) :: rest ->
+						let current_t = Abstract.follow_with_abstracts v.v_type in
+						let var_used = is_var_used v body in
+						(* catch(e:ExtendsHaxeError) *)
+						if is_haxe_exception current_t then
+							let condition =
+								(* catch(e:haxe.Exception) is a wildcard catch *)
+								if fast_eq ctx.haxe_exception_type current_t then
+									mk (TConst (TBool true)) ctx.basic.tbool v.v_pos
+								else begin
+									std_is ctx (get_haxe_exception()) v.v_type v.v_pos
+								end
+							in
+							let body =
+								if var_used then
+									mk (TBlock [
+										(* var v:ExceptionType = cast haxe_exception_local; *)
+										mk (TVar (v, Some (mk_cast (get_haxe_exception()) v.v_type null_pos))) ctx.basic.tvoid null_pos;
+										body
+									]) body.etype body.epos
+								else
+									body
+							in
+							compose condition body rest
+						(* catch(e:Dynamic) *)
+						else if current_t == t_dynamic then
+							begin
+								set_needs_exception_stack catch_var;
+								(* this is a wildcard catch *)
+								let condition = mk (TConst (TBool true)) ctx.basic.tbool v.v_pos in
+								let body =
+									mk (TBlock [
+										(* var v:Dynamic = haxe_exception_local.unwrap(); *)
+										if var_used then
+											mk (TVar (v, Some (unwrap()))) ctx.basic.tvoid null_pos
+										else
+											mk (TBlock[]) ctx.basic.tvoid null_pos;
+										body
+									]) body.etype body.epos
+								in
+								compose condition body rest
+							end
+						(* catch(e:NativeWildcardException) *)
+						else if fast_eq ctx.wildcard_catch_type current_t then
+							begin
+								set_needs_exception_stack catch_var;
+								(* this is a wildcard catch *)
+								let condition = mk (TConst (TBool true)) ctx.basic.tbool v.v_pos in
+								let body =
+									mk (TBlock [
+										(* var v:NativeWildcardException = catch_var; *)
+										if var_used then
+											mk (TVar (v, Some catch_local)) ctx.basic.tvoid null_pos
+										else
+											mk (TBlock[]) ctx.basic.tvoid null_pos;
+										body
+									]) body.etype body.epos
+								in
+								compose condition body rest
+							end
+						(* catch(e:AnythingElse) *)
+						else begin
+							set_needs_exception_stack catch_var;
+							let condition =
+								catch_var_used := true;
+								(* Std.isOfType(haxe_exception_local.unwrap(), ExceptionType) *)
+								std_is ctx (unwrap()) v.v_type v.v_pos
+							in
+							let body =
+								mk (TBlock [
+									(* var v:ExceptionType = cast haxe_exception_local.unwrap() *)
+									if var_used then
+										mk (TVar (v, Some (mk_cast (unwrap()) v.v_type null_pos))) ctx.basic.tvoid null_pos
+									else
+										mk (TBlock[]) ctx.basic.tvoid null_pos;
+									body
+								]) body.etype body.epos
+							in
+							compose condition body rest
+						end
+					| [] -> mk (TThrow catch_local) t p
+				and compose condition body rest_catches =
+					let else_body =
+						match rest_catches with
+						| [] -> mk (TThrow catch_local) (mk_mono()) p
+						| _ -> transform rest_catches
+					in
+					mk (TIf(condition, body, Some else_body)) t p
+				in
+				let transformed_catches = transform rest in
+				(* haxe.Exception.caught(catch_var) *)
+				let caught = haxe_exception_static_call ctx "caught" [catch_local] null_pos in
+				let exprs = [
+					(* var haxe_exception_local = haxe.Exception.caught(catch_var); *)
+					if !needs_haxe_exception then
+						(mk (TVar (haxe_exception_var, Some caught)) ctx.basic.tvoid null_pos)
+					else
+						mk (TBlock[]) ctx.basic.tvoid null_pos;
+					(* var unwrapped_local = haxe_exception_local.unwrap(); *)
+					if !needs_unwrap then
+						let unwrap = haxe_exception_instance_call ctx haxe_exception_local "unwrap" [] null_pos in
+						mk (TVar (unwrapped_var, Some unwrap)) ctx.basic.tvoid null_pos
+					else
+						mk (TBlock[]) ctx.basic.tvoid null_pos;
+					transformed_catches
+				] in
+				mk (TBlock exprs) t p
+			in (* let body =  *)
+			[(catch_var,body)]
+	in
+	transform catches
+
+(**
+	Transform `throw` and `try..catch` expressions.
+	`rename_locals` is required to deal with the names of temp vars.
+*)
+let filter tctx =
+	let stub e = e in
+	match tctx.com.platform with (* TODO: implement for all targets *)
+	| Php | Js | Java | Cs | Python | Lua | Eval | Neko | Flash | Hl | Cpp ->
+		let config = tctx.com.config.pf_exceptions in
+		let tp (pack,name) =
+			match List.rev pack with
+			| module_name :: pack_rev when not (Ast.is_lower_ident module_name) ->
+				({ tpackage = List.rev pack_rev; tname = module_name; tparams = []; tsub = Some name },null_pos)
+			| _ ->
+				({ tpackage = pack; tname = name; tparams = []; tsub = None },null_pos)
+		in
+		let wildcard_catch_type =
+			let t = Typeload.load_instance tctx (tp config.ec_wildcard_catch) true in
+			if is_dynamic t then t_dynamic
+			else t
+		and base_throw_type =
+			let t = Typeload.load_instance tctx (tp config.ec_base_throw) true in
+			if is_dynamic t then t_dynamic
+			else t
+		and haxe_exception_type, haxe_exception_class =
+			match Typeload.load_instance tctx (tp haxe_exception_type_path) true with
+			| TInst(cls,_) as t -> t,cls
+			| _ -> error "haxe.Exception is expected to be a class" null_pos
+		and haxe_native_stack_trace =
+			match Typeload.load_instance tctx (tp (["haxe"],"NativeStackTrace")) true with
+			| TInst(cls,_) -> cls
+			| TAbstract({ a_impl = Some cls },_) -> cls
+			| _ -> error "haxe.NativeStackTrace is expected to be a class or an abstract" null_pos
+		in
+		let ctx = {
+			typer = tctx;
+			basic = tctx.t;
+			config = config;
+			wildcard_catch_type = wildcard_catch_type;
+			base_throw_type = base_throw_type;
+			haxe_exception_class = haxe_exception_class;
+			haxe_exception_type = haxe_exception_type;
+			haxe_native_stack_trace = haxe_native_stack_trace;
+		} in
+		let rec run e =
+			match e.eexpr with
+			| TThrow e1 ->
+				{ e with eexpr = TThrow (throw_native ctx (run e1) e.etype e.epos) }
+			| TTry(e1,catches) ->
+				let catches =
+					let catches = List.map (fun (v,e) -> (v,run e)) catches in
+					(catch_native ctx catches e.etype e.epos)
+				in
+				{ e with eexpr = TTry(run e1,catches) }
+			| _ ->
+				map_expr run e
+		in
+		(fun e ->
+			if contains_throw_or_try e then run e
+			else stub e
+		)
+	| Cross -> stub
+
+(**
+	Inserts `haxe.NativeStackTrace.saveStack(e)` in non-haxe.Exception catches.
+*)
+let insert_save_stacks tctx =
+	if not (has_feature tctx.com "haxe.NativeStackTrace.exceptionStack") then
+		(fun e -> e)
+	else
+		let native_stack_trace_cls =
+			let tp = { tpackage = ["haxe"]; tname = "NativeStackTrace"; tparams = []; tsub = None } in
+			match Typeload.load_type_def tctx null_pos tp with
+			| TClassDecl cls -> cls
+			| TAbstractDecl { a_impl = Some cls } -> cls
+			| _ -> error "haxe.NativeStackTrace is expected to be a class or an abstract" null_pos
+		in
+		let rec contains_insertion_points e =
+			match e.eexpr with
+			| TTry (e, catches) ->
+				List.exists (fun (v, _) -> Meta.has Meta.NeedsExceptionStack v.v_meta) catches
+				|| contains_insertion_points e
+				|| List.exists (fun (_, e) -> contains_insertion_points e) catches
+			| _ ->
+				check_expr contains_insertion_points e
+		in
+		let save_exception_stack catch_var =
+			(* GOTCHA: `has_feature` always returns `true` if executed before DCE filters *)
+			if has_feature tctx.com "haxe.NativeStackTrace.exceptionStack" then
+				let method_field =
+					try PMap.find "saveStack" native_stack_trace_cls.cl_statics
+					with Not_found -> error ("haxe.NativeStackTrace has no field saveStack") null_pos
+				in
+				let return_type =
+					match follow method_field.cf_type with
+					| TFun(_,t) -> t
+					| _ -> error ("haxe.NativeStackTrace." ^ method_field.cf_name ^ " is not a function and cannot be called") null_pos
+				in
+				let catch_local = mk (TLocal catch_var) catch_var.v_type null_pos in
+				make_static_call tctx native_stack_trace_cls method_field (fun t -> t) [catch_local] return_type null_pos
+			else
+				mk (TBlock[]) tctx.t.tvoid null_pos
+		in
+		let rec run e =
+			match e.eexpr with
+			| TTry (e1, catches) ->
+				let e1 = map_expr run e1 in
+				let catches =
+					List.map (fun ((v, body) as catch) ->
+						if Meta.has Meta.NeedsExceptionStack v.v_meta then
+							let exprs =
+								match body.eexpr with
+								| TBlock exprs ->
+									save_exception_stack v :: exprs
+								| _ ->
+									[save_exception_stack v; body]
+							in
+							(v, { body with eexpr = TBlock exprs })
+						else
+							catch
+					) catches
+				in
+				{ e with eexpr = TTry (e1, catches) }
+			| _ ->
+				map_expr run e
+		in
+		(fun e ->
+			if contains_insertion_points e then run e
+			else e
+		)
+
+(**
+	Adds `this.__shiftStack()` calls to constructors of classes which extend `haxe.Exception`
+*)
+let patch_constructors tctx =
+	let tp = ({ tpackage = fst haxe_exception_type_path; tname = snd haxe_exception_type_path; tparams = []; tsub = None },null_pos) in
+	match Typeload.load_instance tctx tp true with
+	(* Add only if `__shiftStack` method exists *)
+	| TInst(cls,_) when PMap.mem "__shiftStack" cls.cl_fields ->
+		(fun mt ->
+			match mt with
+			| TClassDecl cls when not cls.cl_extern && cls.cl_path <> haxe_exception_type_path && is_haxe_exception_class cls ->
+				let shift_stack p =
+					let t = type_of_module_type mt in
+					let this = { eexpr = TConst(TThis); etype = t; epos = p } in
+					let faccess =
+						try quick_field t "__shiftStack"
+						with Not_found -> error "haxe.Exception has no field __shiftStack" p
+					in
+					match faccess with
+					| FInstance (_,_,cf) ->
+						let efield = { eexpr = TField(this,faccess); etype = cf.cf_type; epos = p } in
+						let rt =
+							match follow cf.cf_type with
+							| TFun(_,t) -> t
+							| _ ->
+								error "haxe.Exception.__shiftStack is not a function and cannot be called" cf.cf_name_pos
+						in
+						make_call tctx efield [] rt p
+					| _ -> error "haxe.Exception.__shiftStack is expected to be an instance method" p
+				in
+				TypeloadFunction.add_constructor tctx cls true cls.cl_name_pos;
+				Option.may (fun cf -> ignore(follow cf.cf_type)) cls.cl_constructor;
+				(match cls.cl_constructor with
+				| Some ({ cf_expr = Some e_ctor } as ctor) ->
+					let rec add e =
+						match e.eexpr with
+						| TFunction _ -> e
+						| TReturn _ -> mk (TBlock [shift_stack e.epos; e]) e.etype e.epos
+						| _ -> map_expr add e
+					in
+					(ctor.cf_expr <- match e_ctor.eexpr with
+						| TFunction fn ->
+							Some { e_ctor with
+								eexpr = TFunction { fn with
+									tf_expr = mk (TBlock [add fn.tf_expr; shift_stack fn.tf_expr.epos]) tctx.t.tvoid fn.tf_expr.epos
+								}
+							}
+						| _ -> assert false
+					)
+				| None -> assert false
+				| _ -> ()
+				)
+			| _ -> ()
+		)
+	| _ -> (fun _ -> ())

+ 5 - 9
src/filters/filters.ml

@@ -827,22 +827,17 @@ let run com tctx main =
 		if defined com Define.AnalyzerOptimize then Tre.run tctx else (fun e -> e);
 		Optimizer.reduce_expression tctx;
 		if Common.defined com Define.OldConstructorInline then Optimizer.inline_constructors tctx else InlineConstructors.inline_constructors tctx;
+		Exceptions.filter tctx;
 		CapturedVars.captured_vars com;
 	] in
 	let filters =
 		match com.platform with
 		| Cs ->
 			SetHXGen.run_filter com new_types;
-			filters @ [
-				TryCatchWrapper.configure_cs com
-			]
+			filters
 		| Java when not (Common.defined com Jvm)->
 			SetHXGen.run_filter com new_types;
-			filters @ [
-				TryCatchWrapper.configure_java com
-			]
-		| Js ->
-			filters @ [JsExceptions.init tctx];
+			filters
 		| _ -> filters
 	in
 	let t = filter_timer detail_times ["expr 1"] in
@@ -917,7 +912,9 @@ let run com tctx main =
 	t();
 	com.stage <- CDceDone;
 	(* PASS 3: type filters post-DCE *)
+	List.iter (run_expression_filters tctx [Exceptions.insert_save_stacks tctx]) new_types;
 	let type_filters = [
+		Exceptions.patch_constructors;
 		check_private_path;
 		apply_native_paths;
 		add_rtti;
@@ -930,7 +927,6 @@ let run com tctx main =
 	] in
 	let type_filters = match com.platform with
 		| Cs -> type_filters @ [ fun _ t -> InterfaceProps.run t ]
-		| Js -> JsExceptions.inject_callstack com type_filters
 		| _ -> type_filters
 	in
 	let t = filter_timer detail_times ["type 3"] in

+ 0 - 212
src/filters/jsExceptions.ml

@@ -1,212 +0,0 @@
-(*
-	The Haxe Compiler
-	Copyright (C) 2005-2019  Haxe Foundation
-
-	This program is free software; you can redistribute it and/or
-	modify it under the terms of the GNU General Public License
-	as published by the Free Software Foundation; either version 2
-	of the License, or (at your option) any later version.
-
-	This program is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-	GNU General Public License for more details.
-
-	You should have received a copy of the GNU General Public License
-	along with this program; if not, write to the Free Software
-	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- *)
-
-(*
-	This filter handles everything related to exceptions for the JavaScript target:
-
-	- wrapping non-js.Error types in HaxeError on throwing
-	- unwrapping HaxeError on catch
-	- transforming series of catches into a single catch with Std.is checks (optimized)
-	- re-throwing caught exception with js.Lib.rethrow
-	- storing caught exception in haxe.CallStack.lastException (if haxe.CallStack is used)
-
-	Basically it translates this:
-
-	 try throw "fail"
-	 catch (e:String) { trace(e); js.Lib.rethrow(); }
-	 catch (e:Bool) {}
-
-	into something like this (JS):
-
-	 try {
-		 throw new HaxeError("fail");
-	 } catch (e) {
-		 haxe.CallStack.lastException = e;
-		 var e1 = (e instanceof HaxeError) e.val : e;
-		 if (typeof e1 == "string") {
-			 trace(e1);
-			 throw e;
-		 } else if (typeof e1 == "boolean") {
-		 } else {
-			 throw e;
-		 }
-	 }
-*)
-
-open Common
-open Type
-open Typecore
-open Texpr.Builder
-
-let follow = Abstract.follow_with_abstracts
-
-let rec is_js_error c =
-	match c with
-	| { cl_path = ["js";"lib"],"Error" } -> true
-	| { cl_super = Some (csup,_) } -> is_js_error csup
-	| _ -> false
-
-let find_cl com path =
-	ExtList.List.find_map (function
-		| TClassDecl c when c.cl_path = path -> Some c
-		| _ -> None
-	) com.types
-
-let init ctx =
-	let cJsError = find_cl ctx.com (["js";"lib"],"Error") in
-	let cHaxeError = find_cl ctx.com (["js";"_Boot"],"HaxeError") in
-	let cStd = find_cl ctx.com ([],"Std") in
-	let cBoot = find_cl ctx.com (["js"],"Boot") in
-	let cSyntax = find_cl ctx.com (["js"],"Syntax") in
-
-	let dynamic_wrap e =
-		let eHaxeError = make_static_this cHaxeError e.epos in
-		fcall eHaxeError "wrap" [e] (TInst (cJsError, [])) e.epos
-	in
-
-	let static_wrap e =
-		{ e with eexpr = TNew (cHaxeError,[],[e]); etype = TInst (cHaxeError,[]) }
-	in
-
-	let rec loop vrethrow e =
-		match e.eexpr with
-		| TThrow eexc ->
-			let eexc = loop vrethrow eexc in
-			let eexc =
-				match follow eexc.etype with
-				| TDynamic _ | TMono _ ->
-					(match eexc.eexpr with
-					| TConst (TInt _ | TFloat _ | TString _ | TBool _ | TNull) -> static_wrap eexc
-					| _ -> dynamic_wrap eexc)
-				| TInst (c,_) when (is_js_error c) ->
-					eexc
-				| _ ->
-					static_wrap eexc
-			in
-			{ e with eexpr = TThrow eexc }
-
-		| TCall ({ eexpr = TField (_, FStatic ({ cl_path = ["js"],"Lib" }, { cf_name = "getOriginalException" })) }, _) ->
-			(match vrethrow with
-			| Some erethrowvar -> erethrowvar
-			| None -> abort "js.Lib.getOriginalException can only be called inside a catch block" e.epos)
-
-		| TCall ({ eexpr = TField (_, FStatic ({ cl_path = ["js"],"Lib" }, { cf_name = "rethrow" })) }, _) ->
-			(match vrethrow with
-			| Some erethrowvar -> { e with eexpr = TThrow erethrowvar }
-			| None -> abort "js.Lib.rethrow can only be called inside a catch block" e.epos)
-
-		| TTry (etry, catches) ->
-			let etry = loop vrethrow etry in
-
-			let catchall_name, catchall_kind = match catches with [(v,_)] -> v.v_name, (VUser TVOCatchVariable) | _ -> "e", VGenerated in
-			let vcatchall = alloc_var catchall_kind catchall_name t_dynamic e.epos in
-			let ecatchall = make_local vcatchall e.epos in
-			let erethrow = mk (TThrow ecatchall) t_dynamic e.epos in
-
-			let eSyntax = make_static_this cSyntax e.epos in
-			let eHaxeError = make_static_this cHaxeError e.epos in
-			let eInstanceof = fcall eSyntax "instanceof" [ecatchall;eHaxeError] ctx.com.basic.tbool e.epos in
-			let eVal = field { ecatchall with etype = TInst (cHaxeError,[]) } "val" t_dynamic e.epos in
-			let eunwrap = mk (TIf (eInstanceof, eVal, Some (ecatchall))) t_dynamic e.epos in
-
-			let vunwrapped = alloc_var catchall_kind catchall_name t_dynamic e.epos in
-			vunwrapped.v_kind <- VGenerated;
-			let eunwrapped = make_local vunwrapped e.epos in
-
-			let ecatch = List.fold_left (fun acc (v,ecatch) ->
-				let ecatch = loop (Some ecatchall) ecatch in
-
-				(* it's not really compiler-generated, but it kind of is, since it was used as catch identifier and we add a TVar for it *)
-				v.v_kind <- VGenerated;
-
-				match follow v.v_type with
-				| TDynamic _ ->
-					{ ecatch with
-						eexpr = TBlock [
-							mk (TVar (v, Some eunwrapped)) ctx.com.basic.tvoid ecatch.epos;
-							ecatch;
-						]
-					}
-				| t ->
-					let etype = make_typeexpr (module_type_of_type t) e.epos in
-					let args = [eunwrapped;etype] in
-					let echeck =
-						match Inline.api_inline ctx cStd "isOfType" args e.epos with
-						| Some e -> e
-						| None ->
-							let eBoot = make_static_this cBoot e.epos in
-							fcall eBoot "__instanceof" [eunwrapped;etype] ctx.com.basic.tbool e.epos
-					in
-					let ecatch = { ecatch with
-						eexpr = TBlock [
-							mk (TVar (v, Some eunwrapped)) ctx.com.basic.tvoid ecatch.epos;
-							ecatch;
-						]
-					} in
-					mk (TIf (echeck, ecatch, Some acc)) e.etype e.epos
-			) erethrow (List.rev catches) in
-
-			let ecatch = { ecatch with
-				eexpr = TBlock [
-					mk (TVar (vunwrapped, Some eunwrap)) ctx.com.basic.tvoid e.epos;
-					ecatch;
-				]
-			} in
-			{ e with eexpr = TTry (etry, [(vcatchall,ecatch)]) }
-		| _ ->
-			Type.map_expr (loop vrethrow) e
-	in
-	loop None
-
-let inject_callstack com type_filters =
-	let cCallStack =
-		if Common.has_dce com then
-			if Common.has_feature com "haxe.CallStack.lastException" then
-				Some (find_cl com (["haxe"],"CallStack"))
-			else
-				None
-		else
-			try Some (find_cl com (["haxe"],"CallStack")) with Not_found -> None
-	in
-	match cCallStack with
-	| Some cCallStack ->
-		let run mt e =
-			let rec loop e =
-				match e.eexpr with
-				| TTry (etry,[(v,ecatch)]) ->
-					let etry = loop etry in
-					let ecatch = loop ecatch in
-					add_dependency (t_infos mt).mt_module cCallStack.cl_module;
-					let eCallStack = make_static_this cCallStack ecatch.epos in
-					let elastException = field eCallStack "lastException" t_dynamic ecatch.epos in
-					let elocal = make_local v ecatch.epos in
-					let eStoreException = mk (TBinop (Ast.OpAssign, elastException, elocal)) ecatch.etype ecatch.epos in
-					let ecatch = Type.concat eStoreException ecatch in
-					{ e with eexpr = TTry (etry,[(v,ecatch)]) }
-				| TTry _ ->
-					(* this should be handled by the filter above *)
-					assert false
-				| _ ->
-					Type.map_expr loop e
-			in
-			loop e
-		in
-		type_filters @ [ fun ctx t -> FiltersCommon.run_expression_filters ctx [run t] t ]
-	| None ->
-		type_filters

+ 0 - 187
src/filters/tryCatchWrapper.ml

@@ -1,187 +0,0 @@
-(*
-	The Haxe Compiler
-	Copyright (C) 2005-2019  Haxe Foundation
-
-	This program is free software; you can redistribute it and/or
-	modify it under the terms of the GNU General Public License
-	as published by the Free Software Foundation; either version 2
-	of the License, or (at your option) any later version.
-
-	This program is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-	GNU General Public License for more details.
-
-	You should have received a copy of the GNU General Public License
-	along with this program; if not, write to the Free Software
-	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-*)
-open Globals
-open Common
-open Ast
-open Type
-open Codegen
-open Texpr.Builder
-
-(* ******************************************* *)
-(* Try / Catch + throw native types handling *)
-(* ******************************************* *)
-(*
-	Some languages/vm's do not support throwing any kind of value. For them, only
-	special kinds of objects can be thrown. Because of this, we must wrap some throw
-	statements with an expression, and also we must unwrap it on the catch() phase, and
-	maybe manually test with Std.is()
-*)
-
-(*
-	should_wrap : does the type should be wrapped? This of course works on the reverse way, so it tells us if the type should be unwrapped as well
-	wrap_throw : the wrapper for throw (throw expr->returning wrapped expression)
-	unwrap_expr : the other way around : given the catch var (maybe will need casting to wrapper_type) , return the unwrap expr
-	rethrow_expr : how to rethrow ane exception in the platform
-	catchall_type : the class used for catchall (e:Dynamic)
-	wrapper_type : the wrapper type, so we can test if exception is of type 'wrapper'
-	catch_map : maps the catch expression to include some intialization code (e.g. setting up Stack.exceptionStack)
-	gen_typecheck : generate Std.is (or similar) check expression for given expression and type
-*)
-let init com (should_wrap:t->bool) (wrap_throw:texpr->texpr) (unwrap_expr:texpr->texpr) (rethrow_expr:texpr->texpr) (catchall_type:t) (wrapper_type:t) (catch_map:tvar->texpr->texpr) (gen_typecheck:texpr->t->pos->texpr) =
-	let rec run e =
-		match e.eexpr with
-		| TThrow texpr when should_wrap texpr.etype ->
-			wrap_throw (run texpr)
-		| TTry (ttry, catches) ->
-			let nowrap_catches, must_wrap_catches, catchall = List.fold_left (fun (nowrap_catches, must_wrap_catches, catchall) (v, catch) ->
-				(* first we'll see if the type is Dynamic (catchall) *)
-				match follow v.v_type with
-				| TDynamic _ ->
-					assert (Option.is_none catchall);
-					(nowrap_catches, must_wrap_catches, Some(v, run catch))
-				(* see if we should unwrap it *)
-				| _ when should_wrap (follow v.v_type) ->
-					(nowrap_catches, (v,run catch) :: must_wrap_catches, catchall)
-				| _ ->
-					((v,catch_map v (run catch)) :: nowrap_catches, must_wrap_catches, catchall)
-			) ([], [], None) catches in
-
-			(* temp (?) fix for https://github.com/HaxeFoundation/haxe/issues/4134 *)
-			let must_wrap_catches = List.rev must_wrap_catches in
-
-			(*
-				1st catch all nowrap "the easy way"
-				2nd see if there are any must_wrap or catchall. If there is,
-					do a catchall first with a temp var.
-					then get catchall var (as dynamic) (or create one), and declare it = catchall exception
-					then test if it is of type wrapper_type. If it is, unwrap it
-					then start doing Std.is() tests for each catch type
-					if there is a catchall in the end, end with it. If there isn't, rethrow
-			*)
-			let dyn_catch = match catchall, must_wrap_catches with
-			| Some (v,c), _
-			| _, (v, c) :: _ ->
-				let pos = c.epos in
-
-				let temp_var = alloc_var VGenerated "catchallException" catchall_type pos in
-				let temp_local = make_local temp_var pos in
-				let catchall_var = alloc_var VGenerated "realException" t_dynamic pos in
-				let catchall_local = make_local catchall_var pos in
-
-				(* if it is of type wrapper_type, unwrap it *)
-				let catchall_expr = mk (TIf (gen_typecheck temp_local wrapper_type pos, unwrap_expr temp_local, Some temp_local)) t_dynamic pos in
-				let catchall_decl = mk (TVar (catchall_var, Some catchall_expr)) com.basic.tvoid pos in
-
-				let rec loop must_wrap_catches =
-					match must_wrap_catches with
-					| (vcatch,catch) :: tl ->
-						mk (TIf (gen_typecheck catchall_local vcatch.v_type catch.epos,
-							     mk (TBlock [(mk (TVar (vcatch, Some(mk_cast (* TODO: this should be a fast non-dynamic cast *) catchall_local vcatch.v_type pos))) com.basic.tvoid catch.epos); catch]) catch.etype catch.epos,
-							     Some (loop tl))
-						) catch.etype catch.epos
-					| [] ->
-						match catchall with
-						| Some (v,s) ->
-							Type.concat (mk (TVar (v, Some catchall_local)) com.basic.tvoid pos) s
-						| None ->
-							mk_block (rethrow_expr temp_local)
-				in
-				[(temp_var, catch_map temp_var { e with eexpr = TBlock [catchall_decl; loop must_wrap_catches] })]
-			| _ ->
-				[]
-			in
-			{ e with eexpr = TTry(run ttry, (List.rev nowrap_catches) @ dyn_catch) }
-		| _ ->
-			Type.map_expr run e
-	in
-	run
-
-let find_class com path =
-	let mt = List.find (fun mt -> match mt with TClassDecl c -> c.cl_path = path | _ -> false) com.types in
-	match mt with TClassDecl c -> c | _ -> assert false
-
-let configure_cs com =
-	let base_exception = find_class com (["cs";"system"], "Exception") in
-	let base_exception_t = TInst(base_exception, []) in
-	let hx_exception = find_class com (["cs";"internal";"_Exceptions"], "HaxeException") in
-	let hx_exception_t = TInst (hx_exception, []) in
-	let exc_cl = find_class com (["cs";"internal"],"Exceptions") in
-	let rec is_exception t =
-		match follow t with
-		| TInst (cl,_) -> is_parent base_exception cl
-		| _ -> false
-	in
-	let e_rethrow = mk (TIdent "__rethrow__") t_dynamic null_pos in
-	let should_wrap t = not (is_exception t) in
-	let wrap_throw expr =
-		match expr.eexpr with
-		| TIdent "__rethrow__" ->
-			make_throw expr expr.epos
-		| _ ->
-			let e_hxexception = make_static_this hx_exception expr.epos in
-			let e_wrap = fcall e_hxexception "wrap" [expr] base_exception_t expr.epos in
-			make_throw e_wrap expr.epos
-	in
-	let unwrap_expr local_to_unwrap = field (mk_cast local_to_unwrap hx_exception_t local_to_unwrap.epos) "obj" t_dynamic local_to_unwrap.epos in
-	let rethrow_expr rethrow = make_throw e_rethrow rethrow.epos in
-	let catch_map v e =
-		let e_exc = make_static_this exc_cl e.epos in
-		let e_field = field e_exc "exception" base_exception_t e.epos in
-		let e_setstack = binop OpAssign e_field (make_local v e.epos) v.v_type e.epos in
-		Type.concat e_setstack e
-	in
-	let std_cl = find_class com ([],"Std") in
-	let gen_typecheck e t pos =
-		let std = make_static_this std_cl pos in
-		let e_type = make_typeexpr (module_type_of_type t) pos in
-		fcall std "isOfType" [e; e_type] com.basic.tbool pos
-	in
-	init com should_wrap wrap_throw unwrap_expr rethrow_expr base_exception_t hx_exception_t catch_map gen_typecheck
-
-let configure_java com =
-	let base_exception = find_class com (["java"; "lang"], "Throwable") in
-	let base_exception_t = TInst (base_exception, []) in
-	let hx_exception = find_class com (["java";"internal";"_Exceptions"], "HaxeException") in
-	let hx_exception_t = TInst (hx_exception, []) in
-	let exc_cl = find_class com (["java";"internal"],"Exceptions") in
-	let rec is_exception t =
-		match follow t with
-		| TInst (cl,_) -> is_parent base_exception cl
-		| _ -> false
-	in
-	let should_wrap t = not (is_exception t) in
-	let wrap_throw expr =
-		let e_hxexception = make_static_this hx_exception expr.epos in
-		let e_wrap = fcall e_hxexception "wrap" [expr] base_exception_t expr.epos in
-		make_throw e_wrap expr.epos
-	in
-	let unwrap_expr local_to_unwrap = field (mk_cast local_to_unwrap hx_exception_t local_to_unwrap.epos) "obj" t_dynamic local_to_unwrap.epos in
-	let rethrow_expr exc = { exc with eexpr = TThrow exc } in
-	let catch_map v e =
-		let exc = make_static_this exc_cl e.epos in
-		let e_setstack = fcall exc "setException" [make_local v e.epos] com.basic.tvoid e.epos in
-		Type.concat e_setstack e;
-	in
-	let std_cl = find_class com ([],"Std") in
-	let gen_typecheck e t pos =
-		let std = make_static_this std_cl pos in
-		let e_type = make_typeexpr (module_type_of_type t) pos in
-		fcall std "is" [e; e_type] com.basic.tbool pos
-	in
-	init com should_wrap wrap_throw unwrap_expr rethrow_expr base_exception_t hx_exception_t catch_map gen_typecheck

+ 5 - 3
src/generators/genjava.ml

@@ -905,11 +905,13 @@ let rec handle_throws gen cf =
 			Type.iter iter e
 		with | Exit -> (* needs typed exception to be caught *)
 			let throwable = get_cl (get_type gen (["java";"lang"],"Throwable")) in
+			let cast_cl = get_cl (get_type gen (["java";"lang"],"RuntimeException")) in
 			let catch_var = alloc_var "typedException" (TInst(throwable,[])) in
 			let rethrow = mk_local catch_var e.epos in
-			let hx_exception = get_cl (get_type gen (["haxe";"lang"], "HaxeException")) in
-			let wrap_static = mk_static_field_access (hx_exception) "wrap" (TFun([("obj",false,t_dynamic)], t_dynamic)) rethrow.epos in
-			let wrapped = { rethrow with eexpr = TThrow { rethrow with eexpr = TCall(wrap_static, [rethrow]) }; } in
+			let hx_exception = get_cl (get_type gen (["haxe"], "Exception")) in
+			let wrap_static = mk_static_field_access (hx_exception) "thrown" (TFun([("obj",false,t_dynamic)], t_dynamic)) rethrow.epos in
+			let thrown_value = mk_cast (TInst(cast_cl,[])) { rethrow with eexpr = TCall(wrap_static, [rethrow]) } in
+			let wrapped = { rethrow with eexpr = TThrow thrown_value; } in
 			let map_throws cl =
 				let var = alloc_var "typedException" (TInst(cl,List.map (fun _ -> t_dynamic) cl.cl_params)) in
 				var, { tf.tf_expr with eexpr = TThrow (mk_local var e.epos) }

+ 20 - 1
src/generators/genjs.ml

@@ -61,6 +61,7 @@ type ctx = {
 	mutable type_accessor : module_type -> string;
 	mutable separator : bool;
 	mutable found_expose : bool;
+	mutable catch_vars : texpr list;
 }
 
 type object_store = {
@@ -403,6 +404,21 @@ let rec gen_call ctx e el in_value =
 		spr ctx ")";
 	| TField (_, FStatic ({ cl_path = ["js"],"Syntax" }, { cf_name = meth })), args ->
 		gen_syntax ctx meth args e.epos
+	| TField (_, FStatic ({ cl_path = ["js"],"Lib" }, { cf_name = "rethrow" })), [] ->
+		(match ctx.catch_vars with
+			| e :: _ ->
+				spr ctx "throw ";
+				gen_value ctx e
+			| _ ->
+				abort "js.Lib.rethrow can only be called inside a catch block" e.epos
+		)
+	| TField (_, FStatic ({ cl_path = ["js"],"Lib" }, { cf_name = "getOriginalException" })), [] ->
+		(match ctx.catch_vars with
+			| e :: _ ->
+				gen_value ctx e
+			| _ ->
+				abort "js.Lib.getOriginalException can only be called inside a catch block" e.epos
+		)
 	| TIdent "__new__", args ->
 		print_deprecation_message ctx.com "__new__ is deprecated, use js.Syntax.construct instead" e.epos;
 		gen_syntax ctx "construct" args e.epos
@@ -711,7 +727,9 @@ and gen_expr ctx e =
 		gen_expr ctx etry;
 		check_var_declaration v;
 		print ctx " catch( %s ) " v.v_name;
-		gen_expr ctx ecatch
+		ctx.catch_vars <- (mk (TLocal v) v.v_type v.v_pos) :: ctx.catch_vars;
+		gen_expr ctx ecatch;
+		ctx.catch_vars <- List.tl ctx.catch_vars
 	| TTry _ ->
 		abort "Unhandled try/catch, please report" e.epos
 	| TSwitch (e,cases,def) ->
@@ -1591,6 +1609,7 @@ let alloc_ctx com es_version =
 		type_accessor = (fun _ -> assert false);
 		separator = false;
 		found_expose = false;
+		catch_vars = [];
 	} in
 
 	ctx.type_accessor <- (fun t ->

+ 24 - 108
src/generators/genjvm.ml

@@ -51,6 +51,7 @@ exception HarderFailure of string
 type generation_context = {
 	com : Common.context;
 	jar : Zip.out_file;
+	t_runtime_exception : Type.t;
 	entry_point : (tclass * texpr) option;
 	t_exception : Type.t;
 	t_throwable : Type.t;
@@ -294,45 +295,26 @@ let type_unifies a b =
 
 let follow = Abstract.follow_with_abstracts
 
-class haxe_exception gctx (t : Type.t) = object(self)
-	val native_exception =
-		if follow t == t_dynamic then
-			throwable_sig,false
-		else if type_unifies t gctx.t_exception then
-			jsignature_of_type gctx t,true
-		else
-			haxe_exception_sig,false
-
-	val mutable native_exception_path = None
+class haxe_exception gctx (t : Type.t) =
+	let is_haxe_exception = Exceptions.is_haxe_exception t
+	and native_type = jsignature_of_type gctx t in
+object(self)
+	val native_path = (match native_type with TObject(path,_) -> path | _ -> assert false)
 
 	method is_assignable_to (exc2 : haxe_exception) =
-		match self#is_native_exception,exc2#is_native_exception with
-		| true, true ->
-			(* Native exceptions are assignable if they unify *)
+		match self#is_haxe_exception,exc2#is_haxe_exception with
+		| true, true | false, false ->
 			type_unifies t exc2#get_type
-		| false,false ->
-			(* Haxe exceptions are always assignable to each other *)
-			true
+		(* `haxe.Exception` is assignable to java.lang.RuntimeException/Exception/Throwable *)
 		| false,true ->
-			(* Haxe exception is assignable to native only if caught type is java.lang.Exception/Throwable *)
-			let exc2_native_exception_type = exc2#get_native_exception_type in
-			exc2_native_exception_type = throwable_sig || exc2_native_exception_type = exception_sig
+			List.mem exc2#get_native_type [throwable_sig; exception_sig; runtime_exception_sig]
 		| _ ->
-			(* Native to Haxe is never assignable *)
 			false
 
-	method is_native_exception = snd native_exception
-	method get_native_exception_type = fst native_exception
-
-	method get_native_exception_path =
-		match native_exception_path with
-		| None ->
-			let path = (match (fst native_exception) with TObject(path,_) -> path | _ -> assert false) in
-			native_exception_path <- Some path;
-			path
-		| Some path ->
-			path
+	method is_haxe_exception = is_haxe_exception
 
+	method get_native_type = native_type
+	method get_native_path = native_path
 	method get_type = t
 end
 
@@ -1629,11 +1611,6 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 
 	(* exceptions *)
 
-	method throw vt =
-		jm#expect_reference_type;
-		jm#invokestatic (["haxe";"jvm"],"Exception") "wrap" (method_sig [object_sig] (Some exception_sig));
-		code#athrow;
-
 	method try_catch ret e1 catches =
 		let restore = jm#start_branch in
 		let fp_from = code#get_fp in
@@ -1648,17 +1625,6 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 		let term_try = jm#is_terminated in
 		let r_try = jm#maybe_make_jump in
 		let fp_to = code#get_fp in
-		let unwrap () =
-			code#dup;
-			code#instanceof haxe_exception_path;
-			jm#if_then_else
-				(code#if_ CmpEq)
-				(fun () ->
-					jm#cast haxe_exception_sig;
-					jm#getfield (["haxe";"jvm"],"Exception") "value" object_sig;
-				)
-				(fun () -> jm#cast object_sig);
-		in
 		let start_exception_block path jsig =
 			restore();
 			let fp_target = code#get_fp in
@@ -1671,8 +1637,6 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 			};
 			code#get_stack#push jsig;
 			jm#add_stack_frame;
-			jm#get_code#dup;
-			jm#invokestatic haxe_exception_path "setException" (method_sig [throwable_sig] None);
 		in
 		let run_catch_expr v e =
 			let pop_scope = jm#push_scope in
@@ -1683,63 +1647,15 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 			jm#is_terminated
 		in
 		let add_catch (exc,v,e) =
-			start_exception_block exc#get_native_exception_path exc#get_native_exception_type;
-			if not exc#is_native_exception then begin
-				unwrap();
-				self#cast v.v_type
-			end;
+			start_exception_block exc#get_native_path exc#get_native_type;
 			let term = run_catch_expr v e in
 			let r = jm#maybe_make_jump in
 			term,r
 		in
-		let commit_instanceof_checks excl =
-			start_exception_block throwable_path throwable_sig;
-			let pop_scope = jm#push_scope in
-			let _,load,save = jm#add_local "exc" throwable_sig VarWillInit in
-			code#dup;
-			save();
-			unwrap();
-			let restore = jm#start_branch in
-			let rl = ref [] in
-			let rec loop excl = match excl with
-				| [] ->
-					code#pop;
-					load();
-					code#athrow;
-				| (_,v,e) :: excl ->
-					code#dup;
-					let path = match self#vtype (self#mknull v.v_type) with TObject(path,_) -> path | _ -> assert false in
-					if path = object_path then begin
-						code#pop;
-						restore();
-						let term = run_catch_expr v e in
-						rl := (term,ref 0) :: !rl;
-					end else begin
-						code#instanceof path;
-						jm#if_then_else
-							(code#if_ CmpEq)
-							(fun () ->
-								restore();
-								self#cast v.v_type;
-								let term = run_catch_expr v e in
-								rl := (term,ref 0) :: !rl;
-							)
-							(fun () -> loop excl)
-					end
-			in
-			loop excl;
-			pop_scope();
-			!rl
-		in
 		let rec loop acc excl = match excl with
 			| (exc,v,e) :: excl ->
-				if List.exists (fun (exc',_,_) -> exc'#is_assignable_to exc) excl || excl = [] && not exc#is_native_exception then begin
-					let res = commit_instanceof_checks ((exc,v,e) :: excl) in
-					acc @ res
-				end else begin
-					let res = add_catch (exc,v,e) in
-					loop (res :: acc) excl
-				end
+				let res = add_catch (exc,v,e) in
+				loop (res :: acc) excl
 			| [] ->
 				acc
 		in
@@ -2027,14 +1943,13 @@ class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return
 			self#cast e.etype;
 		| TThrow e1 ->
 			self#texpr rvalue_any e1;
-			let exc = new haxe_exception gctx e1.etype in
-			if not (List.exists (fun exc' -> exc#is_assignable_to exc') caught_exceptions) then jm#add_thrown_exception exc#get_native_exception_path;
-			if not exc#is_native_exception then begin
-				let vt = self#vtype (self#mknull e1.etype) in
-				self#throw vt
-			end else begin
-				code#athrow;
-			end
+			if not (Exceptions.is_haxe_exception e1.etype) && not (type_unifies e1.etype gctx.t_runtime_exception) then begin
+				let exc = new haxe_exception gctx e1.etype in
+				if not (List.exists (fun exc' -> exc#is_assignable_to exc') caught_exceptions) then
+					jm#add_thrown_exception exc#get_native_path;
+			end;
+			code#athrow;
+			jm#set_terminated true
 		| TObjectDecl fl ->
 			let td = gctx.anon_identification#identify true e.etype in
 			begin match follow e.etype,td with
@@ -2897,6 +2812,7 @@ let generate com =
 	let gctx = {
 		com = com;
 		jar = Zip.open_out jar_path;
+		t_runtime_exception = TInst(resolve_class com (["java";"lang"],"RuntimeException"),[]);
 		entry_point = entry_point;
 		t_exception = TInst(resolve_class com (["java";"lang"],"Exception"),[]);
 		t_throwable = TInst(resolve_class com (["java";"lang"],"Throwable"),[]);

+ 6 - 53
src/generators/genlua.ml

@@ -940,7 +940,6 @@ and gen_expr ?(local=true) ctx e = begin
         println ctx "local _hx_status, _hx_result = pcall(function() ";
         let b = open_block ctx in
         gen_expr ctx e;
-        let vname = temp ctx in
         b();
         println ctx "return _hx_pcall_default";
         println ctx "end)";
@@ -953,58 +952,12 @@ and gen_expr ?(local=true) ctx e = begin
                 println ctx "  break";
         println ctx "elseif not _hx_status then ";
         let bend = open_block ctx in
-        newline ctx;
-        print ctx "local %s = _hx_result" vname;
-        let last = ref false in
-        let else_block = ref false in
-        List.iter (fun (v,e) ->
-            if !last then () else
-                let t = (match follow v.v_type with
-                    | TEnum (e,_) -> Some (TEnumDecl e)
-                    | TInst (c,_) -> Some (TClassDecl c)
-                    | TAbstract (a,_) -> Some (TAbstractDecl a)
-                    | TFun _
-                    | TLazy _
-                    | TType _
-                    | TAnon _ ->
-                        assert false
-                    | TMono _
-                    | TDynamic _ ->
-                        None
-                ) in
-                match t with
-                | None ->
-                    last := true;
-                    if !else_block then print ctx "";
-                    if vname <> v.v_name then begin
-                        newline ctx;
-                        print ctx "local %s = %s" v.v_name vname;
-                    end;
-                    gen_block_element ctx e;
-                    if !else_block then begin
-                        newline ctx;
-                        print ctx " end ";
-                    end
-                | Some t ->
-                    if not !else_block then newline ctx;
-                    print ctx "if( %s.__instanceof(%s," (ctx.type_accessor (TClassDecl { null_class with cl_path = ["lua"],"Boot" })) vname;
-                    gen_value ctx (mk (TTypeExpr t) (mk_mono()) e.epos);
-                    spr ctx ") ) then ";
-                    let bend = open_block ctx in
-                    if vname <> v.v_name then begin
-                        newline ctx;
-                        print ctx "local %s = %s" v.v_name vname;
-                    end;
-                    gen_block_element ctx e;
-                    bend();
-                    newline ctx;
-                    spr ctx "else";
-                    else_block := true
-        ) catchs;
-        if not !last then begin
-            println ctx " _G.error(%s)" vname;
-            spr ctx "end";
-        end;
+        (match catchs with
+        | [v,e] ->
+            print ctx "  local %s = _hx_result;" v.v_name;
+            gen_block_element ctx e;
+        | _ -> assert false
+        );
         bend();
         newline ctx;
         println ctx "elseif _hx_result ~= _hx_pcall_default then";

+ 22 - 107
src/generators/genphp7.ml

@@ -85,14 +85,6 @@ let hashtbl_keys tbl = Hashtbl.fold (fun key _ lst -> key :: lst) tbl []
 *)
 let diff_lists list1 list2 = List.filter (fun x -> not (List.mem x list2)) list1
 
-(**
-	Type path for native PHP Exception class
-*)
-let native_exception_path = ([], "Throwable")
-(**
-	Type path for Haxe exceptions wrapper
-*)
-let hxexception_type_path = (["php"; "_Boot"], "HxException")
 (**
 	Type path of `php.Boot`
 *)
@@ -310,7 +302,7 @@ let is_function_type t = match follow t with TFun _ -> true | _ -> false
 *)
 let is_syntax_extern expr =
 	match expr.eexpr with
-		| TField ({ eexpr = TTypeExpr (TClassDecl { cl_path = path }) }, _) when path = syntax_type_path -> true
+		| TField ({ eexpr = TTypeExpr (TClassDecl { cl_path = path }) }, _) -> path = syntax_type_path
 		| _ -> false
 
 (**
@@ -527,34 +519,6 @@ let get_full_type_name ?(escape=false) ?(omit_first_slash=false) (type_path:path
 	else
 		name
 
-(**
-	Check if `target` is or implements native PHP `Throwable` interface
-*)
-let rec is_native_exception (target:Type.t) =
-	match follow target with
-		| TInst ({ cl_path = path }, _) when path = native_exception_path -> true
-		| TInst ({ cl_super = parent ; cl_implements = interfaces ; cl_path = path }, _) ->
-			let (parent, params) =
-				match parent with
-					| Some (parent, params) -> (Some parent, params)
-					| None -> (None, [])
-			in
-			let found = ref false in
-			List.iter
-				(fun (cls, params) ->
-					if not !found then
-						found := is_native_exception (TInst (cls, params))
-				)
-				interfaces;
-			if !found then
-				true
-			else
-				(match parent with
-					| Some parent -> is_native_exception (TInst (parent, params))
-					| None -> false
-				)
-		| _ -> false
-
 (**
 	@return Short type name. E.g. returns "Test" for (["example"], "Test")
 *)
@@ -1624,10 +1588,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 				| TCall (target, [arg1; arg2]) when is_std_is target -> self#write_expr_std_is target arg1 arg2
 				| TCall (_, [arg]) when is_native_struct_array_cast expr && is_object_declaration arg ->
 					(match (reveal_expr arg).eexpr with TObjectDecl fields -> self#write_assoc_array_decl fields | _ -> fail self#pos __POS__)
-				| TCall ({ eexpr = TIdent name}, args) when is_magic expr ->
-					let msg = "untyped " ^ name ^ " is deprecated. Use php.Syntax instead." in
-					DeprecationCheck.warn_deprecation ctx.pgc_common msg self#pos;
-					self#write_expr_magic name args
+				| TCall ({ eexpr = TIdent name}, args) when is_magic expr -> self#write_expr_magic name args
 				| TCall ({ eexpr = TField (expr, access) }, args) when is_string expr -> self#write_expr_call_string expr access args
 				| TCall (expr, args) when is_syntax_extern expr -> self#write_expr_call_syntax_extern expr args
 				| TCall (target, args) when is_sure_var_field_access target -> self#write_expr_call (parenthesis target) args
@@ -1791,7 +1752,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 			vars#captured used_vars;
 			self#write " ";
 			if List.length used_vars > 0 then begin
-				self#write " use (";
+				self#write "use (";
 				write_args self#write (fun name -> self#write ("&$" ^ name)) used_vars;
 				self#write ") "
 			end;
@@ -1864,12 +1825,16 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 			and exprs = match expr.eexpr with TBlock exprs -> exprs | _ -> [expr] in
 			let write_body () =
 				let write_expr expr =
-					if not ctx.pgc_skip_line_directives && not (is_block expr) then
+					if not ctx.pgc_skip_line_directives && not (is_block expr) && expr.epos <> null_pos then
 						if self#write_pos expr then self#write_indentation;
-					self#write_expr expr;
 					match expr.eexpr with
-						| TBlock _ | TIf _ | TTry _ | TSwitch _ | TWhile (_, _, NormalWhile) -> self#write "\n"
-						| _ -> self#write ";\n"
+						| TBlock _ ->
+							self#write_as_block ~inline:true expr
+						| _ ->
+							self#write_expr expr;
+							match expr.eexpr with
+								| TBlock _ | TIf _ | TTry _ | TSwitch _ | TWhile (_, _, NormalWhile) -> self#write "\n"
+								| _ -> self#write ";\n"
 				in
 				let write_expr_with_indent expr =
 					self#write_indentation;
@@ -1930,73 +1895,21 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 		*)
 		method write_expr_throw expr =
 			self#write "throw ";
-			if is_native_exception expr.etype then
-				self#write_expr expr
-			else if sure_extends_extern expr.etype || is_dynamic_type expr.etype then
-				begin
-					self#write "(is_object($__hx__throw = ";
-					self#write_expr expr;
-					self#write (") && $__hx__throw instanceof \\Throwable ? $__hx__throw : new " ^ (self#use hxexception_type_path) ^ "($__hx__throw))")
-				end
-			else
-				begin
-					self#write ("new " ^ (self#use hxexception_type_path) ^ "(");
-					self#write_expr expr;
-					self#write ")"
-				end
+			self#write_expr expr
 		(**
 			Writes try...catch to output buffer
 		*)
 		method write_expr_try_catch try_expr catches =
-			let catching_dynamic = ref false in
-			let haxe_exception = self#use hxexception_type_path
-			and first_catch = ref true in
-			let write_catch (var, expr) =
-				let dynamic = ref false in
-				(match follow var.v_type with
-					| TInst ({ cl_path = ([], "String") }, _) -> self#write "if (is_string($__hx__real_e)) {\n"
-					| TAbstract ({ a_path = ([], "Float") }, _) -> self#write "if (is_float($__hx__real_e)) {\n"
-					| TAbstract ({ a_path = ([], "Int") }, _) -> self#write "if (is_int($__hx__real_e)) {\n"
-					| TAbstract ({ a_path = ([], "Bool") }, _) -> self#write "if (is_bool($__hx__real_e)) {\n"
-					| TDynamic _ ->
-						dynamic := true;
-						catching_dynamic := true;
-						if not !first_catch then self#write "{\n"
-					| vtype -> self#write ("if ($__hx__real_e instanceof " ^ (self#use_t vtype) ^ ") {\n")
-				);
-				if !dynamic && !first_catch then
-					begin
-						self#write ("$" ^ var.v_name ^ " = $__hx__real_e;\n");
-						self#write_indentation;
-						self#write_as_block ~inline:true expr;
-					end
-				else
-					begin
-						self#indent_more;
-						self#write_statement ("$" ^ var.v_name ^ " = $__hx__real_e");
-						self#write_indentation;
-						self#write_as_block ~inline:true expr;
-						self#indent_less;
-						self#write_with_indentation "}";
-					end;
-				if not !dynamic then self#write " else ";
-				first_catch := false;
-			in
 			self#write "try ";
 			self#write_as_block try_expr;
-			self#write " catch (\\Throwable $__hx__caught_e) {\n";
-			self#indent_more;
-			if has_feature ctx.pgc_common "haxe.CallStack.exceptionStack"  then
-				self#write_statement ((self#use (["haxe"], "CallStack")) ^ "::saveExceptionTrace($__hx__caught_e)");
-			self#write_statement ("$__hx__real_e = ($__hx__caught_e instanceof " ^ haxe_exception ^ " ? $__hx__caught_e->e : $__hx__caught_e)");
-			self#write_indentation;
-			List.iter write_catch catches;
-			if not !catching_dynamic then
-				self#write " throw $__hx__caught_e;\n"
-			else
-				(match catches with [_] -> () | _ -> self#write "\n");
-			self#indent_less;
-			self#write_with_indentation "}"
+			let rec traverse = function
+				| [] -> ()
+				| (v,body) :: rest ->
+					self#write (" catch(" ^ (self#use_t v.v_type) ^ " $" ^ v.v_name ^ ") ");
+					self#write_as_block body;
+					traverse rest
+			in
+			traverse catches
 		(**
 			Writes TCast to output buffer
 		*)
@@ -2014,6 +1927,8 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 			@see http://old.haxe.org/doc/advanced/magic#php-magic
 		*)
 		method write_expr_magic name args =
+			let msg = "untyped " ^ name ^ " is deprecated. Use php.Syntax instead." in
+			DeprecationCheck.warn_deprecation ctx.pgc_common msg self#pos;
 			let error = ("Invalid arguments for " ^ name ^ " magic call") in
 			match args with
 				| [] -> fail ~msg:error self#pos __POS__

+ 8 - 73
src/generators/genpy.ml

@@ -1393,19 +1393,7 @@ module Printer = struct
 			| TContinue ->
 				"continue"
 			| TThrow e1 ->
-				let rec is_native_exception t =
-					match Abstract.follow_with_abstracts t with
-					| TInst ({ cl_path = [],"BaseException" }, _) ->
-						true
-					| TInst ({ cl_super = Some csup }, _) ->
-						is_native_exception (TInst(fst csup, snd csup))
-					| _ ->
-						false
-				in
-				if is_native_exception e1.etype then
-					Printf.sprintf "raise %s" (print_expr pctx e1)
-				else
-					Printf.sprintf "raise _HxException(%s)" (print_expr pctx e1)
+				Printf.sprintf "raise %s" (print_expr pctx e1)
 			| TCast(e1,None) ->
 				print_expr pctx e1
 			| TMeta((Meta.Custom ":ternaryIf",_,_),{eexpr = TIf(econd,eif,Some eelse)}) ->
@@ -1484,70 +1472,17 @@ module Printer = struct
 				do_default()
 
 	and print_try pctx e1 catches =
-		let has_catch_all = List.exists (fun (v,_) -> match follow v.v_type with
-			| TDynamic _ -> true
-			| _ -> false
-		) catches in
-		let has_only_catch_all = has_catch_all && begin match catches with
-			| [_] -> true
-			| _ -> false
-		end in
-		let print_catch pctx i (v,e) =
-			let is_empty_expr = begin match e.eexpr with
-				| TBlock [] -> true
-				| _ -> false
-			end in
-			let indent = pctx.pc_indent in
-			(* Don't generate assignment to catch variable when catch expression is an empty block *)
-			let assign = if is_empty_expr then "" else Printf.sprintf "%s = _hx_e1\n%s" v.v_name indent in
-			let handle_base_type bt =
-				let t = print_base_type bt in
-				let print_custom_check t_str =
-					Printf.sprintf "if %s:\n%s    %s    %s" t_str indent assign (print_expr {pctx with pc_indent = "    " ^ pctx.pc_indent} e)
-				in
-				let print_type_check t_str =
-					print_custom_check ("isinstance(_hx_e1, " ^ t_str ^ ")")
-				in
-				let res = match t with
-				| "str" -> print_type_check "str"
-				| "Bool" -> print_type_check "bool"
-				| "Int" -> print_custom_check "(isinstance(_hx_e1, int) and not isinstance(_hx_e1, bool))" (* for historic reasons bool extends int *)
-				| "Float" -> print_type_check "float"
-				| t -> print_type_check t
-				in
-				if i > 0 then
-					indent ^ "el" ^ res
-				else
-					res
-			in
-			match follow v.v_type with
-				| TDynamic _ ->
-					begin if has_only_catch_all then
-						Printf.sprintf "%s%s" assign (print_expr pctx e)
-					else
-						(* Dynamic is always the last block *)
-						Printf.sprintf "%selse:\n    %s%s    %s" indent indent assign (print_expr {pctx with pc_indent = "    " ^ pctx.pc_indent} e)
-					end
-				| TInst(c,_) ->
-					handle_base_type (t_infos (TClassDecl c))
-				| TEnum(en,_) ->
-					handle_base_type (t_infos (TEnumDecl en))
-				| TAbstract(a,_) ->
-					handle_base_type (t_infos (TAbstractDecl a))
-				| _ ->
-					assert false
+		let print_catch pctx (v,e) =
+			let s_type = print_module_type (module_type_of_type v.v_type)
+			and s_expr = pctx.pc_indent ^ (print_expr pctx e) in
+			Printf.sprintf "except %s as %s:\n%s" s_type v.v_name s_expr;
 		in
 		let indent = pctx.pc_indent in
 		let print_expr_indented e = print_expr {pctx with pc_indent = "    " ^ pctx.pc_indent} e in
 		let try_str = Printf.sprintf "try:\n%s    %s\n%s" indent (print_expr_indented e1) indent in
-		let except = if has_feature pctx "has_throw" then
-			Printf.sprintf "except Exception as _hx_e:\n%s    _hx_e1 = _hx_e.val if isinstance(_hx_e, _HxException) else _hx_e\n%s    " indent indent
-		else
-			Printf.sprintf "except Exception as _hx_e:\n%s    _hx_e1 = _hx_e\n%s    " indent indent
-		in
-		let catch_str = String.concat (Printf.sprintf "\n") (ExtList.List.mapi (fun i catch -> print_catch {pctx with pc_indent = "    " ^ pctx.pc_indent} i catch) catches) in
-		let except_end = if not has_catch_all then Printf.sprintf "\n%s    else:\n%s        raise _hx_e" indent indent else "" in
-		Printf.sprintf "%s%s%s%s" try_str except catch_str except_end
+		let catch_pctx = {pctx with pc_indent = "    " ^ pctx.pc_indent} in
+		let catch_str = String.concat (Printf.sprintf "\n") (List.map (print_catch catch_pctx) catches) in
+		Printf.sprintf "%s%s" try_str catch_str
 
 	and print_call2 pctx e1 el =
 		let id = print_expr pctx e1 in

+ 2 - 2
src/generators/genswf9.ml

@@ -1048,7 +1048,7 @@ let rec gen_expr_content ctx retval e =
 		gen_constant ctx c e.etype e.epos
 	| TThrow e ->
 		ctx.infos.icond <- true;
-		if has_feature ctx.com "haxe.CallStack.exceptionStack" then begin
+		if has_feature ctx.com "haxe.CallStack.exceptionStack" && not (Exceptions.is_haxe_exception e.etype) then begin
 			getvar ctx (VGlobal (type_path ctx (["flash"],"Boot")));
 			let id = type_path ctx (["flash";"errors"],"Error") in
 			write ctx (HFindPropStrict id);
@@ -1212,7 +1212,7 @@ let rec gen_expr_content ctx retval e =
 					| _ -> Type.iter call_loop e
 				in
 				let has_call = (try call_loop e; false with Exit -> true) in
-				if has_call && has_feature ctx.com "haxe.CallStack.exceptionStack" then begin
+				if has_call && has_feature ctx.com "haxe.CallStack.exceptionStack" && not (Exceptions.is_haxe_exception v.v_type) then begin
 					getvar ctx (gen_local_access ctx v e.epos Read);
 					write ctx (HAsType (type_path ctx (["flash";"errors"],"Error")));
 					let j = jump ctx J3False in

+ 4 - 1
src/generators/jvm/jvmSignature.ml

@@ -85,7 +85,7 @@ module NativeSignatures = struct
 	let haxe_dynamic_object_path = ["haxe";"jvm"],"DynamicObject"
 	let haxe_dynamic_object_sig = TObject(haxe_dynamic_object_path,[])
 
-	let haxe_exception_path = ["haxe";"jvm"],"Exception"
+	let haxe_exception_path = ["haxe"],"Exception"
 	let haxe_exception_sig = TObject(haxe_exception_path,[])
 
 	let haxe_object_path = ["haxe";"jvm"],"Object"
@@ -97,6 +97,9 @@ module NativeSignatures = struct
 	let exception_path = (["java";"lang"],"Exception")
 	let exception_sig = TObject(exception_path,[])
 
+	let runtime_exception_path = (["java";"lang"],"RuntimeException")
+	let runtime_exception_sig = TObject(runtime_exception_path,[])
+
 	let retention_path = (["java";"lang";"annotation"],"Retention")
 	let retention_sig = TObject(retention_path,[])
 

+ 0 - 1
src/macro/eval/evalHash.ml

@@ -107,7 +107,6 @@ let key_haxe_macro_DisplayKind = hash "haxe.macro.DisplayKind"
 let key_haxe_macro_Message = hash "haxe.macro.Message"
 let key_haxe_macro_FunctionKind = hash "haxe.macro.FunctionKind"
 let key_haxe_macro_StringLiteralKind = hash "haxe.macro.StringLiteralKind"
-let key_haxe_CallStack = hash "haxe.CallStack"
 let key___init__ = hash "__init__"
 let key_new = hash "new"
 let key_questionmark = hash "?"

+ 4 - 4
src/macro/eval/evalStdLib.ml

@@ -537,7 +537,7 @@ module StdBytesBuffer = struct
 	)
 end
 
-module StdCallStack = struct
+module StdNativeStackTrace = struct
 	let make_stack envs =
 		let l = DynArray.create () in
 		List.iter (fun (pos,kind) ->
@@ -3326,9 +3326,9 @@ let init_standard_library builtins =
 		"addBytes",StdBytesBuffer.addBytes;
 		"getBytes",StdBytesBuffer.getBytes;
 	];
-	init_fields builtins (["haxe"],"CallStack") [
-		"getCallStack",StdCallStack.getCallStack;
-		"getExceptionStack",StdCallStack.getExceptionStack;
+	init_fields builtins (["haxe"],"NativeStackTrace") [
+		"_callStack",StdNativeStackTrace.getCallStack;
+		"exceptionStack",StdNativeStackTrace.getExceptionStack;
 	] [];
 	init_fields builtins (["haxe";"zip"],"Compress") [
 		"run",StdCompress.run;

+ 1 - 1
src/optimization/dce.ml

@@ -111,7 +111,7 @@ let mk_keep_meta pos =
 let rec keep_field dce cf c is_static =
 	Meta.has_one_of (Meta.Used :: keep_metas) cf.cf_meta
 	|| cf.cf_name = "__init__"
-	|| not (is_physical_field cf)
+	|| has_class_field_flag cf CfExtern
 	|| (not is_static && overrides_extern_field cf c)
 	|| (
 		cf.cf_name = "new"

+ 6 - 2
src/typing/typer.ml

@@ -1772,11 +1772,14 @@ and type_try ctx e1 catches with_type p =
 			let unreachable () =
 				display_error ctx "This block is unreachable" p;
 				let st = s_type (print_context()) in
-				display_error ctx (Printf.sprintf "%s can be assigned to %s, which is handled here" (st t) (st v.v_type)) e.epos
+				display_error ctx (Printf.sprintf "%s can be caught to %s, which is handled here" (st t) (st v.v_type)) e.epos
 			in
 			begin try
 				begin match follow t,follow v.v_type with
-					| TDynamic _, TDynamic _ ->
+					| _, TDynamic _
+					| _, TInst({ cl_path = ["haxe"],"Error"},_) ->
+						unreachable()
+					| _, TInst({ cl_path = path },_) when path = ctx.com.config.pf_exceptions.ec_wildcard_catch ->
 						unreachable()
 					| TDynamic _,_ ->
 						()
@@ -2651,6 +2654,7 @@ let rec create com =
 		| [TClassDecl c2 ] -> ctx.g.global_using <- (c1,c1.cl_pos) :: (c2,c2.cl_pos) :: ctx.g.global_using
 		| _ -> assert false);
 	| _ -> assert false);
+	ignore(TypeloadModule.load_module ctx (["haxe"],"Exception") null_pos);
 	ctx.g.complete <- true;
 	ctx
 

+ 81 - 0
std/cpp/_std/haxe/Exception.hx

@@ -0,0 +1,81 @@
+package haxe;
+
+//TODO: extend ::std::exception
+@:coreApi
+class Exception {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionMessage:String;
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:Array<String>;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		__exceptionMessage = message;
+		__previousException = previous;
+		if(native != null) {
+			__nativeStack = NativeStackTrace.exceptionStack();
+			__nativeException = native;
+		} else {
+			__nativeStack = NativeStackTrace.callStack();
+			__nativeException = this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return __exceptionMessage;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null: __exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}
+

+ 42 - 0
std/cpp/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,42 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	@:noDebug //Do not mess up the exception stack
+	static public function callStack():Array<String> {
+		return untyped __global__.__hxcpp_get_call_stack(true);
+	}
+
+	@:noDebug //Do not mess up the exception stack/
+	static public function exceptionStack():Array<String> {
+		return untyped __global__.__hxcpp_get_exception_stack();
+	}
+
+	static public function toHaxe(native:Array<String>, skip:Int = 0):Array<StackItem> {
+		var stack:Array<String> = native;
+		var m = new Array<StackItem>();
+		for (i in 0...stack.length) {
+			if(skip > i) {
+				continue;
+			}
+			var words = stack[i].split("::");
+			if (words.length == 0)
+				m.push(CFunction)
+			else if (words.length == 2)
+				m.push(Method(words[0], words[1]));
+			else if (words.length == 4)
+				m.push(FilePos(Method(words[0], words[1]), words[2], Std.parseInt(words[3])));
+		}
+		return m;
+	}
+}

+ 2 - 2
std/cpp/cppia/HostClasses.hx

@@ -85,7 +85,7 @@ class HostClasses {
 		"haxe.ds.ObjectMap",
 		"haxe.ds.StringMap",
 		"haxe.ds.BalancedTree",
-		"haxe.CallStack",
+		"haxe.NativeStackTrace",
 		"haxe.Serializer",
 		"haxe.Unserializer",
 		"haxe.Resource",
@@ -117,7 +117,7 @@ class HostClasses {
 		"haxe.io.StringInput",
 		"haxe.xml.Parser",
 		"haxe.Json",
-		"haxe.CallStack",
+		"haxe.NativeStackTrace",
 		"haxe.Resource",
 		"haxe.Utf8",
 		"haxe.Int64",

+ 0 - 1
std/cs/Boot.hx

@@ -22,7 +22,6 @@
 
 package cs;
 
-import cs.internal.Exceptions;
 import cs.internal.FieldLookup;
 import cs.internal.Function;
 import cs.internal.HxObject;

+ 0 - 1
std/cs/_std/Std.hx

@@ -22,7 +22,6 @@
 
 import cs.Boot;
 import cs.Lib;
-import cs.internal.Exceptions;
 
 @:coreApi @:nativeGen class Std {
 	public static inline function is(v:Dynamic, t:Dynamic):Bool {

+ 114 - 0
std/cs/_std/haxe/Exception.hx

@@ -0,0 +1,114 @@
+package haxe;
+
+import cs.system.Exception as CsException;
+import cs.system.diagnostics.StackTrace;
+
+@:coreApi
+class Exception extends NativeException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:StackTrace;
+	@:noCompletion var __ownStack:Bool;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:CsException;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static public function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, CsException)) {
+			return new Exception((value:CsException).Message, null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static public function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else if(Std.isOfType(value, CsException)) {
+			return value;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message, previous);
+		this.__previousException = previous;
+
+		if(native != null && Std.isOfType(native, CsException)) {
+			__nativeException = native;
+			if(__nativeException.StackTrace == null) {
+				__nativeStack = new StackTrace(1, true);
+				__ownStack = true;
+			} else {
+				__nativeStack = new StackTrace(__nativeException, true);
+				__ownStack = false;
+			}
+		} else {
+			__nativeException = cast this;
+			__nativeStack = new StackTrace(1, true);
+			__ownStack = true;
+		}
+	}
+
+	public function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		if(__ownStack) __skipStack++;
+	}
+
+	function get_message():String {
+		return this.Message;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}
+
+@:dox(hide)
+@:nativeGen
+@:noCompletion
+@:native('System.Exception')
+private extern class NativeException {
+	@:noCompletion private function new(message:String, innerException:NativeException):Void;
+	@:noCompletion @:skipReflection private final Data:cs.system.collections.IDictionary;
+	@:noCompletion @:skipReflection private var HelpLink:String;
+	@:noCompletion @:skipReflection private final InnerException:cs.system.Exception;
+	@:noCompletion @:skipReflection private final Message:String;
+	@:noCompletion @:skipReflection private var Source:String;
+	@:noCompletion @:skipReflection private final StackTrace:String;
+	@:noCompletion @:skipReflection private final TargetSite:cs.system.reflection.MethodBase;
+	@:overload @:noCompletion @:skipReflection private function GetBaseException():cs.system.Exception;
+	@:overload @:noCompletion @:skipReflection private function GetObjectData(info:cs.system.runtime.serialization.SerializationInfo, context:cs.system.runtime.serialization.StreamingContext):Void;
+	@:overload @:noCompletion @:skipReflection private function GetType():cs.system.Type;
+	@:overload @:noCompletion @:skipReflection private function ToString():cs.system.String;
+}

+ 60 - 0
std/cs/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,60 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+import cs.system.diagnostics.StackTrace;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:meta(System.ThreadStaticAttribute)
+	static var exception:Null<cs.system.Exception>;
+
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(e:Any):Void {
+		exception = e;
+	}
+
+	static public inline function callStack():StackTrace {
+		return new StackTrace(1, true);
+	}
+
+	static public function exceptionStack():Null<StackTrace> {
+		return switch exception {
+			case null: null;
+			case e: new StackTrace(e, true);
+		}
+	}
+
+	static public function toHaxe(native:Null<StackTrace>, skip:Int = 0):Array<StackItem> {
+		var stack = [];
+		if(native == null) {
+			return stack;
+		}
+		var cnt = 0;
+		for (i in 0...native.FrameCount) {
+			var frame = native.GetFrame(i);
+			var m = frame.GetMethod();
+
+			if (m == null) {
+				continue;
+			}
+			if(skip > cnt++) {
+				continue;
+			}
+
+			var method = StackItem.Method(m.ReflectedType.ToString(), m.Name);
+
+			var fileName = frame.GetFileName();
+			var lineNumber = frame.GetFileLineNumber();
+
+			if (fileName != null || lineNumber >= 0)
+				stack.push(FilePos(method, fileName, lineNumber));
+			else
+				stack.push(method);
+		}
+		return stack;
+	}
+}

+ 0 - 63
std/cs/internal/Exceptions.hx

@@ -1,63 +0,0 @@
-/*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-package cs.internal;
-
-import cs.system.Exception;
-
-@:nativeGen @:keep @:native("haxe.lang.Exceptions") class Exceptions {
-	@:allow(haxe.CallStack)
-	@:meta(System.ThreadStaticAttribute)
-	static var exception:cs.system.Exception;
-}
-
-// should NOT be usable inside Haxe code
-
-@:classCode('override public string Message { get { return this.toString(); } }\n\n')
-@:nativeGen @:keep @:native("haxe.lang.HaxeException") private class HaxeException extends Exception {
-	private var obj:Dynamic;
-
-	public function new(obj:Dynamic) {
-		super();
-
-		if (Std.isOfType(obj, HaxeException)) {
-			var _obj:HaxeException = cast obj;
-			obj = _obj.getObject();
-		}
-		this.obj = obj;
-	}
-
-	public function getObject():Dynamic {
-		return obj;
-	}
-
-	public function toString():String {
-		return Std.string(obj);
-	}
-
-	public static function wrap(obj:Dynamic):Exception {
-		if (Std.isOfType(obj, Exception))
-			return obj;
-
-		return new HaxeException(obj);
-	}
-}

+ 84 - 0
std/eval/_std/haxe/Exception.hx

@@ -0,0 +1,84 @@
+package haxe;
+
+@:coreApi
+class Exception {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionMessage:String;
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:CallStack;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		__exceptionMessage = message;
+		__previousException = previous;
+		if(native != null) {
+			__nativeStack = NativeStackTrace.exceptionStack();
+			__nativeException = native;
+		} else {
+			__nativeStack = NativeStackTrace.callStack();
+			__nativeException = this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return __exceptionMessage;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = if(__skipStack > 0) {
+					__nativeStack.asArray().slice(__skipStack);
+				} else {
+					__nativeStack;
+				}
+			case s: s;
+		}
+	}
+}

+ 32 - 0
std/eval/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,32 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	static public function callStack():Array<StackItem> {
+		return _callStack();
+	}
+
+	//implemented in the compiler
+	static function _callStack():Array<StackItem> {
+		return null;
+	}
+
+	//implemented in the compiler
+	static public function exceptionStack():Array<StackItem> {
+		return null;
+	}
+
+	static public inline function toHaxe(stack:Array<StackItem>, skip:Int = 0):Array<StackItem> {
+		return skip > 0 ? stack.slice(skip) : stack;
+	}
+}

+ 96 - 0
std/flash/_std/haxe/Exception.hx

@@ -0,0 +1,96 @@
+package haxe;
+
+import flash.errors.Error;
+
+@:coreApi
+class Exception extends NativeException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:String;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int;
+	@:noCompletion var __nativeException:Error;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, Error)) {
+			return new Exception((value:Error).message, null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else if(Std.isOfType(value, Error)) {
+			return value;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message);
+		__previousException = previous;
+		if(native != null && Std.isOfType(native, Error)) {
+			__nativeException = native;
+			__nativeStack = NativeStackTrace.normalize((native:Error).getStackTrace());
+		} else {
+			__nativeException = cast this;
+			__nativeStack = NativeStackTrace.callStack();
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return (cast this:Error).message;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}
+
+@:dox(hide)
+@:native('flash.errors.Error')
+extern class NativeException {
+	@:noCompletion @:flash.property private var errorID(get,never):Int;
+	// @:noCompletion private var message:Dynamic;
+	@:noCompletion private var name:Dynamic;
+	@:noCompletion private function new(?message:Dynamic, id:Dynamic = 0):Void;
+	@:noCompletion private function getStackTrace():String;
+	@:noCompletion private function get_errorID():Int;
+}

+ 69 - 0
std/flash/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,69 @@
+package haxe;
+
+import flash.errors.Error;
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+@:allow(haxe.Exception)
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(e:Any):Void {
+	}
+
+	static public inline function callStack():String {
+		return normalize(new Error().getStackTrace(), 1);
+	}
+
+	static public function exceptionStack():String {
+		var err:Null<Error> = untyped flash.Boot.lastError;
+		return err == null ? '' : normalize(err.getStackTrace());
+	}
+
+	static public function toHaxe(native:String, skip:Int = 0):Array<StackItem> {
+		var a = new Array();
+		var r = ~/at ([^\/]+?)\$?(\/[^\(]+)?\(\)(\[(.*?):([0-9]+)\])?/;
+		var rlambda = ~/^MethodInfo-([0-9]+)$/g;
+		var cnt = 0;
+		while (r.match(native)) {
+			native = r.matchedRight();
+			if(skip > cnt++) {
+				continue;
+			}
+			var cl = r.matched(1).split("::").join(".");
+			var meth = r.matched(2);
+			var item;
+			if (meth == null) {
+				if (rlambda.match(cl))
+					item = LocalFunction(Std.parseInt(rlambda.matched(1)));
+				else
+					item = Method(cl, "new");
+			} else
+				item = Method(cl, meth.substring(1));
+			if (r.matched(3) != null)
+				item = FilePos(item, r.matched(4), Std.parseInt(r.matched(5)));
+			a.push(item);
+		}
+		return a;
+	}
+
+	static function normalize(stack:String, skipItems:Int = 0):String {
+		switch (stack:String).substring(0, 6) {
+			case 'Error:' | 'Error\n': skipItems += 1;
+			case _:
+		}
+		return skipLines(stack, skipItems);
+	}
+
+	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);
+		}
+	}
+}

+ 93 - 337
std/haxe/CallStack.hx

@@ -36,234 +36,122 @@ enum StackItem {
 /**
 	Get information about the call stack.
 **/
-class CallStack {
-	#if js
-	static var lastException:js.lib.Error;
-
-	static function getStack(e:js.lib.Error):Array<StackItem> {
-		if (e == null)
-			return [];
-		// https://v8.dev/docs/stack-trace-api
-		var oldValue = V8Error.prepareStackTrace;
-		V8Error.prepareStackTrace = function(error, callsites) {
-			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.substr(0, idx);
-						var methodName = fullName.substr(idx + 1);
-						method = Method(className, methodName);
-					}
-				}
-				var fileName = site.getFileName();
-				var fileAddr = fileName == null ? -1 : fileName.indexOf("file:");
-				if (wrapCallSite != null && fileAddr > 0)
-					fileName = fileName.substr(fileAddr + 6);
-				stack.push(FilePos(method, fileName, site.getLineNumber(), site.getColumnNumber()));
-			}
-			return stack;
-		}
-		var a = makeStack(e.stack);
-		V8Error.prepareStackTrace = oldValue;
-		return a;
-	}
-
-	// support for source-map-support module
-	@:noCompletion
-	public static var wrapCallSite:V8CallSite->V8CallSite;
-	#end
-
-	#if eval
-	static function getCallStack() {
-		return [];
-	}
-
-	static function getExceptionStack() {
-		return [];
-	}
-	#end
+@:allow(haxe.Exception)
+@:using(haxe.CallStack)
+abstract CallStack(Array<StackItem>) from Array<StackItem> {
+	/**
+		The length of this stack.
+	**/
+	public var length(get,never):Int;
+	inline function get_length():Int return this.length;
 
 	/**
 		Return the call stack elements, or an empty array if not available.
 	**/
 	public static function callStack():Array<StackItem> {
-		#if neko
-		var a = makeStack(untyped __dollar__callstack());
-		a.shift(); // remove Stack.callStack()
-		return a;
-		#elseif flash
-		var a = makeStack(new flash.errors.Error().getStackTrace());
-		a.shift(); // remove Stack.callStack()
-		return a;
-		#elseif cpp
-		var s:Array<String> = untyped __global__.__hxcpp_get_call_stack(true);
-		return makeStack(s);
-		#elseif js
-		try {
-			throw new js.lib.Error();
-		} catch (e:Dynamic) {
-			var a = getStack(js.Lib.getOriginalException());
-			a.shift(); // remove Stack.callStack()
-			return a;
-		}
-		#elseif java
-		var stack = [];
-		for (el in java.lang.Thread.currentThread().getStackTrace()) {
-			var className = el.getClassName();
-			var methodName = el.getMethodName();
-			var fileName = el.getFileName();
-			var lineNumber = el.getLineNumber();
-			var method = Method(className, methodName);
-			if (fileName != null || lineNumber >= 0) {
-				stack.push(FilePos(method, fileName, lineNumber));
-			} else {
-				stack.push(method);
-			}
-		}
-		stack.shift();
-		stack.shift();
-		stack.pop();
-		return stack;
-		#elseif cs
-		return makeStack(new cs.system.diagnostics.StackTrace(1, true));
-		#elseif python
-		var stack = [];
-		var infos = python.lib.Traceback.extract_stack();
-		infos.pop();
-		infos.reverse();
-		for (elem in infos)
-			stack.push(FilePos(Method(null, elem._3), elem._1, elem._2));
-		return stack;
-		#elseif lua
-		var stack = [];
-		var infos = lua.Debug.traceback();
-		var luastack = infos.split("\n").slice(2, -1);
-		for (s in luastack) {
-			var parts = s.split(":");
-			var file = parts[0];
-			var line = parts[1];
-			var method = if(parts.length <= 2) {
-				null;
-			} else {
-				var methodPos = parts[2].indexOf("'");
-				if(methodPos < 0) {
-					null;
-				} else {
-					Method(null, parts[2].substring(methodPos + 1, parts[2].length - 1));
-				}
-			}
-			stack.push(FilePos(method, file, Std.parseInt(line)));
-		}
-		return stack;
-		#elseif hl
-		try {
-			throw null;
-		} catch (e:Dynamic) {
-			var st = _getExceptionStack();
-			return makeStack(st.length > 2 ? st.sub(2, st.length - 2) : st);
-		}
-		#elseif eval
-		return getCallStack();
-		#else
-		return []; // Unsupported
-		#end
-	}
-
-	#if hl
-	@:hlNative("std", "exception_stack") static function _getExceptionStack():hl.NativeArray<hl.Bytes> {
-		return null;
+		return NativeStackTrace.toHaxe(NativeStackTrace.callStack());
 	}
-	#end
 
 	/**
 		Return the exception stack : this is the stack elements between
 		the place the last exception was thrown and the place it was
 		caught, or an empty array if not available.
+
+		May not work if catch type was a derivative from `haxe.Exception`.
 	**/
-	#if cpp
-	@:noDebug /* Do not mess up the exception stack */
-	#end
 	public static function exceptionStack():Array<StackItem> {
-		#if neko
-		return makeStack(untyped __dollar__excstack());
-		#elseif hl
-		return makeStack(_getExceptionStack());
-		#elseif flash
-		var err:flash.errors.Error = untyped flash.Boot.lastError;
-		if (err == null)
-			return new Array();
-		var a = makeStack(err.getStackTrace());
-		var c = callStack();
-		var i = c.length - 1;
-		while (i > 0) {
-			if (Std.string(a[a.length - 1]) == Std.string(c[i]))
-				a.pop();
-			else
-				break;
-			i--;
-		}
-		return a;
-		#elseif cpp
-		var s:Array<String> = untyped __global__.__hxcpp_get_exception_stack();
-		return makeStack(s);
-		#elseif java
-		var stack = [];
-		switch (#if jvm jvm.Exception #else java.internal.Exceptions #end.currentException()) {
-			case null:
-			case current:
-				for (el in current.getStackTrace()) {
-					var className = el.getClassName();
-					var methodName = el.getMethodName();
-					var fileName = el.getFileName();
-					var lineNumber = el.getLineNumber();
-					var method = Method(className, methodName);
-					if (fileName != null || lineNumber >= 0) {
-						stack.push(FilePos(method, fileName, lineNumber));
-					} else {
-						stack.push(method);
-					}
-				}
-		}
-		return stack;
-		#elseif cs
-		return cs.internal.Exceptions.exception == null ? [] : makeStack(new cs.system.diagnostics.StackTrace(cs.internal.Exceptions.exception, true));
-		#elseif python
-		var stack = [];
-		var exc = python.lib.Sys.exc_info();
-		if (exc._3 != null) {
-			var infos = python.lib.Traceback.extract_tb(exc._3);
-			infos.reverse();
-			for (elem in infos)
-				stack.push(FilePos(Method(null, elem._3), elem._1, elem._2));
-		}
-		return stack;
-		#elseif js
-		return getStack(lastException);
-		#elseif eval
-		return getExceptionStack();
-		#else
-		return []; // Unsupported
-		#end
+		var eStack:CallStack = NativeStackTrace.toHaxe(NativeStackTrace.exceptionStack());
+		return eStack.subtract(callStack()).asArray();
 	}
 
 	/**
 		Returns a representation of the stack as a printable string.
 	**/
-	public static function toString(stack:Array<StackItem>) {
+	static public function toString(stack:CallStack):String {
 		var b = new StringBuf();
-		for (s in stack) {
-			b.add("\nCalled from ");
+		for (s in stack.asArray()) {
+			b.add('\nCalled from ');
 			itemToString(b, s);
 		}
 		return b.toString();
 	}
 
-	private static function itemToString(b:StringBuf, s) {
+	/**
+		Returns a range of entries of current stack from the beginning to the the
+		common part of this and `stack`.
+	**/
+	public function subtract(stack:CallStack):CallStack {
+		var startIndex = -1;
+		var i = -1;
+		while(++i < this.length) {
+			for(j in 0...stack.length) {
+				if(equalItems(this[i], stack[j])) {
+					if(startIndex < 0) {
+						startIndex = i;
+					}
+					++i;
+					if(i >= this.length) break;
+				} else {
+					startIndex = -1;
+				}
+			}
+			if(startIndex >= 0) break;
+		}
+		return startIndex >= 0 ? this.slice(0, startIndex) : this;
+	}
+
+	/**
+		Make a copy of the stack.
+	**/
+	public inline function copy():CallStack {
+		return this.copy();
+	}
+
+	@:arrayAccess public inline function get(index:Int):StackItem {
+		return this[index];
+	}
+
+	inline function asArray():Array<StackItem> {
+		return this;
+	}
+
+	static function equalItems(item1:Null<StackItem>, item2:Null<StackItem>):Bool {
+		return switch([item1, item2]) {
+			case [null, null]: true;
+			case [CFunction, CFunction]: true;
+			case [Module(m1), Module(m2)]:
+				m1 == m2;
+			case [FilePos(item1, file1, line1, col1), FilePos(item2, file2, line2, col2)]:
+				file1 == file2 && line1 == line2 && col1 == col2 && equalItems(item1, item2);
+			case [Method(class1, method1), Method(class2, method2)]:
+				class1 == class2 && method1 == method2;
+			case [LocalFunction(v1), LocalFunction(v2)]:
+				v1 == v2;
+			case _: false;
+		}
+	}
+
+	static function exceptionToString(e:Exception):String {
+		if(e.previous == null) {
+			return 'Exception: ${e.message}${e.stack}';
+		}
+		var result = '';
+		var e:Null<Exception> = e;
+		var prev:Null<Exception> = null;
+		while(e != null) {
+			if(prev == null) {
+				result = 'Exception: ${e.message}${e.stack}' + result;
+			} else {
+				var prevStack = @:privateAccess e.stack.subtract(prev.stack);
+				result = 'Exception: ${e.message}${prevStack}\n\nNext ' + result;
+			}
+			prev = e;
+			e = e.previous;
+		}
+		return result;
+	}
+
+	static function itemToString(b:StringBuf, s) {
 		switch (s) {
 			case CFunction:
 				b.add("a C function");
@@ -293,136 +181,4 @@ class CallStack {
 				b.add(n);
 		}
 	}
-
-	#if cpp
-	@:noDebug /* Do not mess up the exception stack */
-	#end
-	private static function makeStack(s #if cs:cs.system.diagnostics.StackTrace #elseif hl:hl.NativeArray<hl.Bytes> #else:Dynamic #end) {
-		#if neko
-		var a = new Array();
-		var l = untyped __dollar__asize(s);
-		var i = 0;
-		while (i < l) {
-			var x = s[i++];
-			if (x == null)
-				a.unshift(CFunction);
-			else if (untyped __dollar__typeof(x) == __dollar__tstring)
-				a.unshift(Module(new String(x)));
-			else
-				a.unshift(FilePos(null, new String(untyped x[0]), untyped x[1]));
-		}
-		return a;
-		#elseif flash
-		var a = new Array();
-		var r = ~/at ([^\/]+?)\$?(\/[^\(]+)?\(\)(\[(.*?):([0-9]+)\])?/;
-		var rlambda = ~/^MethodInfo-([0-9]+)$/g;
-		while (r.match(s)) {
-			var cl = r.matched(1).split("::").join(".");
-			var meth = r.matched(2);
-			var item;
-			if (meth == null) {
-				if (rlambda.match(cl))
-					item = LocalFunction(Std.parseInt(rlambda.matched(1)));
-				else
-					item = Method(cl, "new");
-			} else
-				item = Method(cl, meth.substr(1));
-			if (r.matched(3) != null)
-				item = FilePos(item, r.matched(4), Std.parseInt(r.matched(5)));
-			a.push(item);
-			s = r.matchedRight();
-		}
-		return a;
-		#elseif cpp
-		var stack:Array<String> = s;
-		var m = new Array<StackItem>();
-		for (func in stack) {
-			var words = func.split("::");
-			if (words.length == 0)
-				m.push(CFunction)
-			else if (words.length == 2)
-				m.push(Method(words[0], words[1]));
-			else if (words.length == 4)
-				m.push(FilePos(Method(words[0], words[1]), words[2], Std.parseInt(words[3])));
-		}
-		return m;
-		#elseif js
-		if (s == null) {
-			return [];
-		} else if (js.Syntax.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();
-			var m = [];
-			var rie10 = ~/^   at ([A-Za-z0-9_. ]+) \(([^)]+):([0-9]+):([0-9]+)\)$/;
-			for (line in stack) {
-				if (rie10.match(line)) {
-					var path = rie10.matched(1).split(".");
-					var meth = path.pop();
-					var file = rie10.matched(2);
-					var line = Std.parseInt(rie10.matched(3));
-					var column = Std.parseInt(rie10.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 {
-			return cast s;
-		}
-		#elseif cs
-		var stack = [];
-		for (i in 0...s.FrameCount) {
-			var frame = s.GetFrame(i);
-			var m = frame.GetMethod();
-
-			if (m == null) {
-				continue;
-			}
-			var method = StackItem.Method(m.ReflectedType.ToString(), m.Name);
-
-			var fileName = frame.GetFileName();
-			var lineNumber = frame.GetFileLineNumber();
-
-			if (fileName != null || lineNumber >= 0)
-				stack.push(FilePos(method, fileName, lineNumber));
-			else
-				stack.push(method);
-		}
-		return stack;
-		#elseif hl
-		var stack = [];
-		var r = ~/^([A-Za-z0-9.$_]+)\.([~A-Za-z0-9_]+(\.[0-9]+)?)\((.+):([0-9]+)\)$/;
-		var r_fun = ~/^fun\$([0-9]+)\((.+):([0-9]+)\)$/;
-		for (i in 0...s.length - 1) {
-			var str = @:privateAccess String.fromUCS2(s[i]);
-			if (r.match(str))
-				stack.push(FilePos(Method(r.matched(1), r.matched(2)), r.matched(4), Std.parseInt(r.matched(5))));
-			else if (r_fun.match(str))
-				stack.push(FilePos(LocalFunction(Std.parseInt(r_fun.matched(1))), r_fun.matched(2), Std.parseInt(r_fun.matched(3))));
-			else
-				stack.push(Module(str));
-		}
-		return stack;
-		#else
-		return null;
-		#end
-	}
-}
-
-#if js
-// https://v8.dev/docs/stack-trace-api
-@:native("Error")
-private extern class V8Error {
-	static var prepareStackTrace:(error:js.lib.Error, structuredStackTrace:Array<V8CallSite>)->Any;
-}
-
-typedef V8CallSite = {
-	function getFunctionName():String;
-	function getFileName():String;
-	function getLineNumber():Int;
-	function getColumnNumber():Int;
-}
-#end
+}

+ 110 - 0
std/haxe/Exception.hx

@@ -0,0 +1,110 @@
+package haxe;
+
+/**
+	Base class for exceptions.
+
+	If this class (or derivatives) is used to catch an exception, then
+	`haxe.CallStack.exceptionStack()` will not return a stack for the exception
+	caught. Use `haxe.Exception.stack` property instead:
+	```haxe
+	try {
+		throwSomething();
+	} catch(e:Exception) {
+		trace(e.stack);
+	}
+	```
+
+	Custom exceptions should extend this class:
+	```haxe
+	class MyException extends haxe.Exception {}
+	//...
+	throw new MyException('terrible exception');
+	```
+
+	`haxe.Exception` is also a wildcard type to catch any exception:
+	```haxe
+	try {
+		throw 'Catch me!';
+	} catch(e:haxe.Exception) {
+		trace(e.message); // Output: Catch me!
+	}
+	```
+
+	To rethrow an exception just throw it again.
+	Haxe will try to rethrow an original native exception whenever possible.
+	```haxe
+	try {
+		var a:Array<Int> = null;
+		a.push(1); // generates target-specific null-pointer exception
+	} catch(e:haxe.Exception) {
+		throw e; // rethrows native exception instead of haxe.Exception
+	}
+	```
+**/
+extern class Exception {
+	/**
+		Exception message.
+	**/
+	public var message(get,never):String;
+	private function get_message():String;
+
+	/**
+		The call stack at the moment of the exception creation.
+	**/
+	public var stack(get,never):CallStack;
+	private function get_stack():CallStack;
+
+	/**
+		Contains an exception, which was passed to `previous` constructor argument.
+	**/
+	public var previous(get,never):Null<Exception>;
+	private function get_previous():Null<Exception>;
+
+	/**
+		Native exception, which caused this exception.
+	**/
+	public var native(get,never):Any;
+	final private function get_native():Any;
+
+	/**
+		Used internally for wildcard catches like `catch(e:Exception)`.
+	**/
+	static private function caught(value:Any):Exception;
+
+	/**
+		Used internally for wrapping non-throwable values for `throw` expressions.
+	**/
+	static private function thrown(value:Any):Any;
+
+	/**
+		Create a new Exception instance.
+
+		The `native` argument is for internal usage only.
+		There is no need to provide `native` argument manually and no need to keep it
+		upon extending `haxe.Exception` unless you know what you're doing.
+	**/
+	public function new(message:String, ?previous:Exception, ?native:Any):Void;
+
+	/**
+		Extract an originally thrown value.
+
+		Used internally for catching non-native exceptions.
+		Do _not_ override unless you know what you are doing.
+	**/
+	private function unwrap():Any;
+
+	/**
+		Exception description.
+
+		Includes message, stack and the previous exception (if set).
+	**/
+	public function toString():String;
+
+	/**
+		If this field is defined in a target implementation, then a call to this
+		field will be generated automatically in every constructor of derived classes
+		to make exception stacks point to derived constructor invocations instead of
+		`super` calls.
+	**/
+	// @:noCompletion @:ifFeature("haxe.Exception.stack") private function __shiftStack():Void;
+}

+ 15 - 0
std/haxe/NativeStackTrace.hx

@@ -0,0 +1,15 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+extern class NativeStackTrace {
+	static public function saveStack(exception:Any):Void;
+	static public function callStack():Any;
+	static public function exceptionStack():Any;
+	static public function toHaxe(nativeStackTrace:Any, skip:Int = 0):Array<StackItem>;
+}

+ 38 - 0
std/haxe/ValueException.hx

@@ -0,0 +1,38 @@
+package haxe;
+
+/**
+	An exception containing arbitrary value.
+
+	This class is automatically used for throwing values, which don't extend `haxe.Exception`
+	or native exception type.
+	For example:
+	```haxe
+	throw "Terrible error";
+	```
+	will be compiled to
+	```haxe
+	throw new ValueException("Terrible error");
+	```
+**/
+class ValueException extends Exception {
+	/**
+		Thrown value.
+	**/
+	public var value(default,null):Any;
+
+	public function new(value:Any, ?previous:Exception, ?native:Any):Void {
+		super(#if js js.Syntax.code('String({0})', value) #else Std.string(value) #end, previous, native);
+		this.value = value;
+	}
+
+	/**
+		Extract an originally thrown value.
+
+		This method must return the same value on subsequent calls.
+		Used internally for catching non-native exceptions.
+		Do _not_ override unless you know what you are doing.
+	**/
+	override function unwrap():Any {
+		return value;
+	}
+}

+ 79 - 0
std/hl/_std/haxe/Exception.hx

@@ -0,0 +1,79 @@
+package haxe;
+
+@:coreApi
+class Exception {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionMessage:String;
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:hl.NativeArray<hl.Bytes>;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		__exceptionMessage = message;
+		__previousException = previous;
+		if(native != null) {
+			__nativeStack = NativeStackTrace.exceptionStack();
+			__nativeException = native;
+		} else {
+			__nativeStack = NativeStackTrace.callStack();
+			__nativeException = this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return __exceptionMessage;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null: __exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}

+ 58 - 0
std/hl/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,58 @@
+package haxe;
+
+import hl.NativeArray;
+import hl.Bytes;
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	@:hlNative("std", "exception_stack")
+	static public function exceptionStack():NativeArray<Bytes> {
+		return null;
+	}
+
+	//TODO: implement in hashlink like `exceptionStack`
+	static public function callStack():NativeArray<Bytes> {
+		var stack:NativeArray<Bytes> = try {
+			throw new Exception('', null, 'stack');
+		} catch (e:Exception) {
+			exceptionStack();
+		}
+		var skip = 1;
+		for(i in 0...stack.length - 1) {
+			var s = @:privateAccess String.fromUCS2(stack[i]);
+			if(s.indexOf('NativeStackTrace.callStack') < 0) {
+				break;
+			}
+			skip++;
+		}
+		return skip < stack.length ? stack.sub(skip, stack.length - skip) : stack;
+	}
+
+	static public function toHaxe(native:NativeArray<Bytes>, skip:Int = 0):Array<StackItem> {
+		var stack = [];
+		var r = ~/^([A-Za-z0-9.$_]+)\.([~A-Za-z0-9_]+(\.[0-9]+)?)\((.+):([0-9]+)\)$/;
+		var r_fun = ~/^fun\$([0-9]+)\((.+):([0-9]+)\)$/;
+		for (i in 0...native.length - 1) {
+			if(skip > i) {
+				continue;
+			}
+			var str = @:privateAccess String.fromUCS2(native[i]);
+			if (r.match(str))
+				stack.push(FilePos(Method(r.matched(1), r.matched(2)), r.matched(4), Std.parseInt(r.matched(5))));
+			else if (r_fun.match(str))
+				stack.push(FilePos(LocalFunction(Std.parseInt(r_fun.matched(1))), r_fun.matched(2), Std.parseInt(r_fun.matched(3))));
+			else
+				stack.push(Module(str));
+		}
+		return stack;
+	}
+}

+ 0 - 1
std/java/Boot.hx

@@ -22,7 +22,6 @@
 
 package java;
 
-import java.internal.Exceptions;
 import java.internal.Function;
 import java.internal.HxObject;
 import java.internal.Runtime;

+ 0 - 1
std/java/_std/Std.hx

@@ -22,7 +22,6 @@
 
 import java.Boot;
 import java.Lib;
-import java.internal.Exceptions;
 
 @:coreApi @:nativeGen class Std {
 	public static inline function is(v:Dynamic, t:Dynamic):Bool {

+ 108 - 0
std/java/_std/haxe/Exception.hx

@@ -0,0 +1,108 @@
+package haxe;
+
+import java.NativeArray;
+import java.lang.Throwable;
+import java.lang.RuntimeException;
+import java.lang.StackTraceElement;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+@:coreApi
+class Exception extends NativeException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeException:Throwable;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, Throwable)) {
+			return new Exception((value:Throwable).getMessage(), null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			var native = (value:Exception).__nativeException;
+			return Std.isOfType(native, RuntimeException) ? native : value;
+		} else if(Std.isOfType(value, RuntimeException)) {
+			return value;
+		} else if(Std.isOfType(value, Throwable)) {
+			return new Exception((value:Throwable).getMessage(), null, value);
+		} else {
+			var e = new ValueException(value);
+			var stack = e.getStackTrace();
+			if(stack.length > 1) {
+				e.setStackTrace(java.util.Arrays.copyOfRange(stack, 1, stack.length));
+			}
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message, cast previous);
+		__previousException = previous;
+		if(native != null && Std.isOfType(native, Throwable)) {
+			__nativeException = native;
+			setStackTrace(__nativeException.getStackTrace());
+		} else {
+			__nativeException = cast this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	override public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	function get_message():String {
+		return this.getMessage();
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeException.getStackTrace());
+			case s: s;
+		}
+	}
+}
+
+@:dox(hide)
+@:noCompletion
+@:native('java.lang.RuntimeException')
+private extern class NativeException {
+	@:noCompletion private function new(?message:String, ?cause:Throwable):Void;
+
+	@:noCompletion @:skipReflection private function addSuppressed (param1:Throwable):Void;
+	@:noCompletion @:skipReflection private function fillInStackTrace ():Throwable;
+	@:noCompletion @:skipReflection private function getCause ():Throwable;
+	@:noCompletion @:skipReflection private function getLocalizedMessage ():String;
+	@:noCompletion @:skipReflection private function getMessage ():String;
+	@:noCompletion @:skipReflection private function getStackTrace ():NativeArray<StackTraceElement>;
+	@:noCompletion @:skipReflection private function getSuppressed ():NativeArray<Throwable>;
+	@:noCompletion @:skipReflection private function initCause (param1:Throwable):Throwable;
+	@:noCompletion @:skipReflection @:overload private function printStackTrace (param1:PrintWriter):Void;
+	@:noCompletion @:skipReflection @:overload private function printStackTrace ():Void;
+	@:noCompletion @:skipReflection @:overload private function printStackTrace (param1:PrintStream):Void;
+	@:noCompletion @:skipReflection private function setStackTrace (param1:NativeArray<StackTraceElement>):Void;
+	@:noCompletion @:skipReflection private function toString ():String;
+}

+ 55 - 0
std/java/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,55 @@
+package haxe;
+
+import java.NativeArray;
+import java.lang.ThreadLocal;
+import java.lang.Throwable;
+import java.lang.Thread;
+import java.lang.StackTraceElement;
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	static var exception = new ThreadLocal<Throwable>();
+
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(e:Throwable):Void {
+		exception.set(e);
+	}
+
+	static public function callStack():NativeArray<StackTraceElement> {
+		var stack = Thread.currentThread().getStackTrace();
+		return stack.length <= 3 ? stack : java.util.Arrays.copyOfRange(stack, 3, stack.length);
+	}
+
+	static public function exceptionStack():NativeArray<StackTraceElement> {
+		return switch exception.get() {
+			case null: new NativeArray(0);
+			case e: e.getStackTrace();
+		}
+	}
+
+	static public function toHaxe(native:NativeArray<StackTraceElement>, skip:Int = 0):Array<StackItem> {
+		var stack = [];
+		for (i in 0...native.length) {
+			if(skip > i) {
+				continue;
+			}
+			var el = native[i];
+			var className = el.getClassName();
+			var methodName = el.getMethodName();
+			var fileName = el.getFileName();
+			var lineNumber = el.getLineNumber();
+			var method = Method(className, methodName);
+			if (fileName != null || lineNumber >= 0) {
+				stack.push(FilePos(method, fileName, lineNumber));
+			} else {
+				stack.push(method);
+			}
+		}
+		return stack;
+	}
+}

+ 0 - 90
std/java/internal/Exceptions.hx

@@ -1,90 +0,0 @@
-/*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-package java.internal;
-
-import java.lang.Throwable;
-import java.lang.RuntimeException;
-import java.lang.Exception;
-
-@:native("haxe.lang.Exceptions")
-class Exceptions {
-	private static var exception = new java.lang.ThreadLocal<java.lang.Throwable>();
-
-	@:keep private static function setException(exc:Throwable) {
-		exception.set(exc);
-	}
-
-	public static function currentException() {
-		return exception.get();
-	}
-}
-
-@:classCode("public static final long serialVersionUID = 5956463319488556322L;")
-@:nativeGen @:keep @:native("haxe.lang.HaxeException") private class HaxeException extends RuntimeException {
-	private var obj:Dynamic;
-
-	public function new(obj:Dynamic, msg:String, cause:Throwable) {
-		super(msg, cause);
-
-		if (Std.isOfType(obj, HaxeException)) {
-			var _obj:HaxeException = cast obj;
-			obj = _obj.getObject();
-		}
-
-		this.obj = obj;
-	}
-
-	public function getObject():Dynamic {
-		return obj;
-	}
-
-	#if !debug
-	@:overload override public function fillInStackTrace():Throwable {
-		return this;
-	}
-	#end
-
-	@:overload override public function toString():String {
-		return "Haxe Exception: " + obj;
-	}
-
-	@:overload override public function getMessage():String {
-		return switch (super.getMessage()) {
-			case null: Std.string(obj);
-			case var message: message;
-		}
-	}
-
-	public static function wrap(obj:Dynamic):RuntimeException {
-		var ret:RuntimeException = null;
-		if (Std.isOfType(obj, RuntimeException))
-			ret = obj;
-		else if (Std.isOfType(obj, String))
-			ret = new HaxeException(obj, obj, null);
-		else if (Std.isOfType(obj, Throwable))
-			ret = new HaxeException(obj, Std.string(obj), obj);
-		else
-			ret = new HaxeException(obj, Std.string(obj), null);
-		return ret;
-	}
-}

+ 5 - 5
std/java/internal/Runtime.hx

@@ -294,7 +294,7 @@ package java.internal;
 		}
 
 		if (throwErrors)
-			throw HaxeException.wrap(t);
+			throw (java.lang.RuntimeException)haxe.Exception.thrown(t);
 
 		return null;
 	}
@@ -331,7 +331,7 @@ package java.internal;
 		}
 		catch (Throwable t)
 		{
-			throw HaxeException.wrap(t);
+			throw (java.lang.RuntimeException)haxe.Exception.thrown(t);
 		}
 	')
 	public static function slowSetField(obj:Dynamic, field:String, value:Dynamic):Dynamic {
@@ -421,7 +421,7 @@ package java.internal;
 
 		java.lang.reflect.Method found;
 		if (ms.length == 0 || (found = ms[0]) == null)
-			throw haxe.lang.HaxeException.wrap("No compatible method found for: " + field);
+			throw (java.lang.RuntimeException)haxe.Exception.thrown("No compatible method found for: " + field);
 
 		if (hasNumber)
 		{
@@ -471,12 +471,12 @@ package java.internal;
 
 		catch (java.lang.reflect.InvocationTargetException e)
 		{
-			throw haxe.lang.HaxeException.wrap(e.getCause());
+			throw (java.lang.RuntimeException)haxe.Exception.thrown(e.getCause());
 		}
 
 		catch (Throwable t)
 		{
-			throw haxe.lang.HaxeException.wrap(t);
+			throw (java.lang.RuntimeException)haxe.Exception.thrown(t);
 		}
 	')
 	public static function slowCallField(obj:Dynamic, field:String, args:java.NativeArray<Dynamic>):Dynamic {

+ 1 - 21
std/js/Boot.hx

@@ -24,26 +24,6 @@ package js;
 
 import js.Syntax; // import it here so it's always available in the compiler
 
-private class HaxeError extends js.lib.Error {
-	var val:Dynamic;
-
-	@:pure
-	public function new(val:Dynamic) {
-		super();
-		this.val = val;
-		if ((cast js.lib.Error).captureStackTrace)
-			(cast js.lib.Error).captureStackTrace(this, HaxeError);
-	}
-
-	public static function wrap(val:Dynamic):js.lib.Error {
-		return if (js.Syntax.instanceof(val, js.lib.Error)) val else new HaxeError(val);
-	}
-
-	static function __init__() {
-		js.Syntax.code("try{Object.defineProperty({0}.prototype, \"message\", {get: function(){return String(this.val)}})}catch(e){}", HaxeError);
-	}
-}
-
 @:dox(hide)
 class Boot {
 	static inline function isClass(o:Dynamic):Bool {
@@ -179,7 +159,7 @@ class Boot {
 		return __interfLoop(cc.__super__, cl);
 	}
 
-	@:ifFeature("typed_catch") @:pure private static function __instanceof(o:Dynamic, cl:Dynamic) {
+	@:pure private static function __instanceof(o:Dynamic, cl:Dynamic) {
 		if (cl == null)
 			return false;
 		switch (cl) {

+ 153 - 0
std/js/_std/haxe/Exception.hx

@@ -0,0 +1,153 @@
+package haxe;
+
+import js.lib.Error;
+
+@:coreApi
+class Exception extends NativeException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:ifFeature("haxe.Exception.get_stack")
+	@:noCompletion var __skipStack:Int;
+	@:noCompletion var __exceptionStack(get,set):Null<CallStack>;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.isOfType(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, Error)) {
+			return new Exception((cast value:Error).message, null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else if(Std.isOfType(value, Error)) {
+			return value;
+		} else {
+			var e = new ValueException(value);
+			untyped __feature__("haxe.Exception.get_stack", e.__shiftStack());
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message);
+		(cast this).message = message;
+		__previousException = previous;
+		__nativeException = native != null ? native : this;
+		untyped __feature__('haxe.Exception.stack', {
+			__skipStack = 0;
+			var old = js.Syntax.code('Error.prepareStackTrace');
+			js.Syntax.code('Error.prepareStackTrace = function(e) { return e.stack; }');
+			if(Std.isOfType(native, Error)) {
+				(cast this).stack = native.stack;
+			} else {
+				var e:Error = null;
+				if ((cast Error).captureStackTrace) {
+					(cast Error).captureStackTrace(this, Exception);
+					e = cast this;
+				} else {
+					e = new Error();
+				}
+				(cast this).stack = e.stack;
+			}
+			js.Syntax.code('Error.prepareStackTrace = {0}', old);
+		});
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return (cast this:Error).message;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(NativeStackTrace.normalize((cast this).stack), __skipStack);
+			case s: s;
+		}
+	}
+
+	function setProperty(name:String, value:Any):Void {
+		try {
+			js.lib.Object.defineProperty(this, name, {value:value});
+		} catch(e:Exception) {
+			js.Syntax.code('{0}[{1}] = {2}', this, name, value);
+		}
+	}
+
+	inline function get___exceptionStack():CallStack {
+		return (cast this).__exceptionStack;
+	}
+
+	inline function set___exceptionStack(value:CallStack):CallStack {
+		setProperty('__exceptionStack', value);
+		return value;
+	}
+
+	inline function get___skipStack():Int {
+		return (cast this).__skipStack;
+	}
+
+	inline function set___skipStack(value:Int):Int {
+		setProperty('__skipStack', value);
+		return value;
+	}
+
+	inline function get___nativeException():Any {
+		return (cast this).__nativeException;
+	}
+
+	inline function set___nativeException(value:Any):Any {
+		setProperty('__nativeException', value);
+		return value;
+	}
+
+	inline function get___previousException():Null<Exception> {
+		return (cast this).__previousException;
+	}
+
+	inline function set___previousException(value:Null<Exception>):Null<Exception> {
+		setProperty('__previousException', value);
+		return value;
+	}
+}
+
+@:dox(hide)
+@:noCompletion
+@:native('Error')
+private extern class NativeException {
+	// private var message:String; //redefined in haxe.Exception
+	@:noCompletion private var name:String;
+	// private var stack(default, null):String; //redefined in haxe.Exception
+
+	private function new(?message:String):Void;
+}

+ 137 - 0
std/js/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,137 @@
+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 {
+		return normalize(tryHaxeStack(new Error()), 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(".");
+					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 - 1);
+					var methodName = fullName.substring(idx + 1);
+					method = Method(className, methodName);
+				}
+			}
+			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);
+		}
+	}
+}

+ 0 - 60
std/jvm/Exception.hx

@@ -1,60 +0,0 @@
-/*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-package jvm;
-
-@:keep
-@:native('haxe.jvm.Exception')
-class Exception<T> extends java.lang.Exception {
-	static public var exception = new java.lang.ThreadLocal<java.lang.Throwable>();
-
-	static public function setException(exc:java.lang.Throwable) {
-		exception.set(exc);
-	}
-
-	static public function currentException() {
-		return exception.get();
-	}
-
-	public var value:T;
-
-	public function new(value:T) {
-		super();
-		this.value = value;
-	}
-
-	@:overload override public function toString() {
-		return Std.string(value);
-	}
-
-	public function unwrap() {
-		return value;
-	}
-
-	static public function wrap<T>(t:Null<T>) {
-		if (Jvm.instanceof(t, java.lang.Exception)) {
-			return (cast t : java.lang.Exception);
-		} else {
-			return new Exception(t);
-		}
-	}
-}

+ 0 - 1
std/jvm/Jvm.hx

@@ -31,7 +31,6 @@ import java.Init;
 import java.NativeArray;
 import jvm.DynamicObject;
 import jvm.EmptyConstructor;
-import jvm.Exception;
 import jvm.Object;
 import jvm.annotation.ClassReflectionInformation;
 import jvm.annotation.EnumReflectionInformation;

+ 81 - 0
std/lua/_std/haxe/Exception.hx

@@ -0,0 +1,81 @@
+package haxe;
+
+@:coreApi
+class Exception {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionMessage:String;
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:Array<String>;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		__exceptionMessage = message;
+		__previousException = previous;
+		if(native != null) {
+			__nativeException = native;
+			__nativeStack = NativeStackTrace.exceptionStack();
+		} else {
+			__nativeException = this;
+			__nativeStack = NativeStackTrace.callStack();
+			__skipStack = 1;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return __exceptionMessage;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}

+ 54 - 0
std/lua/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,54 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	static public function callStack():Array<String> {
+		return switch lua.Debug.traceback() {
+			case null: [];
+			case s: s.split('\n').slice(3);
+		}
+	}
+
+	static public function exceptionStack():Array<String> {
+		return []; //Not implemented. Maybe try xpcal instead of pcal in genlua.
+	}
+
+	static public function toHaxe(native:Array<String>, skip:Int = 0):Array<StackItem> {
+		var stack = [];
+		var cnt = -1;
+		for (item in native) {
+			var parts = item.substr(1).split(":"); //`substr` to skip a tab at the beginning of a line
+			var file = parts[0];
+			if(file == '[C]') {
+				continue;
+			}
+			++cnt;
+			if(skip > cnt) {
+				continue;
+			}
+			var line = parts[1];
+			var method = if(parts.length <= 2) {
+				null;
+			} else {
+				var methodPos = parts[2].indexOf("'");
+				if(methodPos < 0) {
+					null;
+				} else {
+					Method(null, parts[2].substring(methodPos + 1, parts[2].length - 1));
+				}
+			}
+			stack.push(FilePos(method, file, Std.parseInt(line)));
+		}
+		return stack;
+	}
+}

+ 81 - 0
std/neko/_std/haxe/Exception.hx

@@ -0,0 +1,81 @@
+package haxe;
+
+@:coreApi
+class Exception {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionMessage:String;
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:Any;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:Any;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		__exceptionMessage = message;
+		__previousException = previous;
+		if(native != null) {
+			__nativeStack = NativeStackTrace.exceptionStack();
+			__nativeException = native;
+		} else {
+			__nativeStack = NativeStackTrace.callStack();
+			__shiftStack();
+			__nativeException = this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return __exceptionMessage;
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}

+ 51 - 0
std/neko/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,51 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+private typedef NativeTrace = {
+	final skip:Int;
+	final stack:Dynamic;
+}
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	static public inline function callStack():NativeTrace {
+		return { skip:1, stack:untyped __dollar__callstack() };
+	}
+
+	static public function exceptionStack():NativeTrace {
+		return { skip:0, stack:untyped __dollar__excstack() };
+	}
+
+	static public function toHaxe(native:NativeTrace, skip:Int = 0):Array<StackItem> {
+		skip += native.skip;
+		var a = new Array();
+		var l = untyped __dollar__asize(native.stack);
+		var i = 0;
+		while (i < l) {
+			var x = native.stack[l - i - 1];
+			//skip all CFunctions until we skip required amount of hx entries
+			if(x == null && skip > i) {
+				skip++;
+			}
+			if(skip > i++) {
+				continue;
+			}
+			if (x == null)
+				a.push(CFunction);
+			else if (untyped __dollar__typeof(x) == __dollar__tstring)
+				a.push(Module(new String(x)));
+			else
+				a.push(FilePos(null, new String(untyped x[0]), untyped x[1]));
+		}
+		return a;
+	}
+}

+ 2 - 16
std/php/Boot.hx

@@ -315,7 +315,7 @@ class Boot {
 
 	/**
 		Implementation for `cast(value, Class<Dynamic>)`
-		@throws HxException if `value` cannot be casted to this type
+		@throws haxe.ValueError if `value` cannot be casted to this type
 	**/
 	public static function typedCast(hxClass:HxClass, value:Dynamic):Dynamic {
 		if (value == null)
@@ -1014,18 +1014,4 @@ private class HxClosure {
 	public function callWith(newThis:Dynamic, args:NativeArray):Dynamic {
 		return Global.call_user_func_array(getCallback(newThis), args);
 	}
-}
-
-/**
-	Special exception which is used to wrap non-throwable values
-**/
-@:keep
-@:dox(hide)
-private class HxException extends Exception {
-	var e:Dynamic;
-
-	public function new(e:Dynamic):Void {
-		this.e = e;
-		super(Boot.stringify(e));
-	}
-}
+}

+ 1 - 1
std/php/Throwable.hx

@@ -35,4 +35,4 @@ extern interface Throwable {
 	function getTrace():NativeIndexedArray<NativeAssocArray<Dynamic>>; // an array of the backtrace
 	function getTraceAsString():String; // formatted string of trace
 	@:phpMagic function __toString():String; // formatted string for display
-}
+}

+ 0 - 165
std/php/_std/haxe/CallStack.hx

@@ -1,165 +0,0 @@
-/*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-package haxe;
-
-import php.*;
-
-private typedef NativeTrace = NativeIndexedArray<NativeAssocArray<Dynamic>>;
-
-enum StackItem {
-	CFunction;
-	Module(m:String);
-	FilePos(s:Null<StackItem>, file:String, line:Int, ?column:Null<Int>);
-	Method(classname:Null<String>, method:String);
-	LocalFunction(?v:Int);
-}
-
-class CallStack {
-	/**
-		If defined this function will be used to transform call stack entries.
-		@param String - generated php file name.
-		@param Int - Line number in generated file.
-	**/
-	static public var mapPosition:String->Int->Null<{?source:String, ?originalLine:Int}>;
-
-	@:ifFeature("haxe.CallStack.exceptionStack")
-	static var lastExceptionTrace:NativeTrace;
-
-	public static function callStack():Array<StackItem> {
-		return makeStack(Global.debug_backtrace(Const.DEBUG_BACKTRACE_IGNORE_ARGS));
-	}
-
-	public static function exceptionStack():Array<StackItem> {
-		return makeStack(lastExceptionTrace == null ? new NativeIndexedArray() : lastExceptionTrace);
-	}
-
-	public static function toString(stack:Array<StackItem>) {
-		var b = new StringBuf();
-		for (s in stack) {
-			b.add("\nCalled from ");
-			itemToString(b, s);
-		}
-		return b.toString();
-	}
-
-	static function itemToString(b:StringBuf, s) {
-		switch (s) {
-			case CFunction:
-				b.add("a C function");
-			case Module(m):
-				b.add("module ");
-				b.add(m);
-			case FilePos(s, file, line, _):
-				if (s != null) {
-					itemToString(b, s);
-					b.add(" (");
-				}
-				b.add(file);
-				b.add(" line ");
-				b.add(line);
-				if (s != null)
-					b.add(")");
-			case Method(cname, meth):
-				b.add(cname == null ? "<unknown>" : cname);
-				b.add(".");
-				b.add(meth);
-			case LocalFunction(n):
-				b.add("local function");
-		}
-	}
-
-	@:ifFeature("haxe.CallStack.exceptionStack")
-	static function saveExceptionTrace(e:Throwable):Void {
-		lastExceptionTrace = e.getTrace();
-
-		// Reduce exception stack to the place where exception was caught
-		var currentTrace = Global.debug_backtrace(Const.DEBUG_BACKTRACE_IGNORE_ARGS);
-		var count = Global.count(currentTrace);
-
-		for (i in -(count - 1)...1) {
-			var exceptionEntry:NativeAssocArray<Dynamic> = Global.end(lastExceptionTrace);
-
-			if (!Global.isset(exceptionEntry['file']) || !Global.isset(currentTrace[-i]['file'])) {
-				Global.array_pop(lastExceptionTrace);
-			} else if (currentTrace[-i]['file'] == exceptionEntry['file'] && currentTrace[-i]['line'] == exceptionEntry['line']) {
-				Global.array_pop(lastExceptionTrace);
-			} else {
-				break;
-			}
-		}
-
-		// Remove arguments from trace to avoid blocking some objects from GC
-		var count = Global.count(lastExceptionTrace);
-		for (i in 0...count) {
-			lastExceptionTrace[i]['args'] = new NativeArray();
-		}
-
-		var thrownAt = new NativeAssocArray<Dynamic>();
-		thrownAt['function'] = '';
-		thrownAt['line'] = e.getLine();
-		thrownAt['file'] = e.getFile();
-		thrownAt['class'] = '';
-		thrownAt['args'] = new NativeArray();
-		Global.array_unshift(lastExceptionTrace, thrownAt);
-	}
-
-	static function makeStack(native:NativeTrace):Array<StackItem> {
-		var result = [];
-		var count = Global.count(native);
-
-		for (i in 0...count) {
-			var entry = native[i];
-			var item = null;
-
-			if (i + 1 < count) {
-				var next = native[i + 1];
-
-				if (!Global.isset(next['function']))
-					next['function'] = '';
-				if (!Global.isset(next['class']))
-					next['class'] = '';
-
-				if ((next['function'] : String).indexOf('{closure}') >= 0) {
-					item = LocalFunction();
-				} else if (Global.strlen(next['class']) > 0 && Global.strlen(next['function']) > 0) {
-					var cls = Boot.getClassName(next['class']);
-					item = Method(cls, next['function']);
-				}
-			}
-			if (Global.isset(entry['file'])) {
-				if (mapPosition != null) {
-					var pos = mapPosition(entry['file'], entry['line']);
-					if (pos != null && pos.source != null && pos.originalLine != null) {
-						entry['file'] = pos.source;
-						entry['line'] = pos.originalLine;
-					}
-				}
-				result.push(FilePos(item, entry['file'], entry['line']));
-			} else if (item != null) {
-				result.push(item);
-			}
-		}
-
-		return result;
-	}
-}

+ 99 - 0
std/php/_std/haxe/Exception.hx

@@ -0,0 +1,99 @@
+package haxe;
+
+import php.Throwable;
+import php.NativeAssocArray;
+import php.NativeIndexedArray;
+
+@:coreApi
+class Exception extends NativeException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeException:Throwable;
+	@:noCompletion var __skipStack:Int = 0;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, Throwable)) {
+			return new Exception((value:Throwable).getMessage(), null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else if(Std.isOfType(value, Throwable)) {
+			return value;
+		} else {
+			var e = new ValueException(value);
+			e.__skipStack = 1;
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message, 0, previous);
+		this.__previousException = previous;
+		if(native != null && Std.isOfType(native, Throwable)) {
+			__nativeException = native;
+		} else {
+			__nativeException = cast this;
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	function get_message():String {
+		return this.getMessage();
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				var nativeTrace = NativeStackTrace.complementTrace(__nativeException.getTrace(), native);
+				__exceptionStack = NativeStackTrace.toHaxe(nativeTrace, __skipStack);
+			case s: s;
+		}
+	}
+}
+
+@:dox(hide)
+@:noCompletion
+@:native('Exception')
+private extern class NativeException {
+	@:noCompletion private function new(?message:String, ?code:Int, ?previous:NativeException):Void;
+
+	@:noCompletion private var code:Int;
+	@:noCompletion private var file:String;
+	@:noCompletion private var line:Int;
+
+	@:noCompletion final private function getPrevious():Throwable;
+	@:noCompletion private function getMessage():String;
+	@:noCompletion private function getCode():Int;
+	@:noCompletion private function getFile():String;
+	@:noCompletion private function getLine():Int;
+	@:noCompletion private function getTrace():NativeIndexedArray<NativeAssocArray<Dynamic>>;
+	@:noCompletion private function getTraceAsString():String;
+	@:noCompletion @:phpMagic private function __toString():String;
+}

+ 115 - 0
std/php/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,115 @@
+package haxe;
+
+import php.*;
+import haxe.CallStack.StackItem;
+
+private typedef NativeTrace = NativeIndexedArray<NativeAssocArray<Dynamic>>;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+@:allow(haxe.Exception)
+class NativeStackTrace {
+	/**
+		If defined this function will be used to transform call stack entries.
+		@param String - generated php file name.
+		@param Int - Line number in generated file.
+	**/
+	static public var mapPosition:String->Int->Null<{?source:String, ?originalLine:Int}>;
+
+	static var lastExceptionTrace:Null<NativeTrace>;
+
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public function saveStack(e:Throwable) {
+		var nativeTrace = e.getTrace();
+
+		// Reduce exception stack to the place where exception was caught
+		var currentTrace = Global.debug_backtrace(Const.DEBUG_BACKTRACE_IGNORE_ARGS);
+		var count = Global.count(currentTrace);
+
+		for (i in -(count - 1)...1) {
+			var exceptionEntry:NativeAssocArray<Dynamic> = Global.end(nativeTrace);
+
+			if (!Global.isset(exceptionEntry['file']) || !Global.isset(currentTrace[-i]['file'])) {
+				Global.array_pop(nativeTrace);
+			} else if (currentTrace[-i]['file'] == exceptionEntry['file'] && currentTrace[-i]['line'] == exceptionEntry['line']) {
+				Global.array_pop(nativeTrace);
+			} else {
+				break;
+			}
+		}
+
+		// Remove arguments from trace to avoid blocking some objects from GC
+		var count = Global.count(nativeTrace);
+		for (i in 0...count) {
+			nativeTrace[i]['args'] = new NativeArray();
+		}
+
+		lastExceptionTrace = complementTrace(nativeTrace, e);
+	}
+
+	static public inline function callStack():NativeTrace {
+		return Global.debug_backtrace(Const.DEBUG_BACKTRACE_IGNORE_ARGS);
+	}
+
+	static public function exceptionStack():NativeTrace {
+		return lastExceptionTrace == null ? new NativeIndexedArray() : lastExceptionTrace;
+	}
+
+	static public function toHaxe(native:NativeTrace, skip:Int = 0):Array<StackItem> {
+		var result = [];
+		var count = Global.count(native);
+
+		for (i in 0...count) {
+			if(skip > i) {
+				continue;
+			}
+
+			var entry = native[i];
+			var item = null;
+
+			if (i + 1 < count) {
+				var next = native[i + 1];
+
+				if (!Global.isset(next['function']))
+					next['function'] = '';
+				if (!Global.isset(next['class']))
+					next['class'] = '';
+
+				if ((next['function'] : String).indexOf('{closure}') >= 0) {
+					item = LocalFunction();
+				} else if (Global.strlen(next['class']) > 0 && Global.strlen(next['function']) > 0) {
+					var cls = Boot.getClassName(next['class']);
+					item = Method(cls, next['function']);
+				}
+			}
+			if (Global.isset(entry['file'])) {
+				if (mapPosition != null) {
+					var pos = mapPosition(entry['file'], entry['line']);
+					if (pos != null && pos.source != null && pos.originalLine != null) {
+						entry['file'] = pos.source;
+						entry['line'] = pos.originalLine;
+					}
+				}
+				result.push(FilePos(item, entry['file'], entry['line']));
+			} else if (item != null) {
+				result.push(item);
+			}
+		}
+
+		return result;
+	}
+
+	static function complementTrace(nativeTrace:NativeTrace, e:Throwable):NativeTrace {
+		var thrownAt = new NativeAssocArray<Dynamic>();
+		thrownAt['function'] = '';
+		thrownAt['line'] = e.getLine();
+		thrownAt['file'] = e.getFile();
+		thrownAt['class'] = '';
+		thrownAt['args'] = new NativeArray();
+		Global.array_unshift(nativeTrace, thrownAt);
+		return nativeTrace;
+	}
+}

+ 0 - 1
std/python/Boot.hx

@@ -28,7 +28,6 @@ import python.internal.Internal;
 import python.internal.StringImpl;
 import python.internal.EnumImpl;
 import python.internal.HxOverrides;
-import python.internal.HxException;
 import python.internal.AnonObject;
 import python.internal.UBuiltins;
 import python.lib.Inspect;

+ 90 - 0
std/python/_std/haxe/Exception.hx

@@ -0,0 +1,90 @@
+package haxe;
+
+import python.Exceptions.BaseException;
+import python.Exceptions.Exception in PyException;
+import python.lib.Traceback;
+import python.internal.UBuiltins;
+
+private typedef PyStackItem = python.Tuple.Tuple4<String, Int, String, String>;
+
+@:coreApi
+class Exception extends PyException {
+	public var message(get,never):String;
+	public var stack(get,never):CallStack;
+	public var previous(get,never):Null<Exception>;
+	public var native(get,never):Any;
+
+	@:noCompletion var __exceptionStack:Null<CallStack>;
+	@:noCompletion var __nativeStack:Array<PyStackItem>;
+	@:noCompletion @:ifFeature("haxe.Exception.get_stack") var __skipStack:Int = 0;
+	@:noCompletion var __nativeException:BaseException;
+	@:noCompletion var __previousException:Null<Exception>;
+
+	static function caught(value:Any):Exception {
+		if(Std.is(value, Exception)) {
+			return value;
+		} else if(Std.isOfType(value, BaseException)) {
+			return new Exception(UBuiltins.str(value), null, value);
+		} else {
+			return new ValueException(value, null, value);
+		}
+	}
+
+	static function thrown(value:Any):Any {
+		if(Std.isOfType(value, Exception)) {
+			return (value:Exception).native;
+		} else if(Std.isOfType(value, BaseException)) {
+			return value;
+		} else {
+			var e = new ValueException(value);
+			e.__shiftStack();
+			return e;
+		}
+	}
+
+	public function new(message:String, ?previous:Exception, ?native:Any) {
+		super(message);
+		this.__previousException = previous;
+		if(native != null && Std.isOfType(native, BaseException)) {
+			__nativeException = native;
+			__nativeStack = NativeStackTrace.exceptionStack();
+		} else {
+			__nativeException = cast this;
+			__nativeStack = NativeStackTrace.callStack();
+		}
+	}
+
+	function unwrap():Any {
+		return __nativeException;
+	}
+
+	public function toString():String {
+		return inline CallStack.exceptionToString(this);
+	}
+
+	@:noCompletion
+	@:ifFeature("haxe.Exception.get_stack")
+	inline function __shiftStack():Void {
+		__skipStack++;
+	}
+
+	function get_message():String {
+		return UBuiltins.str(this);
+	}
+
+	function get_previous():Null<Exception> {
+		return __previousException;
+	}
+
+	final function get_native():Any {
+		return __nativeException;
+	}
+
+	function get_stack():CallStack {
+		return switch __exceptionStack {
+			case null:
+				__exceptionStack = NativeStackTrace.toHaxe(__nativeStack, __skipStack);
+			case s: s;
+		}
+	}
+}

+ 46 - 0
std/python/_std/haxe/NativeStackTrace.hx

@@ -0,0 +1,46 @@
+package haxe;
+
+import haxe.CallStack.StackItem;
+
+private typedef NativeTrace = Array<python.Tuple.Tuple4<String, Int, String, String>>;
+
+/**
+	Do not use manually.
+**/
+@:dox(hide)
+@:noCompletion
+class NativeStackTrace {
+	@:ifFeature('haxe.NativeStackTrace.exceptionStack')
+	static public inline function saveStack(exception:Any):Void {
+	}
+
+	static public inline function callStack():NativeTrace {
+		var infos = python.lib.Traceback.extract_stack();
+		infos.pop();
+		infos.reverse();
+		return infos;
+	}
+
+	static public function exceptionStack():NativeTrace {
+		var exc = python.lib.Sys.exc_info();
+		if (exc._3 != null) {
+			var infos = python.lib.Traceback.extract_tb(exc._3);
+			infos.reverse();
+			return infos;
+		} else {
+			return [];
+		}
+	}
+
+	static public function toHaxe(native:NativeTrace, skip:Int = 0):Array<StackItem> {
+		var stack = [];
+		for(i in 0...native.length) {
+			if(skip > i) {
+				continue;
+			}
+			var elem = native[i];
+			stack.push(FilePos(Method(null, elem._3), elem._1, elem._2));
+		}
+		return stack;
+	}
+}

+ 0 - 37
std/python/internal/HxException.hx

@@ -1,37 +0,0 @@
-/*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-package python.internal;
-
-@:ifFeature("has_throw")
-@:native("_HxException")
-class HxException extends python.Exceptions.Exception {
-	@:ifFeature("has_throw")
-	public var val:Dynamic;
-
-	@:ifFeature("has_throw")
-	public function new(val:Dynamic) {
-		var message = UBuiltins.str(val);
-		super(message);
-		this.val = val;
-	}
-}

+ 2 - 0
tests/misc/projects/Issue8303/MainCatch.hx

@@ -6,6 +6,8 @@ class MainCatch {
 	static function test() {
 		function log() {
 			log();
+			//prevent tail recursion elimination
+			return 0;
 		}
 		try {
 			log();

+ 1 - 1
tests/misc/projects/Issue8303/compile-fail.hxml

@@ -1,3 +1,3 @@
 -main Main
--D eval-call-stack-depth=5
+-D eval-call-stack-depth=20
 --interp

+ 16 - 1
tests/misc/projects/Issue8303/compile-fail.hxml.stderr

@@ -2,5 +2,20 @@ Uncaught exception Stack overflow
 Main.hx:1: character 1 : Called from here
 Main.hx:8: characters 4-9 : Called from here
 Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
+Main.hx:8: characters 4-9 : Called from here
 Main.hx:10: characters 3-8 : Called from here
-Main.hx:3: characters 3-9 : Called from here
+Main.hx:3: characters 3-9 : Called from here

+ 1 - 1
tests/misc/projects/Issue8303/compile.hxml

@@ -1,3 +1,3 @@
 -main MainCatch
--D eval-call-stack-depth=5
+-D eval-call-stack-depth=20
 --interp

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

@@ -62,7 +62,7 @@ class TestJs {
 		return v + v2;
 	}
 
-	@:js("var a = [];var tmp;try {tmp = a[0];} catch( e ) {((e) instanceof js__$Boot_HaxeError);tmp = null;}tmp;")
+	@:js("var a = [];var tmp;try {tmp = a[0];} catch( _g3 ) {tmp = null;}tmp;")
 	@:analyzer(no_local_dce)
 	static function testInlineWithComplexExpr() {
 		var a = [];
@@ -167,27 +167,27 @@ class TestJs {
 		var vRand = new Inl(Math.random());
 	}
 
-	@:js("try {throw new js__$Boot_HaxeError(false);} catch( e ) {}")
+	@:js("try {throw haxe_Exception.thrown(false);} catch( _g9 ) {}")
 	static function testNoHaxeErrorUnwrappingWhenNotRequired() {
 		try throw false catch (e:Dynamic) {}
 	}
 
-	@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {TestJs.use(((e) instanceof js__$Boot_HaxeError) ? e.val : e);}')
+	@:js('try {throw haxe_Exception.thrown(false);} catch( _g12 ) {TestJs.use(haxe_Exception.caught(_g12).unwrap());}')
 	static function testHaxeErrorUnwrappingWhenUsed() {
 		try throw false catch (e:Dynamic) use(e);
 	}
 
-	@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {if(typeof(((e) instanceof js__$Boot_HaxeError) ? e.val : e) != "boolean") {throw e;}}')
+	@:js('try {throw haxe_Exception.thrown(false);} catch( _g15 ) {if(typeof(haxe_Exception.caught(_g15).unwrap()) != "boolean") {throw _g15;}}')
 	static function testHaxeErrorUnwrappingWhenTypeChecked() {
 		try throw false catch (e:Bool) {};
 	}
 
-	@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {if(typeof(((e) instanceof js__$Boot_HaxeError) ? e.val : e) == "boolean") {TestJs.use(e);} else {throw e;}}')
+	@:js('try {throw haxe_Exception.thrown(false);} catch( _g18 ) {if(typeof(haxe_Exception.caught(_g18).unwrap()) == "boolean") {TestJs.use(_g18);} else {throw _g18;}}')
 	static function testGetOriginalException() {
 		try throw false catch (e:Bool) use(js.Lib.getOriginalException());
 	}
 
-	@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {if(typeof(((e) instanceof js__$Boot_HaxeError) ? e.val : e) == "boolean") {throw e;} else {throw e;}}')
+	@:js('try {throw haxe_Exception.thrown(false);} catch( _g21 ) {if(typeof(haxe_Exception.caught(_g21).unwrap()) == "boolean") {throw _g21;} else {throw _g21;}}')
 	static function testRethrow() {
 		try throw false catch (e:Bool) js.Lib.rethrow();
 	}
@@ -362,7 +362,7 @@ class TestJs {
 	@:js('
 		TestJs.getInt();
 		if(TestJs.getInt() != 0) {
-			throw new js__$Boot_HaxeError("meh");
+			throw haxe_Exception.thrown("meh");
 		}
 	')
 	static function testIfInvert() {

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

@@ -131,10 +131,10 @@ class TestTreGeneration {
 		while(true) {
 			try {
 				if(n <= 0) {
-					throw new js__$Boot_HaxeError("exit");
+					throw haxe_Exception.thrown("exit");
 				}
 				return TestTreGeneration.testTryCancelsTre(n - 1);
-			} catch( e ) {
+			} catch( _g24 ) {
 				if(n == 0) {
 					n -= 1;
 					continue;

+ 2 - 0
tests/runci/targets/Flash.hx

@@ -144,6 +144,8 @@ class Flash {
 				break;
 			}
 		}
+		traceProcess.kill();
+		traceProcess.close();
 		Sys.command("cat", [flashlogPath]);
 		return success;
 	}

+ 336 - 0
tests/unit/src/unit/TestExceptions.hx

@@ -0,0 +1,336 @@
+package unit;
+
+import haxe.Exception;
+import haxe.ValueException;
+import haxe.CallStack;
+import utest.Assert;
+
+private enum EnumError {
+	EError;
+}
+
+private abstract AbstrString(String) from String {}
+private abstract AbstrException(CustomHaxeException) from CustomHaxeException {}
+
+private class CustomHaxeException extends Exception {}
+
+#if php
+private class CustomNativeException extends php.Exception {}
+#elseif js
+private class CustomNativeException extends js.lib.Error {}
+#elseif flash
+private class CustomNativeException extends flash.errors.Error {}
+#elseif java
+private class CustomNativeException extends java.lang.RuntimeException {}
+#elseif cs
+private class CustomNativeException extends cs.system.Exception {}
+#elseif python
+private class CustomNativeException extends python.Exceptions.Exception {}
+#elseif (lua || eval || neko || hl || cpp)
+private class CustomNativeException { public function new(m:String) {} }
+#end
+
+#if java
+private class NativeExceptionBase extends java.lang.RuntimeException {}
+private class NativeExceptionChild extends NativeExceptionBase {}
+private class NativeExceptionOther extends java.lang.RuntimeException {}
+#end
+
+private class NoConstructorValueException extends ValueException {}
+
+private class WithConstructorValueException extends ValueException {
+	public function new(value:Any, ?previous:Exception, ?native:Any) {
+		super(value, previous, native);
+	}
+}
+
+private typedef ItemData = {?file:String, ?line:Int, ?method:String}
+
+class TestExceptions extends Test {
+	/** Had to move to instance var because of https://github.com/HaxeFoundation/haxe/issues/9174 */
+	var rethrown:Bool = false;
+
+	public function testWildCardCatch() {
+		try {
+			throw 123;
+		} catch(e:Dynamic) {
+			eq(123, e);
+		}
+
+		try {
+			throw 123;
+		} catch(e:Exception) {
+			eq('123', e.message);
+			t(Std.isOfType(e, ValueException));
+		}
+	}
+
+	public function testWildCardCatch_rethrow() {
+		var thrown = new CustomHaxeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:Exception) {
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomHaxeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+
+		var thrown = new CustomNativeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:Exception) {
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomNativeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+	}
+
+	public function testSpecificCatch_propagatesUnrelatedExceptions() {
+		var propagated = false;
+		try {
+			try {
+				throw new ValueException('hello');
+			} catch(e:CustomHaxeException) {
+				assert();
+			}
+			assert();
+		} catch(e:ValueException) {
+			propagated = true;
+		}
+		t(propagated);
+	}
+
+	public function testCatchAbstract() {
+		var a:AbstrString = 'hello';
+		try {
+			throw a;
+		} catch(e:AbstrString) {
+			eq(a, e);
+		}
+
+		var a:AbstrException = new CustomHaxeException('');
+		try {
+			throw a;
+		} catch(e:AbstrException) {
+			eq(a, e);
+		}
+	}
+
+	public function testValueException() {
+		try {
+			throw 123;
+		} catch(e:ValueException) {
+			eq(123, e.value);
+		}
+		try {
+			throw 123;
+		} catch(e:Int) {
+			eq(123, e);
+		}
+
+		try {
+			throw EError;
+		} catch(e:ValueException) {
+			eq('EError', e.message);
+		}
+		try {
+			throw EError;
+		} catch(e:EnumError) {
+			eq(EError, e);
+		}
+
+		try {
+			throw 'string';
+		} catch(e:ValueException) {
+			eq('string', e.value);
+		}
+		try {
+			throw 'string';
+		} catch(e:String) {
+			eq('string', e);
+		}
+	}
+
+	public function testCustomNativeException() {
+		var thrown = new CustomNativeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:CustomNativeException) {
+				eq(thrown, e);
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomNativeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+	}
+
+	public function testCustomNativeException_thrownAsDynamic() {
+		var thrown:Any = new CustomNativeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:CustomNativeException) {
+				eq(thrown, e);
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomNativeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+	}
+
+	public function testCustomHaxeException() {
+		var thrown = new CustomHaxeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:CustomHaxeException) {
+				eq(thrown, e);
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomHaxeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+	}
+
+	public function testCustomHaxeException_thrownAsDynamic() {
+		var thrown:Any = new CustomHaxeException('');
+		rethrown = false;
+		try {
+			try {
+				throw thrown;
+			} catch(e:CustomHaxeException) {
+				eq(thrown, e);
+				rethrown = true;
+				throw e;
+			}
+		} catch(e:CustomHaxeException) {
+			eq(thrown, e);
+			t(rethrown);
+		}
+	}
+
+	public function testExceptionStack() {
+		var data = [
+			'_without_ throws' => stacksWithoutThrowLevel1(),
+			'_with_ throws' => stacksWithThrowLevel1()
+		];
+		for(label => stacks in data) {
+			Assert.isTrue(stacks.length > 1, '$label: wrong stacks.length');
+			var expected = null;
+			var lineShift = 0;
+			for(s in stacks) {
+				if(expected == null) {
+					expected = stackItemData(s[0]);
+				} else {
+					var actual = stackItemData(s[0]);
+					if(expected.line != actual.line) {
+						if(lineShift == 0) {
+							lineShift = actual.line - expected.line;
+						}
+						expected.line += lineShift;
+					}
+					Assert.same(expected, actual, '$label: $expected is expected, but got $actual');
+				}
+			}
+		}
+	}
+
+	function stacksWithoutThrowLevel1() {
+		return stacksWithoutThrowLevel2();
+	}
+
+	function stacksWithoutThrowLevel2():Array<CallStack> {
+		var result:Array<CallStack> = [];
+		// It's critical for `testExceptionStack` test to keep the following lines
+		// order with no additional code in between.
+		result.push(CallStack.callStack());
+		result.push(new Exception('').stack);
+		result.push(new ValueException('').stack);
+		result.push(new WithConstructorValueException('').stack);
+		result.push(new NoConstructorValueException('').stack);
+		result.push(@:privateAccess (Exception.thrown(''):Exception).stack);
+		return result;
+	}
+
+	function stacksWithThrowLevel1() {
+		return stacksWithThrowLevel2();
+	}
+
+	function stacksWithThrowLevel2():Array<CallStack> {
+		var result:Array<CallStack> = [];
+		// It's critical for `testExceptionStack` test to keep the following lines
+		// order with no additional code in between.
+		result.push(try throw new Exception('') catch(e:Exception) e.stack);
+		result.push(try throw new ValueException('') catch(e:Exception) e.stack);
+		result.push(try throw new WithConstructorValueException('') catch(e:Exception) e.stack);
+		result.push(try throw new NoConstructorValueException('') catch(e:Exception) e.stack);
+		result.push(try throw @:privateAccess (Exception.thrown(''):Exception) catch(e:Exception) e.stack);
+		return result;
+	}
+
+	function stackItemData(item:StackItem):ItemData {
+		var result:ItemData = {};
+		switch item {
+			case FilePos(s, f, l, _):
+				result.file = f;
+				result.line = l;
+				switch s {
+					case Method(_, m): result.method = m;
+					case _:
+				}
+			case _:
+		}
+		return result;
+	}
+
+#if java
+	function testCatchChain() {
+		eq("caught NativeExceptionChild: msg", raise(() -> throw new NativeExceptionChild("msg")));
+		eq("caught NativeExceptionBase: msg", raise(() -> throw new NativeExceptionBase("msg")));
+		eq("caught String: msg", raise(() -> throw "msg"));
+		eq("caught NativeExceptionOther: msg", raise(() -> throw new NativeExceptionOther("msg")));
+		eq("caught Int: 12", raise(() -> throw 12));
+		eq("caught Throwable: 12.1", raise(() -> throw 12.1));
+		eq("caught Throwable: false", raise(() -> throw false));
+		eq("caught Throwable: msg", raise(() -> throw new java.lang.Exception("msg")));
+	}
+
+	function raise<T>(f:Void -> String) {
+		return try {
+			f();
+		} catch(e:NativeExceptionChild) {
+			'caught NativeExceptionChild: ${e.getMessage()}';
+		} catch(e:NativeExceptionBase) {
+			'caught NativeExceptionBase: ${e.getMessage()}';
+		} catch(e:String) {
+			'caught String: $e';
+		} catch(e:NativeExceptionOther) {
+			'caught NativeExceptionOther: ${e.getMessage()}';
+		} catch(e:Int) {
+			'caught Int: $e';
+ 		} catch(e:java.lang.Throwable) {
+			'caught Throwable: ${e.getMessage()}';
+		}
+	}
+#end
+}

+ 1 - 0
tests/unit/src/unit/TestMain.hx

@@ -50,6 +50,7 @@ class TestMain {
 		var classes = [
 			new TestOps(),
 			new TestBasetypes(),
+			new TestExceptions(),
 			new TestBytes(),
 			new TestIO(),
 			new TestLocals(),

+ 2 - 2
tests/unit/src/unit/issues/Issue4644.hx

@@ -14,9 +14,9 @@ class Issue4644 extends Test {
 			() -> throw (new js.lib.Error():Dynamic),
 			b -> isHaxeError = b,
 			#if js_unflatten
-			js.Syntax.code("js._Boot.HaxeError")
+			js.Syntax.code("haxe.Exception")
 			#else
-			js.Syntax.code("js__$Boot_HaxeError")
+			js.Syntax.code("haxe_Exception")
 			#end
 		);
 		f(isHaxeError);

+ 13 - 4
tests/unit/src/unitstd/haxe/CallStack.unit.hx

@@ -4,17 +4,26 @@ var stack = haxe.CallStack.callStack();
 var stack = haxe.CallStack.exceptionStack();
 (stack is Array) == true;
 
-try {
+function throw2() {
 	throw false;
+}
+function throw1() {
+	throw2();
+}
+try {
+	throw1();
 } catch (_:Dynamic) {
 	var stack = haxe.CallStack.exceptionStack();
 	(stack is Array) == true;
+	#if !lua
+	stack.length > 0;
+	#end
 }
 #if js
-var old = @:privateAccess haxe.CallStack.lastException;
-@:privateAccess haxe.CallStack.lastException = null;
+var old = @:privateAccess haxe.NativeStackTrace.lastError;
+@:privateAccess haxe.NativeStackTrace.lastError = null;
 var stack = haxe.CallStack.exceptionStack();
 (stack is Array) == true;
 stack.length == 0;
-@:privateAccess haxe.CallStack.lastException = old;
+@:privateAccess haxe.NativeStackTrace.lastError = old;
 #end