fix: track temporary strings from toString and concat for RC cleanup

- toString now stores result in temp variable and registers for RC
- String concatenation stores result and registers for RC
- Immediately decref temporary input strings after concat to avoid leaks
- Add is_rc_temp() helper to identify RC temporary variables

This fixes the memory leak where dynamically created strings from
toString() and string concatenation were not being freed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 15:00:29 -05:00
parent bba94b534d
commit 1ca31fe985

View File

@@ -1748,7 +1748,21 @@ impl CBackend {
let left_is_string = self.is_string_expr(left); let left_is_string = self.is_string_expr(left);
let right_is_string = self.is_string_expr(right); let right_is_string = self.is_string_expr(right);
if left_is_string || right_is_string { if left_is_string || right_is_string {
return Ok(format!("lux_string_concat({}, {})", l, r)); // String concat returns RC-managed string - track it
let temp = format!("_str_concat_{}", self.fresh_name());
self.writeln(&format!("LuxString {} = lux_string_concat({}, {});", temp, l, r));
self.register_rc_var(&temp, "LuxString");
// Also need to decref any temporary strings used as inputs
// (toString results, nested concats)
if self.is_rc_temp(&l) {
self.writeln(&format!("lux_decref_string({});", l));
self.unregister_rc_var(&l);
}
if self.is_rc_temp(&r) {
self.writeln(&format!("lux_decref_string({});", r));
self.unregister_rc_var(&r);
}
return Ok(temp);
} }
} }
@@ -1856,9 +1870,12 @@ impl CBackend {
// Check for built-in functions like toString // Check for built-in functions like toString
if let Expr::Var(ident) = func.as_ref() { if let Expr::Var(ident) = func.as_ref() {
if ident.name == "toString" { if ident.name == "toString" {
// toString converts Int to String // toString converts Int to String - returns RC-managed string
let arg = self.emit_expr(&args[0])?; let arg = self.emit_expr(&args[0])?;
return Ok(format!("lux_int_to_string({})", arg)); let temp = format!("_to_string_{}", self.fresh_name());
self.writeln(&format!("LuxString {} = lux_int_to_string({});", temp, arg));
self.register_rc_var(&temp, "LuxString");
return Ok(temp);
} }
} }
@@ -3293,6 +3310,11 @@ impl CBackend {
self.rc_scopes.iter().any(|scope| scope.iter().any(|var| var.name == name)) self.rc_scopes.iter().any(|scope| scope.iter().any(|var| var.name == name))
} }
/// Check if a value is a temporary RC variable (starts with _ and is tracked)
fn is_rc_temp(&self, name: &str) -> bool {
name.starts_with("_") && self.is_rc_tracked(name)
}
/// Remove a variable from RC tracking (for ownership transfer) /// Remove a variable from RC tracking (for ownership transfer)
fn unregister_rc_var(&mut self, name: &str) { fn unregister_rc_var(&mut self, name: &str) {
for scope in self.rc_scopes.iter_mut() { for scope in self.rc_scopes.iter_mut() {