// testTrace.c -- cross-boundary stack traces. A Lua context exports a function that errors; a // JavaScript context calls it by name. The failure crosses the context/engine boundary, and the // error delivered to the handler carries the boundary tag ("[lua ctx N] ...") -- so a cross-engine // failure is readable without changing the error handler's signature. #define _POSIX_C_SOURCE 200809L #include "calog.h" #include "calogExport.h" #include #include #include #include #define PUMP_LIMIT 4000 static CalogT *calog = NULL; static _Atomic bool ready = false; static _Atomic int32_t errorCount = 0; static char lastMessage[512] = { 0 }; static int32_t testsRun = 0; static int32_t testsFailed = 0; static _Atomic bool reported = false; static void checkImpl(bool condition, const char *message, int32_t line); static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static void onError(uint64_t contextId, const char *message, void *userData); static void pumpUntil(_Atomic bool *flag); #define CHECK(cond, msg) checkImpl((cond), (msg), __LINE__) static void checkImpl(bool condition, const char *message, int32_t line) { testsRun++; if (!condition) { testsFailed++; printf("FAIL testTrace.c:%d %s\n", line, message); } } static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; atomic_store(&ready, true); calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; calogValueNil(result); if (argCount == 1 && args[0].type == calogStringE) { size_t len; len = (size_t)args[0].as.s.length; if (len >= sizeof(lastMessage)) { len = sizeof(lastMessage) - 1; } memcpy(lastMessage, args[0].as.s.bytes, len); lastMessage[len] = '\0'; atomic_store(&reported, true); } return calogOkE; } static void onError(uint64_t contextId, const char *message, void *userData) { (void)contextId; (void)message; (void)userData; atomic_fetch_add(&errorCount, 1); } static void pumpUntil(_Atomic bool *flag) { struct timespec ts = { 0, 500000 }; int32_t i; for (i = 0; i < PUMP_LIMIT; i++) { calogPump(calog); if (atomic_load(flag)) { calogPump(calog); return; } nanosleep(&ts, NULL); } } int main(void) { CalogContextT *lua; CalogContextT *js; _Atomic bool errored = false; int32_t before; calog = calogCreate(); if (calog == NULL) { printf("calog create failed\n"); return 1; } calogSetErrorHandler(calog, onError, NULL); calogExportRegister(calog); calogRegister(calog, "ready", nativeReady, NULL); calogRegister(calog, "report", nativeReport, NULL); // A Lua context exports a function that raises. lua = calogContextOpen(calog, &calogLuaEngine); calogContextEval(lua, "calogExport('boom', function() error('kaboom in lua') end); ready()"); pumpUntil(&ready); CHECK(atomic_load(&ready), "lua context published the export"); // A JavaScript context calls the Lua export by name and catches the failure; the caught error // is the cross-boundary-tagged message. before = atomic_load(&errorCount); js = calogContextOpen(calog, &calogJsEngine); calogContextEval(js, "try { calogCall('boom'); } catch (e) { report(String(e)); }"); pumpUntil(&reported); (void)errored; (void)before; CHECK(atomic_load(&reported), "the cross-engine call failed and JS caught it"); CHECK(strstr(lastMessage, "kaboom in lua") != NULL, "the original engine error survived the crossing"); CHECK(strstr(lastMessage, "lua ctx") != NULL, "the error is tagged with the boundary it unwound through"); calogDestroy(calog); printf("last message: %s\n", lastMessage); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); return testsFailed == 0 ? 0 : 1; }