ChaiScript/unittests/async_lifetime_test.cpp
leftibot c5b81b880f Fix #632: Join async threads before engine destruction to prevent use-after-free
The heap-use-after-free was caused by a race condition where async threads
continued accessing the engine's shared state (global objects map) after the
main thread began destroying the Dispatch_Engine. The fix moves the async
function registration from the stdlib module into ChaiScript_Basic, where it
can track spawned threads via Dispatch_Engine. The engine's destructor now
joins all outstanding async threads before destroying shared data structures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:24:10 -06:00

43 lines
1.2 KiB
C++

// Regression test for #632: Heap-use-after-free in async threads during engine shutdown.
// The engine must wait for outstanding async threads before destroying shared state.
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/chaiscript_basic.hpp>
#include <chaiscript/language/chaiscript_parser.hpp>
int main() {
// Test 1: Async threads that are still running when the engine is destroyed.
// Without the fix, this triggers a heap-use-after-free under ASAN/TSAN.
{
chaiscript::ChaiScript chai;
chai.eval(R"(
var func = fun(){
var ret = 0;
for (var i = 0; i < 5000; ++i) {
ret += i;
}
return ret;
}
var fut1 = async(func);
var fut2 = async(func);
)");
// Engine is destroyed here without explicitly waiting for futures.
// The fix ensures the engine joins all async threads before destruction.
}
// Test 2: Verify async still works correctly (results are accessible).
{
chaiscript::ChaiScript chai;
auto result = chai.eval<int>(R"(
var f = async(fun() { return 42; });
f.get();
)");
if (result != 42) {
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}