From 4fb50f17139e4a68e888e158aa10f3a06626a425 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 23:49:59 +0300 Subject: [PATCH] cgen: fix `dump` modifying decoded generic linked list when struct is @[heap] (fixes #25731) --- vlib/v/gen/c/assign.v | 38 ++++++++++++-- vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/struct.v | 44 +++++++++++++++-- .../dump_heap_generic_linked_list_test.v | 49 +++++++++++++++++++ 4 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 vlib/v/tests/generics/dump_heap_generic_linked_list_test.v diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 9c7cce041..7dd7ffd9a 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -2004,13 +2004,41 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } else { var_type }.clear_flag(.shared_f) // don't reset the mutex, just change the value - if exp_type.is_any_kind_of_pointer() && val is ast.PrefixExpr - && val.op == .amp && val.right is ast.Ident - && ((val.right as ast.Ident).is_auto_heap() - || g.resolved_ident_is_auto_heap(val.right as ast.Ident)) { + mut use_heap_pointed_ident := false + mut use_raw_auto_heap_ident := false + if val is ast.PrefixExpr && val.op == .amp && val.right is ast.Ident { + right_ident := val.right as ast.Ident + mut resolved_right_type := g.resolved_expr_type(ast.Expr(right_ident), + val.right_type) + if resolved_right_type == 0 { + resolved_right_type = val.right_type + } + resolved_right_type = + g.unwrap_generic(g.recheck_concrete_type(resolved_right_type)) + rhs_sym_ := g.table.final_sym(resolved_right_type) + if rhs_sym_.kind != .function { + right_type_for_compare := + resolved_right_type.clear_flag(.shared_f).clear_flag(.atomic_f) + exp_type_for_compare := + exp_type.clear_flag(.shared_f).clear_flag(.atomic_f) + right_points_to_heap := resolved_right_type.is_ptr() + && g.table.final_sym(resolved_right_type.deref()).is_heap() + use_heap_pointed_ident = resolved_right_type != 0 + && right_points_to_heap + && right_type_for_compare == exp_type_for_compare + right_is_auto_heap := right_ident.is_auto_heap() + || g.resolved_ident_is_auto_heap(right_ident) + use_raw_auto_heap_ident = right_is_auto_heap + && !use_heap_pointed_ident && resolved_right_type != 0 + && !resolved_right_type.is_ptr() + } + } + if use_heap_pointed_ident { + g.expr(ast.Expr((val as ast.PrefixExpr).right)) + } else if use_raw_auto_heap_ident { old_inside_assign_fn_var := g.inside_assign_fn_var g.inside_assign_fn_var = true - g.expr(ast.Expr(val.right)) + g.expr(ast.Expr((val as ast.PrefixExpr).right)) g.inside_assign_fn_var = old_inside_assign_fn_var } else { g.expr_with_cast(val, val_type, exp_type) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 27ef24795..71aa21182 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5464,7 +5464,7 @@ fn (mut g Gen) expr(node_ ast.Expr) { } } mut has_slice_call := false - if !g.is_option_auto_heap && !(g.is_amp && node.right.is_auto_deref_var()) { + if !g.is_option_auto_heap { has_slice_call = node.op == .amp && node.right is ast.IndexExpr && node.right.index is ast.RangeExpr if has_slice_call { diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 6351128b4..f138546a1 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -54,6 +54,11 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } resolved_node_type := g.recheck_concrete_type(base_node_typ) unwrapped_typ := g.unwrap_generic(resolved_node_type) + struct_init_typ := if node.typ.has_flag(.generic) && resolved_node_type != 0 { + resolved_node_type + } else { + node.typ + } mut sym := g.table.final_sym(unwrapped_typ) if sym.kind == .sum_type { if unwrapped_typ.is_ptr() { @@ -81,6 +86,7 @@ fn (mut g Gen) struct_init(node ast.StructInit) { mut aligned := 0 mut is_anon := false mut is_array_fixed_struct_init := false // return T{} where T is fixed array + mut is_ptr_heap_init := false if mut sym.info is ast.Struct { if attr := sym.info.attrs.find_first('aligned') { aligned = if attr.arg == '' { 0 } else { attr.arg.int() } @@ -143,9 +149,27 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } } } - } else if node.typ.is_ptr() { - basetyp := g.styp(node.typ.set_nr_muls(0)) - if is_multiline { + } else if struct_init_typ.is_ptr() { + mut resolved_ptr_type := g.unwrap_generic(g.recheck_concrete_type(struct_init_typ)) + if resolved_ptr_type == 0 { + resolved_ptr_type = struct_init_typ + } + pointee_type := resolved_ptr_type.set_nr_muls(0) + basetyp := g.styp(pointee_type) + pointee_sym := g.table.final_sym(pointee_type) + if pointee_sym.is_heap() { + is_ptr_heap_init = true + if aligned != 0 { + g.write('(${basetyp}*)builtin__memdup_align(&(${basetyp}){') + } else { + g.write_heap_alloc(basetyp, pointee_type) + if is_multiline { + g.writeln('(${basetyp}){') + } else { + g.write('(${basetyp}){') + } + } + } else if is_multiline { g.writeln('&(${basetyp}){') } else { g.write('&(${basetyp}){') @@ -435,7 +459,7 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } } - if !is_array_fixed_struct_init && !is_generic_default { + if !is_array_fixed_struct_init && (!is_generic_default || is_ptr_heap_init) { g.write('}') } if g.is_shared && !g.inside_opt_data && !g.is_arraymap_set { @@ -459,6 +483,18 @@ fn (mut g Gen) struct_init(node ast.StructInit) { g.write_heap_alloc_close(unwrapped_typ) } } + } else if is_ptr_heap_init { + mut resolved_ptr_type := g.unwrap_generic(g.recheck_concrete_type(struct_init_typ)) + if resolved_ptr_type == 0 { + resolved_ptr_type = struct_init_typ + } + pointee_type := resolved_ptr_type.set_nr_muls(0) + basetyp := g.styp(pointee_type) + if aligned != 0 { + g.write(', sizeof(${basetyp}), ${aligned})') + } else { + g.write_heap_alloc_close(pointee_type) + } } } diff --git a/vlib/v/tests/generics/dump_heap_generic_linked_list_test.v b/vlib/v/tests/generics/dump_heap_generic_linked_list_test.v new file mode 100644 index 000000000..e9d1d58b3 --- /dev/null +++ b/vlib/v/tests/generics/dump_heap_generic_linked_list_test.v @@ -0,0 +1,49 @@ +@[heap] +struct DumpHeapGenericLink { + name string + next &DumpHeapGenericLink = unsafe { nil } +} + +type DumpHeapGenericNode = string | map[string]DumpHeapGenericNode + +fn dump_heap_generic_decode_struct[T](mut t T, values map[string]DumpHeapGenericNode) { + $for f in T.fields { + val := values[f.name] or { return } + $if f is string { + if val is string { + t.$(f.name) = val + } + } $else $if f is $pointer { + if f.is_struct && val is map[string]DumpHeapGenericNode { + mut elem := dump_heap_generic_deref_element(&t.$(f.name)) + dump_heap_generic_decode_struct(mut elem, val) + t.$(f.name) = &elem + } + } + } +} + +fn dump_heap_generic_deref_element[T](a &T) T { + return T{} +} + +fn test_dump_does_not_corrupt_heap_generic_linked_list() { + n3 := { + 'name': DumpHeapGenericNode('3') + } + n2 := { + 'name': DumpHeapGenericNode('2') + 'next': n3 + } + n1 := { + 'name': DumpHeapGenericNode('1') + 'next': n2 + } + mut link := DumpHeapGenericLink{} + dump_heap_generic_decode_struct[DumpHeapGenericLink](mut link, n1) + dump(link) + assert link.name == '1' + assert link.next.name == '2' + assert link.next.next.name == '3' + assert link.next.next.next == unsafe { nil } +} -- 2.39.5