From 73db4132625b0339f859e59dee3322d313c755d8 Mon Sep 17 00:00:00 2001 From: AutisticLulu Date: Thu, 13 Aug 2026 15:36:20 +0200 Subject: [PATCH] Raise a jitted cppia exception at the boundary back to native code Jitted cppia does not throw. ThrowExpr::genCode writes the value to ctx->exception and jumps to the function epilogue, and jitted code checks the context after every call it makes to other jitted code, so within jitted code the unwind is complete. Nothing did that where jitted code returns to native code. runFunction and runFunctionClosure call compiled(ctx) and never look at ctx->exception, and the compiled branch of CppiaClosure::__run looks only to decide against reading a return value, then answers null() with the exception still set. The caller reads null, and since nothing clears the context, every later call into jitted cppia returns at its first checkException. All three sites now raise it and clear the context, which is the shape TryExpr::runVoid already uses on the interpreted path. The cppia test suite covers it, run with -jit. --- src/hx/cppia/CppiaFunction.cpp | 24 +++++++++++++++++++++++- test/cppia/Client.hx | 11 +++++++++++ test/cppia/cases/TestCommon.hx | 19 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/hx/cppia/CppiaFunction.cpp b/src/hx/cppia/CppiaFunction.cpp index 005977b36..c6e7b829d 100644 --- a/src/hx/cppia/CppiaFunction.cpp +++ b/src/hx/cppia/CppiaFunction.cpp @@ -508,10 +508,18 @@ void ScriptCallable::runFunction(CppiaCtx *ctx) #ifdef CPPIA_JIT if (compiled) { + { AutoFrame frame(ctx); //printf("Running compiled code...\n"); compiled(ctx); //printf("Done.\n"); + } + if (ctx->exception) + { + Dynamic caught = ctx->exception; + ctx->exception = nullptr; + HX_STACK_DO_THROW(caught); + } } else #endif @@ -543,10 +551,18 @@ void ScriptCallable::runFunctionClosure(CppiaCtx *ctx) #ifdef CPPIA_JIT if (compiled) { + { AutoFrame frame(ctx); //printf("Running compiled code...\n"); compiled(ctx); //printf("Done.\n"); + } + if (ctx->exception) + { + Dynamic caught = ctx->exception; + ctx->exception = nullptr; + HX_STACK_DO_THROW(caught); + } } else #endif @@ -747,7 +763,13 @@ class CppiaClosure : public hx::Object AutoFrame frame(ctx); function->compiled(ctx); } - if (!ctx->exception) + if (ctx->exception) + { + Dynamic caught = ctx->exception; + ctx->exception = nullptr; + HX_STACK_DO_THROW(caught); + } + { switch(function->returnType) { diff --git a/test/cppia/Client.hx b/test/cppia/Client.hx index 710373f4b..7a306efb6 100644 --- a/test/cppia/Client.hx +++ b/test/cppia/Client.hx @@ -14,6 +14,17 @@ class ClientFoo implements IFoo { } } +class ClientThrower { + + public static function boom():String { + throw "boom"; + } + + public static function fine():String { + return "still here"; + } +} + class Client { public static var clientBool0 = true; diff --git a/test/cppia/cases/TestCommon.hx b/test/cppia/cases/TestCommon.hx index eaacb5a76..bed28ab71 100644 --- a/test/cppia/cases/TestCommon.hx +++ b/test/cppia/cases/TestCommon.hx @@ -59,6 +59,25 @@ class TestCommon extends Test { Assert.equals(2, Common.callbackSet, 'Bad cppia closure'); } + @:depends(testStatus) + function testThrowReachesTheCaller() { + final cls = Type.resolveClass('ClientThrower'); + + if (Assert.notNull(cls, 'Unable to resolve ClientThrower')) { + var caught:String = null; + + try { + Reflect.callMethod(null, Reflect.field(cls, 'boom'), []); + } catch (e:Dynamic) { + caught = Std.string(e); + } + + Assert.equals('boom', caught, 'The throw did not reach the caller'); + Assert.equals('still here', Std.string(Reflect.callMethod(null, Reflect.field(cls, 'fine'), [])), + 'A later call answered null, so the exception was left on the context'); + } + } + @:depends(testStatus) function testInterfaceCalling() { final obj : IFoo = Type.createInstance(Type.resolveClass('ClientFoo'), []);