Fix #635: Segfault in async result via dangling pointer from optimized for loop

Two bugs combined to cause the crash:

1. 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.

2. The `:=` operator's `Data::operator=` performed a shallow clone of
   the Any (copying the reference_wrapper, not the value), so
   `ret := i` made `ret` alias the stack-local `int`. When the
   optimized loop's frame unwound, `ret` held a dangling pointer.

Fix 1: The optimizer now heap-allocates the loop counter via
`Boxed_Value(start_int)` and obtains a reference to the
heap-allocated int for the native C++ loop. The Boxed_Value's
shared_ptr ensures the storage outlives any copies.

Fix 2: The `:=` operator now deep-copies arithmetic values via
`Boxed_Number::clone` instead of the shallow `assign()`, ensuring
value semantics. This also fixes a pre-existing aliasing bug where
`a := b; ++b` would incorrectly mutate `a`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 23:12:37 -06:00
parent 4804fb6e03
commit 6dc28ca1ba
4 changed files with 28 additions and 3 deletions

View File

@ -473,7 +473,11 @@ namespace chaiscript {
}
} else if (this->text == ":=") {
if (params[0].is_undef() || Boxed_Value::type_match(params[0], params[1])) {
params[0].assign(params[1]);
if (params[1].get_type_info().is_arithmetic() && params[0].get_type_info().is_arithmetic()) {
params[0].assign(Boxed_Number::clone(params[1]));
} else {
params[0].assign(params[1]);
}
params[0].reset_return_value();
} else {
throw exception::eval_error("Mismatched types in equation");

View File

@ -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<int *>(bv_i.get_ptr());
t_ss.add_object(id, bv_i);
try {
for (; i < end_int; ++i) {

View File

@ -0,0 +1,7 @@
// Verify that := performs a value copy, not an alias
var a = 10
var b = 20
a := b
++b
assert_equal(20, a)
assert_equal(21, b)

View File

@ -0,0 +1,13 @@
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(999, fut1.get())
assert_equal(999, fut2.get())