Ver Fonte

[lua] Clean up uncaught error handling (#11082)

* [lua] Clean up uncaught error handling

Removes usage of os.exit, which makes it safer and applicable both
to lua scripts embedded in another application and standalone scripts.
This removes the need for the lua-standalone flag.

* [lua] Mark Exception.toString with @:keep

If it is dce'd, we get a mess when lua prints an uncaught error

* [lua] Prevent error handler polluting error stack

* Add tests for #10979

* Prevent test failures on lua 5.1

Lua 5.1 has slightly different error messages and doesn't print custom
error objects.

* Minor fix
tobil4sk há 2 anos atrás
pai
commit
a6aee34f3c

+ 0 - 6
src-json/define.json

@@ -402,12 +402,6 @@
 		"doc": "Enable the jit compiler for lua (version 5.2 only).",
 		"platforms": ["lua"]
 	},
-	{
-		"name": "LuaStandalone",
-		"define": "lua-standalone",
-		"doc": "Print uncaught error before exiting the lua script.",
-		"platforms": ["lua"]
-	},
 	{
 		"name": "LuaVanilla",
 		"define": "lua-vanilla",

+ 16 - 26
src/generators/genlua.ml

@@ -47,7 +47,6 @@ type ctx = {
     mutable separator : bool;
     mutable found_expose : bool;
     mutable lua_jit : bool;
-    mutable lua_standalone : bool;
     mutable lua_vanilla : bool;
     mutable lua_ver : float;
 }
@@ -1884,7 +1883,6 @@ let alloc_ctx com =
         separator = false;
         found_expose = false;
         lua_jit = Common.defined com Define.LuaJit;
-        lua_standalone = Common.defined com Define.LuaStandalone;
         lua_vanilla = Common.defined com Define.LuaVanilla;
         lua_ver = try
                 float_of_string (Common.defined_value com Define.LuaVer)
@@ -2148,36 +2146,28 @@ let generate com =
         print_file (Common.find_file com "lua/_lua/_hx_dyn_add.lua");
     end;
 
-    if ctx.lua_standalone && Option.is_some com.main then begin
-        print_file (Common.find_file com "lua/_lua/_hx_handle_error.lua");
-    end;
+    print_file (Common.find_file com "lua/_lua/_hx_handle_error.lua");
 
     println ctx "_hx_static_init();";
 
     List.iter (generate_enumMeta_fields ctx) com.types;
 
     Option.may (fun e ->
-        let luv_run =
-            (* Runs libuv loop if needed *)
-            mk_lua_code ctx.com.basic "_hx_luv.run()" [] ctx.com.basic.tvoid Globals.null_pos
-        in
-        if ctx.lua_standalone then begin
-            spr ctx "_G.xpcall(";
-                let fn =
-                    {
-                        tf_args = [];
-                        tf_type = com.basic.tvoid;
-                        tf_expr = mk (TBlock [e;luv_run]) com.basic.tvoid e.epos;
-                    }
-                in
-                gen_value ctx { e with eexpr = TFunction fn; etype = TFun ([],com.basic.tvoid) };
-            spr ctx ", _hx_handle_error)";
-        end else begin
-            gen_expr ctx e;
-            newline ctx;
-            gen_expr ctx luv_run;
-        end;
-        newline ctx
+        spr ctx "local success, err = _G.xpcall(";
+            let luv_run =
+                (* Runs libuv loop if needed *)
+                mk_lua_code ctx.com.basic "_hx_luv.run()" [] ctx.com.basic.tvoid Globals.null_pos
+            in
+            let fn =
+                {
+                    tf_args = [];
+                    tf_type = com.basic.tvoid;
+                    tf_expr = mk (TBlock [e;luv_run]) com.basic.tvoid e.epos;
+                }
+            in
+            gen_value ctx { e with eexpr = TFunction fn; etype = TFun ([],com.basic.tvoid) };
+        println ctx ", _hx_handle_error)";
+        println ctx "if not success then _G.error(err) end";
     ) com.main;
 
     if anyExposed then

+ 6 - 10
std/lua/_lua/_hx_handle_error.lua

@@ -1,12 +1,8 @@
 function _hx_handle_error(obj)
-    if obj.value then
-        _G.print("runtime error:\n " .. _hx_tostring(obj.value));
-    else
-        _G.print("runtime error:\n " .. tostring(obj));
-    end
-
-    if _G.debug and _G.debug.traceback then
-        _G.print(_G.debug.traceback());
-    end
-    _G.os.exit(1)
+  local message = tostring(obj)
+  if _G.debug and _G.debug.traceback then
+    -- level 2 to skip _hx_handle_error
+    message = _G.debug.traceback(message, 2)
+  end
+  return setmetatable({}, { __tostring = function() return message end })
 end

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

@@ -49,6 +49,7 @@ class Exception {
 		return __nativeException;
 	}
 
+	@:keep // required for uncaught error handling
 	public function toString():String {
 		return message;
 	}

+ 5 - 0
tests/misc/lua/projects/Issue10979/HaxeException.hx

@@ -0,0 +1,5 @@
+class HaxeException {
+	static function main() {
+		throw "Exception thrown from Haxe";
+	}
+}

+ 6 - 0
tests/misc/lua/projects/Issue10979/NativeError.hx

@@ -0,0 +1,6 @@
+class NativeError {
+	static function main() {
+		final object:Dynamic = null;
+		trace(object.value);
+	}
+}

+ 38 - 0
tests/misc/lua/projects/Issue10979/RunScript.hx

@@ -0,0 +1,38 @@
+using StringTools;
+
+function isLua5_1() {
+	final proc = new sys.io.Process("lua", ["-v"]);
+	final out = proc.stderr.readLine();
+	proc.close();
+	return out.startsWith("Lua 5.1.");
+}
+
+function matchesExpectedMessage(actual:String) {
+	// lua 5.1 doesn't print custom error objects
+	if (actual == "(error object is not a string)") {
+		return true;
+	}
+	return new EReg(Sys.args()[1], "").match(actual);
+}
+
+function main() {
+	final proc = new sys.io.Process("lua", [Sys.args()[0]]);
+
+	// ignore "lua: "
+	final exceptionMessage = proc.stderr.readLine().substr(5);
+
+	final hasExpectedMessage = matchesExpectedMessage(exceptionMessage);
+	// we don't want a bare error without a stack trace
+	final hasStackTrace = try {
+		proc.stderr.readLine().contains('stack traceback');
+	} catch (_:haxe.io.Eof) {
+		false;
+	};
+
+	Sys.println('Error code: ${proc.exitCode()}');
+	Sys.println('Has expected exception message: ${hasExpectedMessage}');
+	// 5.1 interpreter doesn't handle custom objects
+	Sys.println('Has call stack: ${hasStackTrace || isLua5_1()}');
+
+	proc.close();
+}

+ 4 - 0
tests/misc/lua/projects/Issue10979/embedded-native.hxml

@@ -0,0 +1,4 @@
+--lua bin/native-error.lua
+--main NativeError
+
+--cmd lua runner.lua 'bin/native-error' '%./bin/native%-error%.lua:%d+: attempt to index .+'

+ 3 - 0
tests/misc/lua/projects/Issue10979/embedded-native.hxml.stdout

@@ -0,0 +1,3 @@
+Success: false
+Has expected exception message: true
+Has call stack: true

+ 4 - 0
tests/misc/lua/projects/Issue10979/embedded.hxml

@@ -0,0 +1,4 @@
+--lua bin/haxe-exception.lua
+--main HaxeException
+
+--cmd lua runner.lua 'bin/haxe-exception' 'Exception thrown from Haxe'

+ 3 - 0
tests/misc/lua/projects/Issue10979/embedded.hxml.stdout

@@ -0,0 +1,3 @@
+Success: false
+Has expected exception message: true
+Has call stack: true

+ 10 - 0
tests/misc/lua/projects/Issue10979/runner.lua

@@ -0,0 +1,10 @@
+-- error in loaded script should be caught here, instead of exiting everything
+local success, err = pcall(require, arg[1])
+
+local exception_message = string.match(tostring(err), '^[^\n]+')
+local has_expected_message = string.match(exception_message, arg[2]) ~= nil
+local has_stack_trace = string.match(tostring(err), 'stack traceback') ~= nil
+
+print('Success: '..tostring(success))
+print('Has expected exception message: '..tostring(has_expected_message))
+print('Has call stack: '..tostring(has_stack_trace))

+ 7 - 0
tests/misc/lua/projects/Issue10979/standalone-native.hxml

@@ -0,0 +1,7 @@
+--lua bin/native-error.lua
+--main NativeError
+
+--next
+--run RunScript
+bin/native-error.lua
+bin/native-error\.lua:\d+: attempt to index .*

+ 3 - 0
tests/misc/lua/projects/Issue10979/standalone-native.hxml.stdout

@@ -0,0 +1,3 @@
+Error code: 1
+Has expected exception message: true
+Has call stack: true

+ 7 - 0
tests/misc/lua/projects/Issue10979/standalone.hxml

@@ -0,0 +1,7 @@
+--lua bin/haxe-exception.lua
+--main HaxeException
+
+--next
+--run RunScript
+bin/haxe-exception.lua
+Exception thrown from Haxe

+ 3 - 0
tests/misc/lua/projects/Issue10979/standalone.hxml.stdout

@@ -0,0 +1,3 @@
+Error code: 1
+Has expected exception message: true
+Has call stack: true