From f59d34892a629e4c4f2080f329684633106acc30 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:48:14 +0300 Subject: [PATCH] cgen: fix memory leak when inserting random value into struct reference (fixes #18555) --- vlib/v/gen/c/array.v | 134 +++++++++++++++++- .../builtin_arrays/array_map_ref_it_test.v | 35 +++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/builtin_arrays/array_map_ref_it_test.v diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index a87dba316..f4c786fee 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -872,7 +872,13 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) { i := g.new_tmp_var() g.writeln('for (${ast.int_type_name} ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {') g.indent++ - is_auto_heap := expr is ast.CastExpr && (expr.expr is ast.Ident && expr.expr.is_auto_heap()) + var_name := g.get_array_expr_param_name(mut expr) + g.refresh_array_expr_param_type(expr, var_name, inp_elem_type) + is_auto_heap := g.array_expr_param_needs_indirect_access(expr, var_name) + old_param_auto_heap := g.set_array_expr_param_auto_heap(expr, var_name, is_auto_heap) + defer { + g.set_array_expr_param_auto_heap(expr, var_name, old_param_auto_heap) + } g.write_prepared_var(var_name, inp_elem_type, inp_elem_styp, past.tmp_var, i, left_is_array, is_auto_heap) g.set_current_pos_as_last_stmt_pos() @@ -2420,6 +2426,132 @@ fn (mut g Gen) get_array_expr_param_name(mut expr ast.Expr) string { return if mut expr is ast.LambdaExpr { expr.params[0].name } else { 'it' } } +fn (mut g Gen) array_expr_param_needs_indirect_access(expr ast.Expr, var_name string) bool { + return g.array_expr_param_is_auto_heap(expr, var_name) + || g.array_expr_takes_param_address(expr, var_name) +} + +fn (mut g Gen) array_expr_param_is_auto_heap(expr ast.Expr, var_name string) bool { + if var_name == '' || g.file.scope == unsafe { nil } { + return false + } + mut scope := g.file.scope.innermost(expr.pos().pos) + if scope == unsafe { nil } { + scope = g.file.scope + } + if scope == unsafe { nil } { + return false + } + if v := scope.find_var(var_name) { + return v.is_auto_heap + } + return false +} + +fn (mut g Gen) set_array_expr_param_auto_heap(expr ast.Expr, var_name string, is_auto_heap bool) bool { + if var_name == '' || g.file.scope == unsafe { nil } { + return false + } + mut scope := g.file.scope.innermost(expr.pos().pos) + if scope == unsafe { nil } { + scope = g.file.scope + } + if scope == unsafe { nil } { + return false + } + if mut v := scope.find_var(var_name) { + old_is_auto_heap := v.is_auto_heap + v.is_auto_heap = is_auto_heap + return old_is_auto_heap + } + return false +} + +fn (mut g Gen) array_expr_takes_param_address(expr ast.Expr, var_name string) bool { + match expr { + ast.AsCast { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + ast.CallExpr { + if g.array_expr_takes_param_address(expr.left, var_name) { + return true + } + for arg in expr.args { + if g.array_expr_takes_param_address(arg.expr, var_name) { + return true + } + } + return false + } + ast.CastExpr { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + ast.IndexExpr { + return g.array_expr_takes_param_address(expr.left, var_name) + || g.array_expr_takes_param_address(expr.index, var_name) + } + ast.InfixExpr { + return g.array_expr_takes_param_address(expr.left, var_name) + || g.array_expr_takes_param_address(expr.right, var_name) + } + ast.LambdaExpr { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + ast.ParExpr { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + ast.PostfixExpr { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + ast.PrefixExpr { + if expr.op == .amp && g.array_expr_roots_at_param(expr.right, var_name) { + return true + } + return g.array_expr_takes_param_address(expr.right, var_name) + } + ast.SelectorExpr { + return g.array_expr_takes_param_address(expr.expr, var_name) + } + else { + return false + } + } +} + +fn (mut g Gen) array_expr_roots_at_param(expr ast.Expr, var_name string) bool { + mut root := expr + for { + if mut root is ast.ParExpr { + root = root.expr + continue + } + break + } + match mut root { + ast.AsCast { + return g.array_expr_roots_at_param(root.expr, var_name) + } + ast.CastExpr { + return g.array_expr_roots_at_param(root.expr, var_name) + } + ast.Ident { + return root.name == var_name + } + ast.IndexExpr { + return g.array_expr_roots_at_param(root.left, var_name) + } + ast.PostfixExpr { + return g.array_expr_roots_at_param(root.expr, var_name) + } + ast.SelectorExpr { + return g.array_expr_roots_at_param(root.expr, var_name) + } + else { + return false + } + } +} + fn (mut g Gen) refresh_array_expr_param_type(expr ast.Expr, var_name string, elem_type ast.Type) { if var_name == '' || elem_type == 0 || g.file.scope == unsafe { nil } { return diff --git a/vlib/v/tests/builtin_arrays/array_map_ref_it_test.v b/vlib/v/tests/builtin_arrays/array_map_ref_it_test.v new file mode 100644 index 000000000..661d89c50 --- /dev/null +++ b/vlib/v/tests/builtin_arrays/array_map_ref_it_test.v @@ -0,0 +1,35 @@ +struct MapRefShapes { +mut: + rectangles []MapRefRectangle +} + +struct MapRefRectangle { +mut: + id int + square bool +} + +fn (shapes MapRefShapes) get_squares() []&MapRefRectangle { + return shapes.rectangles.filter(it.square).filter(it.id > 0).map(&it) +} + +fn test_array_map_ref_it_after_filter_keeps_struct_fields() { + mut shapes := MapRefShapes{} + shapes.rectangles << MapRefRectangle{ + id: 1 + square: true + } + shapes.rectangles << MapRefRectangle{ + id: 2 + square: false + } + + squares := shapes.get_squares() + rendered := '${squares}' + + assert squares.len == 1 + assert squares[0].id == 1 + assert squares[0].square + assert rendered.contains('id: 1') + assert rendered.contains('square: true') +} -- 2.39.5