From 6dc28ca1baebb0483e608e8caf94d8be7d0cf044 Mon Sep 17 00:00:00 2001 From: leftibot Date: Sat, 11 Apr 2026 23:12:37 -0600 Subject: [PATCH] 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) --- include/chaiscript/language/chaiscript_eval.hpp | 6 +++++- .../chaiscript/language/chaiscript_optimizer.hpp | 5 +++-- unittests/assign_no_aliasing.chai | 7 +++++++ unittests/async_return_value.chai | 13 +++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 unittests/assign_no_aliasing.chai create mode 100644 unittests/async_return_value.chai diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 5679ac1d..104e174d 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -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"); 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..554b0e84 --- /dev/null +++ b/unittests/assign_no_aliasing.chai @@ -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) diff --git a/unittests/async_return_value.chai b/unittests/async_return_value.chai new file mode 100644 index 00000000..144b8b4a --- /dev/null +++ b/unittests/async_return_value.chai @@ -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())