From 653c22b15f77cf02c3041c1372b6cbb0090a507f Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 10 Apr 2025 16:20:52 +0100 Subject: [PATCH 1/8] [cppia] Add missing exception checks In interp mode, we need to stop immediately if a jit exception has been thrown. --- src/hx/cppia/Cppia.cpp | 27 ++++++++++++++++----------- src/hx/cppia/Cppia.h | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/hx/cppia/Cppia.cpp b/src/hx/cppia/Cppia.cpp index 8b6a8d8a2..8a11eb7c7 100644 --- a/src/hx/cppia/Cppia.cpp +++ b/src/hx/cppia/Cppia.cpp @@ -1761,6 +1761,7 @@ struct CallHaxe : public CppiaExpr { unsigned char *pointer = ctx->pointer; ctx->pushObject(isStatic ? 0: thisExpr ? thisExpr->runObject(ctx) : ctx->getThis(false)); + BCR_VCHECK; const char *s = function.signature+1; for(int a=0;apushObject( arg->runObject(ctx) ); break; default: ;// huh? } + BCR_VCHECK; } AutoStack a(ctx,pointer); @@ -2165,8 +2167,9 @@ struct CallMemberVTable : public CppiaExpr ExprType getType() { return returnType; } // ScriptCallable **vtable = (ScriptCallable **)thisVal->__GetScriptVTable(); - #define CALL_VTABLE_SETUP \ + #define CALL_VTABLE_SETUP(errorValue) \ hx::Object *thisVal = thisExpr ? thisExpr->runObject(ctx) : ctx->getThis(); \ + BCR_CHECK_RET(errorValue); \ CPPIA_CHECK(thisVal); \ ScriptCallable **vtable = (!isInterfaceCall ? (*(ScriptCallable ***)((char *)thisVal +scriptVTableOffset)) : (ScriptCallable **) thisVal->__GetScriptVTable()); \ unsigned char *pointer = ctx->pointer; \ @@ -2177,29 +2180,29 @@ struct CallMemberVTable : public CppiaExpr void runVoid(CppiaCtx *ctx) { - CALL_VTABLE_SETUP + CALL_VTABLE_SETUP() ctx->runVoid(func); } int runInt(CppiaCtx *ctx) { - CALL_VTABLE_SETUP - return runContextConvertInt(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); + CALL_VTABLE_SETUP(BCRReturn()) + return runContextConvertInt(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); } - + Float runFloat(CppiaCtx *ctx) { - CALL_VTABLE_SETUP - return runContextConvertFloat(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); + CALL_VTABLE_SETUP(BCRReturn()) + return runContextConvertFloat(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); } String runString(CppiaCtx *ctx) { - CALL_VTABLE_SETUP - return runContextConvertString(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); + CALL_VTABLE_SETUP(BCRReturn()) + return runContextConvertString(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); } hx::Object *runObject(CppiaCtx *ctx) { - CALL_VTABLE_SETUP - return runContextConvertObject(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); + CALL_VTABLE_SETUP(BCRReturn()) + return runContextConvertObject(ctx, checkInterfaceReturnType ? func->getReturnType() : returnType, func); } bool isBoolInt() { return boolResult; } @@ -3109,6 +3112,8 @@ struct Call : public CppiaDynamicExpr hx::Object *runObject(CppiaCtx *ctx) { hx::Object *funcVal = func->runObject(ctx); + BCR_CHECK; + CPPIA_CHECK_FUNC(funcVal); int size = args.size(); diff --git a/src/hx/cppia/Cppia.h b/src/hx/cppia/Cppia.h index d29fada5d..ec7bb374c 100644 --- a/src/hx/cppia/Cppia.h +++ b/src/hx/cppia/Cppia.h @@ -842,7 +842,7 @@ struct BCRReturn #define BCR_CHECK if (ctx->breakContReturn || ctx->exception) return BCRReturn(); -#define BCR_CHECK_RET(x) if (ctx->breakContReturn) return x; +#define BCR_CHECK_RET(x) if (ctx->breakContReturn || ctx->exception) return x; #define BCR_VCHECK if (ctx->breakContReturn || ctx->exception) return; From a22d2f1a26fd722f533f672aecd5ee552b698ecc Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 10 Apr 2025 16:41:01 +0100 Subject: [PATCH 2/8] [cppia] Test for method on exception throwing expr --- test/cppia/Client.hx | 14 +++++++++ test/cppia/LocalFunctionExceptions.hx | 42 ++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 65d193a8a..796a62f0e 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -189,6 +189,20 @@ class Client default: } + switch LocalFunctionExceptions.testObjMethodOnReturn() { + case Error(message): + Common.status = 'Failed test for running object method on returned value: ' + message; + return; + default: + } + + switch LocalFunctionExceptions.testClassMethodOnReturn() { + case Error(message): + Common.status = 'Failed test for running class method on returned value: ' + message; + return; + default: + } + // regression test for #926 var x:Dynamic = 3; x *= 5; diff --git a/test/cppia/LocalFunctionExceptions.hx b/test/cppia/LocalFunctionExceptions.hx index 3caae76bc..a5519ec0a 100644 --- a/test/cppia/LocalFunctionExceptions.hx +++ b/test/cppia/LocalFunctionExceptions.hx @@ -3,8 +3,12 @@ enum Status { Error(message:String); } +class DummyClass { + public function run() {} +} + class LocalFunctionExceptions { - static function staticFunction() { + static function staticFunction():Dynamic { throw 'Thrown from static'; } @@ -65,4 +69,40 @@ class LocalFunctionExceptions { return Error("No exception caught"); } + + public static function testObjMethodOnReturn():Status { + function localFunction() { + (staticFunction() : Dynamic).run(); + } + + try { + localFunction(); + } catch (e:String) { + if (e == 'Thrown from static') { + return Ok; + } else { + return Error("Incorrect exception caught from local function call: " + e); + } + } + + return Error("No exception caught"); + } + + public static function testClassMethodOnReturn():Status { + function localFunction() { + (staticFunction() : DummyClass).run(); + } + + try { + localFunction(); + } catch (e:String) { + if (e == 'Thrown from static') { + return Ok; + } else { + return Error("Incorrect exception caught from local function call: " + e); + } + } + + return Error("No exception caught"); + } } From d8f4f8b906b0116ab22a15339b9ddff4fbde72be Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 10 Apr 2025 21:13:51 +0100 Subject: [PATCH 3/8] [cppia] Add test for host class method call This covers the CallHaxe bug --- test/cppia/Client.hx | 7 +++++++ test/cppia/Common.hx | 1 + test/cppia/LocalFunctionExceptions.hx | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 796a62f0e..2eda86a0a 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -203,6 +203,13 @@ class Client default: } + switch LocalFunctionExceptions.testHostClassMethodOnHostReturn() { + case Error(message): + Common.status = 'Failed test for running host class method on returned value: ' + message; + return; + default: + } + // regression test for #926 var x:Dynamic = 3; x *= 5; diff --git a/test/cppia/Common.hx b/test/cppia/Common.hx index 99f80f815..e547881b6 100644 --- a/test/cppia/Common.hx +++ b/test/cppia/Common.hx @@ -8,4 +8,5 @@ class Common public static var callbackSet:Int = 0; public static var callback: Void->Void; + public function dummyMethod() {} } diff --git a/test/cppia/LocalFunctionExceptions.hx b/test/cppia/LocalFunctionExceptions.hx index a5519ec0a..0b6ce174b 100644 --- a/test/cppia/LocalFunctionExceptions.hx +++ b/test/cppia/LocalFunctionExceptions.hx @@ -105,4 +105,22 @@ class LocalFunctionExceptions { return Error("No exception caught"); } + + public static function testHostClassMethodOnHostReturn():Status { + function localFunction() { + (staticFunction() : Common).dummyMethod(); + } + + try { + localFunction(); + } catch (e:String) { + if (e == 'Thrown from static') { + return Ok; + } else { + return Error("Incorrect exception caught from local function call: " + e); + } + } + + return Error("No exception caught"); + } } From f6833f06195f6e2e4f32d2025c95f3a87c490a86 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 10 Apr 2025 21:58:00 +0100 Subject: [PATCH 4/8] [cppia] Fix throw/catch of seg faults in tests The host must be compiled with HXCPP_CATCH_SEGV so that seg faults are handled as hxcpp exceptions. --- test/cppia/LocalFunctionExceptions.hx | 6 +++--- test/cppia/compile-host.hxml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/cppia/LocalFunctionExceptions.hx b/test/cppia/LocalFunctionExceptions.hx index 0b6ce174b..7395daeb6 100644 --- a/test/cppia/LocalFunctionExceptions.hx +++ b/test/cppia/LocalFunctionExceptions.hx @@ -77,7 +77,7 @@ class LocalFunctionExceptions { try { localFunction(); - } catch (e:String) { + } catch (e:Dynamic) { if (e == 'Thrown from static') { return Ok; } else { @@ -95,7 +95,7 @@ class LocalFunctionExceptions { try { localFunction(); - } catch (e:String) { + } catch (e:Dynamic) { if (e == 'Thrown from static') { return Ok; } else { @@ -113,7 +113,7 @@ class LocalFunctionExceptions { try { localFunction(); - } catch (e:String) { + } catch (e:Dynamic) { if (e == 'Thrown from static') { return Ok; } else { diff --git a/test/cppia/compile-host.hxml b/test/cppia/compile-host.hxml index c07d47137..76bf2d790 100644 --- a/test/cppia/compile-host.hxml +++ b/test/cppia/compile-host.hxml @@ -5,3 +5,4 @@ HostExtendedRoot -L utest --dce no --cpp bin +-D HXCPP_CATCH_SEGV From 44f74ecdf55afa618df5de3715439131f6ae8e52 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 24 Feb 2026 02:58:31 +0000 Subject: [PATCH 5/8] [tests] Verify CallHaxe BCR after this check This test verifies that the full BCR check is needed in CallHaxe::run after runObject, otherwise the return is ignored and arguments continue to be evaluated --- test/cppia/Client.hx | 7 +++++++ test/cppia/Common.hx | 6 ++++++ test/cppia/ReturnExpressions.hx | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 test/cppia/ReturnExpressions.hx diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 2eda86a0a..3b8933450 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -210,6 +210,13 @@ class Client default: } + switch ReturnExpressions.testHostThisReturn() { + case Error(message): + Common.status = 'Failed test for host this return stopping argument evaluation: ' + message; + return; + default: + } + // regression test for #926 var x:Dynamic = 3; x *= 5; diff --git a/test/cppia/Common.hx b/test/cppia/Common.hx index e547881b6..8b9884268 100644 --- a/test/cppia/Common.hx +++ b/test/cppia/Common.hx @@ -8,5 +8,11 @@ class Common public static var callbackSet:Int = 0; public static var callback: Void->Void; + public static var count = 0; + public static function incrementCount():Int { + return count++; + } + public function dummyMethod() {} + public function dummyMethodArg(_) {} } diff --git a/test/cppia/ReturnExpressions.hx b/test/cppia/ReturnExpressions.hx new file mode 100644 index 000000000..363ed7fcd --- /dev/null +++ b/test/cppia/ReturnExpressions.hx @@ -0,0 +1,18 @@ +import LocalFunctionExceptions.Status; + +class ReturnExpressions { + // prevent the call from being optimised out due to unconditional return + @:analyzer(ignore) + static function returnAsCallHaxeThis() { + (cast return:Common).dummyMethodArg(Common.incrementCount()); + } + + public static function testHostThisReturn() { + Common.count = 0; + returnAsCallHaxeThis(); + if (Common.count == 0) { + return Ok; + } + return Error("Host method executed after return"); + } +} From cb6b669367be1d769fcaaece6c5c99c6b3f1e6aa Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 24 Feb 2026 04:36:57 +0000 Subject: [PATCH 6/8] [tests] Verify CallHaxe BCR after each arg --- test/cppia/Client.hx | 7 +++++++ test/cppia/Common.hx | 6 ++++++ test/cppia/ReturnExpressions.hx | 14 ++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 3b8933450..760371ed5 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -217,6 +217,13 @@ class Client default: } + switch ReturnExpressions.testHostArgReturn() { + case Error(message): + Common.status = 'Failed test for argument return stopping argument evaluation: ' + message; + return; + default: + } + // regression test for #926 var x:Dynamic = 3; x *= 5; diff --git a/test/cppia/Common.hx b/test/cppia/Common.hx index 8b9884268..1206f3ea4 100644 --- a/test/cppia/Common.hx +++ b/test/cppia/Common.hx @@ -13,6 +13,12 @@ class Common return count++; } + public function new() {} + public function dummyMethod() {} public function dummyMethodArg(_) {} + + public function instanceIncrementCount(_) { + count++; + } } diff --git a/test/cppia/ReturnExpressions.hx b/test/cppia/ReturnExpressions.hx index 363ed7fcd..8012a5fbc 100644 --- a/test/cppia/ReturnExpressions.hx +++ b/test/cppia/ReturnExpressions.hx @@ -15,4 +15,18 @@ class ReturnExpressions { } return Error("Host method executed after return"); } + + @:analyzer(ignore) + static function returnAsCallHaxeArg() { + new Common().instanceIncrementCount(return); + } + + public static function testHostArgReturn() { + Common.count = 0; + returnAsCallHaxeArg(); + if (Common.count == 0) { + return Ok; + } + return Error("Host method executed after return"); + } } From 41afe5ce5b9e0ff3a07f2287b2854fa78d3123df Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 24 Feb 2026 05:32:26 +0000 Subject: [PATCH 7/8] [tests] Verify Call with return as function --- test/cppia/Client.hx | 7 +++++++ test/cppia/ReturnExpressions.hx | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 760371ed5..265d68ce3 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -224,6 +224,13 @@ class Client default: } + switch ReturnExpressions.testFuncReturn() { + case Error(message): + Common.status = 'Failed test for function value return stopping evaluation: ' + message; + return; + default: + } + // regression test for #926 var x:Dynamic = 3; x *= 5; diff --git a/test/cppia/ReturnExpressions.hx b/test/cppia/ReturnExpressions.hx index 8012a5fbc..e8e6788fe 100644 --- a/test/cppia/ReturnExpressions.hx +++ b/test/cppia/ReturnExpressions.hx @@ -29,4 +29,20 @@ class ReturnExpressions { } return Error("Host method executed after return"); } + + @:analyzer(ignore) + static function returnAsFunction() { + // if return is not handled, there is no function to run so this will + // give a null function exception + (cast return : (Int) -> Void)(Common.incrementCount()); + } + + public static function testFuncReturn() { + Common.count = 0; + returnAsFunction(); + if (Common.count == 0) { + return Ok; + } + return Error("Host method executed after return"); + } } From 1141d48c529df73a7bc63f3dc615547780151dad Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Tue, 24 Feb 2026 07:10:27 +0000 Subject: [PATCH 8/8] [tests] Verify CallMemberVTable with this return --- test/cppia/Client.hx | 7 +++++++ test/cppia/ReturnExpressions.hx | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 265d68ce3..710373f4b 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -224,6 +224,13 @@ class Client default: } + switch ReturnExpressions.testClientThisReturn() { + case Error(message): + Common.status = 'Failed test for client this return stopping evaluation: ' + message; + return; + default: + } + switch ReturnExpressions.testFuncReturn() { case Error(message): Common.status = 'Failed test for function value return stopping evaluation: ' + message; diff --git a/test/cppia/ReturnExpressions.hx b/test/cppia/ReturnExpressions.hx index e8e6788fe..fe4badc33 100644 --- a/test/cppia/ReturnExpressions.hx +++ b/test/cppia/ReturnExpressions.hx @@ -45,4 +45,22 @@ class ReturnExpressions { } return Error("Host method executed after return"); } + + function vtableMethod(_) {} + + @:analyzer(ignore) + public static function returnAsClientThis() { + // if return is not handled, there is no instance to run the method with + // so this will give a null function exception + (cast return:ReturnExpressions).vtableMethod(Common.incrementCount()); + } + + public static function testClientThisReturn() { + Common.count = 0; + returnAsClientThis(); + if (Common.count == 0) { + return Ok; + } + return Error("Host method executed after return"); + } }