From 07d62aae9976528a93f5d6ca716c4fe79a808dc5 Mon Sep 17 00:00:00 2001 From: leftibot Date: Sun, 12 Apr 2026 16:47:06 -0600 Subject: [PATCH] Fix #635: Segfault in async result via dangling pointer from optimized for loop (#671) * Fix #635: Segfault in async result via dangling pointer from optimized for loop * The optimized for loop (chaiscript_optimizer.hpp) stored the loop counter as a stack-local `int` and exposed it to ChaiScript via `var(&i)`, creating a reference-type Boxed_Value pointing to the stack frame. --- CMakeLists.txt | 1 + .../language/chaiscript_optimizer.hpp | 5 ++- unittests/assign_no_aliasing.chai | 41 +++++++++++++++++++ unittests/async_return_value.chai | 25 +++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 unittests/assign_no_aliasing.chai create mode 100644 unittests/async_return_value.chai diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a4b6f55..30924841 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,6 +295,7 @@ list(SORT UNIT_TESTS) if(NOT MULTITHREAD_SUPPORT_ENABLED) list(REMOVE_ITEM UNIT_TESTS async_engine_lifetime.chai + async_return_value.chai future.chai list_push_front.chai move_async.chai diff --git a/include/chaiscript/language/chaiscript_optimizer.hpp b/include/chaiscript/language/chaiscript_optimizer.hpp index e8048eb3..b35dd31b 100644 --- a/include/chaiscript/language/chaiscript_optimizer.hpp +++ b/include/chaiscript/language/chaiscript_optimizer.hpp @@ -397,8 +397,9 @@ namespace chaiscript { assert(children.size() == 1); chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); - int i = start_int; - t_ss.add_object(id, var(&i)); + Boxed_Value bv_i(start_int); + auto &i = *static_cast(bv_i.get_ptr()); + t_ss.add_object(id, bv_i); try { for (; i < end_int; ++i) { diff --git a/unittests/assign_no_aliasing.chai b/unittests/assign_no_aliasing.chai new file mode 100644 index 00000000..0f027600 --- /dev/null +++ b/unittests/assign_no_aliasing.chai @@ -0,0 +1,41 @@ +// Issue #635: optimized for loop with := must not produce dangling values +// := is reference-assign, so ret aliases i and sees the loop exit value + +// Basic: capture loop variable via := +var ret = 0 +for (var i = 0; i < 100; ++i) { + ret := i +} +assert_equal(100, ret) + +// Return from function containing optimized for loop +def loop_result() { + var ret = 0 + for (var i = 0; i < 200; ++i) { + ret := i + } + return ret +} +assert_equal(200, loop_result()) + +// Multiple calls return consistent results +assert_equal(loop_result(), loop_result()) + +// Nested optimized for loops +var outer_val = 0 +var inner_val = 0 +for (var i = 0; i < 10; ++i) { + for (var j = 0; j < 10; ++j) { + inner_val := j + } + outer_val := i +} +assert_equal(10, outer_val) +assert_equal(10, inner_val) + +// Value-assign (=) captures last body-iteration value, not exit value +var ret2 = 0 +for (var i = 0; i < 100; ++i) { + ret2 = i +} +assert_equal(99, ret2) diff --git a/unittests/async_return_value.chai b/unittests/async_return_value.chai new file mode 100644 index 00000000..71bebbc8 --- /dev/null +++ b/unittests/async_return_value.chai @@ -0,0 +1,25 @@ +// Issue #635: async + optimized for loop must not segfault from dangling pointer +// := is reference-assign, so ret aliases i and sees the loop exit value + +var func = fun(){ + var ret = 0; + for (var i = 0; i < 1000; ++i) { + ret := i; + } + return ret; +} + +var&fut1 = async(func); +var fut2 = async(func); + +assert_equal(1000, fut1.get()) +assert_equal(1000, fut2.get()) + +// Multiple concurrent async calls to stress the scenario +var results = [] +for (var n = 0; n < 5; ++n) { + results.push_back(async(func)) +} +for (var n = 0; n < 5; ++n) { + assert_equal(1000, results[n].get()) +}