Fix #635: Segfault in optimized for-loop when := copies stack-local variable

The optimized for-loop in chaiscript_optimizer.hpp stored the loop counter
as a stack-local int and exposed it to ChaiScript via var(&i), creating a
reference. When the := operator was used inside the loop body (e.g.
ret := i), it copied the raw pointers to this stack variable into the
target's Data. When the function returned via exception unwinding, the
stack frame was destroyed, leaving dangling pointers in the return value.
Accessing these pointers in the caller (e.g. via string interpolation)
caused a segfault. The fix heap-allocates the loop counter via
std::make_shared<int>, so the data survives scope exit through
reference counting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 09:35:51 -06:00
parent 7b45fe19fe
commit f1a0c2ae41
2 changed files with 24 additions and 3 deletions

View File

@ -397,11 +397,11 @@ 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));
auto i = std::make_shared<int>(start_int);
t_ss.add_object(id, var(i));
try {
for (; i < end_int; ++i) {
for (; *i < end_int; ++(*i)) {
try {
// Body of Loop
children[0]->eval(t_ss);

View File

@ -0,0 +1,21 @@
// Regression test for #635: Segfault when using := in async function
// with optimized for loop. The := operator copies raw pointers from the
// loop variable, which is stack-allocated in the optimized for loop.
// When the function returns, the stack is unwound and pointers dangle.
var func = fun(){
var ret = 0;
for (var i = 0; i < 50000; ++i) {
ret := i;
}
return ret;
}
var fut1 = async(func);
var fut2 = async(func);
// This triggers Boxed_Number::get_as<int> on the future results
// which would crash if the pointers are dangling
// := creates an alias, so ret ends up as 50000 (loop exit value of i)
assert_equal(50000, fut1.get())
assert_equal(50000, fut2.get())