From 6cbb2aa7ca92dad49622e09a1286e3b6979777c5 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 17:16:17 +0300 Subject: [PATCH] cgen: fix autofree memory leak on reused assignment (fixes #18408) --- vlib/v/gen/c/assign.v | 32 ++++++++++++++++--- .../c/testdata/autofree_reused.c.must_have | 8 +++-- vlib/v/gen/c/testdata/autofree_reused.out | 1 + vlib/v/gen/c/testdata/autofree_reused.vv | 7 ++++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 8d65df44f..1d6309282 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -627,8 +627,9 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } // Free the old value assigned to this string var (only if it's `str = [new value]` // or `x.str = [new value]` ) - mut af := g.is_autofree && !g.is_builtin_mod && !g.is_autofree_tmp && node.op == .assign - && node.left_types.len == 1 && node.left[0] in [ast.Ident, ast.SelectorExpr] + mut af := g.is_autofree && !g.is_builtin_mod && !g.is_autofree_tmp && node.left_types.len == 1 + && node.left[0] in [ast.Ident, ast.SelectorExpr] && (node.op == .assign + || (node.op == .plus_assign && node.left_types[0] == ast.string_type)) if af && node.right.len == 1 && node.right[0] is ast.CallExpr { call_expr := node.right[0] as ast.CallExpr if call_expr.is_method && call_expr.left is ast.CallExpr { @@ -640,7 +641,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { if af { first_left_type := node.left_types[0] first_left_sym := g.table.sym(node.left_types[0]) - if first_left_type == ast.string_type || first_left_sym.kind == .array { + if first_left_type == ast.string_type + || (node.op == .assign && first_left_sym.kind == .array) { type_to_free = if first_left_type == ast.string_type { 'string' } else { 'array' } mut ok := true left0 := node.left[0] @@ -723,7 +725,10 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { val_type = g.unwrap_generic(resolved_right_type) } } - val := node.right[i] + mut val := node.right[i] + mut str_add_rhs_tmp := '' + mut str_add_rhs_needs_free := false + mut skip_str_add_rhs_clone := false if is_decl && g.cur_concrete_types.len > 0 && val is ast.CallExpr && val.return_type_generic != 0 { mut resolved_val_type := g.resolve_return_type(val).clear_option_and_result() @@ -1587,6 +1592,22 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { is_mut_arg_pointer_rebind = true } if node.op == .plus_assign && unaliased_right_sym.kind == .string { + if g.is_autofree && !g.is_builtin_mod && !g.is_autofree_tmp + && val !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector] { + str_add_rhs_tmp = '_str_add_rhs_${node.pos.pos}_${i}' + g.writeln(g.autofree_tmp_arg_init_stmt('string ${str_add_rhs_tmp} = ', val)) + val = ast.Expr(ast.Ident{ + mod: g.cur_mod.name + name: str_add_rhs_tmp + }) + str_add_rhs_needs_free = true + skip_str_add_rhs_clone = true + defer(fn) { + if str_add_rhs_needs_free { + g.writeln('builtin__string_free(&${str_add_rhs_tmp});') + } + } + } if mut left is ast.IndexExpr { if g.table.sym(left.left_type).kind == .array_fixed { // strs[0] += str2 => `strs[0] = _string__plus(strs[0], str2)` @@ -1901,7 +1922,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { mut cloned := false if g.is_autofree { if right_sym.kind in [.array, .string] && !unwrapped_val_type.has_flag(.shared_f) { - if g.gen_clone_assignment(var_type, val, unwrapped_val_type, false) { + if !skip_str_add_rhs_clone + && g.gen_clone_assignment(var_type, val, unwrapped_val_type, false) { cloned = true } } else if right_sym.info is ast.Interface && var_type != ast.error_type { diff --git a/vlib/v/gen/c/testdata/autofree_reused.c.must_have b/vlib/v/gen/c/testdata/autofree_reused.c.must_have index d609b996e..2c93b61af 100644 --- a/vlib/v/gen/c/testdata/autofree_reused.c.must_have +++ b/vlib/v/gen/c/testdata/autofree_reused.c.must_have @@ -4,6 +4,10 @@ VV_LOC void main__main(void) { string _t1 = Array_u8_str(s); builtin__println(_t1); builtin__string_free(&_t1); ; byteptr bb = ((byteptr)("a")); - string ss = builtin__byteptr_vstring(bb); - builtin__println(ss); +string ss = builtin__byteptr_vstring(bb); +builtin__println(ss); +string _str_add_rhs_ +s = builtin__string__plus(s, _str_add_rhs_ +builtin__string_free(&_str_add_rhs_ +builtin__string_free(&_sref } diff --git a/vlib/v/gen/c/testdata/autofree_reused.out b/vlib/v/gen/c/testdata/autofree_reused.out index 30ec3724e..589902b74 100644 --- a/vlib/v/gen/c/testdata/autofree_reused.out +++ b/vlib/v/gen/c/testdata/autofree_reused.out @@ -1,2 +1,3 @@ [97] a +ab diff --git a/vlib/v/gen/c/testdata/autofree_reused.vv b/vlib/v/gen/c/testdata/autofree_reused.vv index a4593f05d..4689e8349 100644 --- a/vlib/v/gen/c/testdata/autofree_reused.vv +++ b/vlib/v/gen/c/testdata/autofree_reused.vv @@ -1,4 +1,10 @@ // vtest vflags: -autofree +fn use_plus_assign() { + mut s := u8(`a`).ascii_str() + s += u8(`b`).ascii_str() + println(s) +} + fn main() { b := byteptr(c'a') s := unsafe { b.vbytes(1) } @@ -7,4 +13,5 @@ fn main() { bb := byteptr(c'a') ss := unsafe { bb.vstring() } println(ss) + use_plus_assign() } -- 2.39.5