From 2dd282333f1424050c2615be9fd1a6ba8f1d43e5 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:19 +0300 Subject: [PATCH] cgen: fix dump() cross-reference field printing recursively (fixes #13370) --- vlib/builtin/autostr.v | 32 +++++++++++++++++++ vlib/v/gen/c/auto_str_methods.v | 28 ++++++++++------ vlib/v/gen/js/auto_str_methods.v | 16 +++++++--- .../inout/dump_cross_reference_field.out | 5 +++ .../inout/dump_cross_reference_field.vv | 17 ++++++++++ .../string_interpolation_struct_test.v | 19 +++++++++++ 6 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 vlib/builtin/autostr.v create mode 100644 vlib/v/slow_tests/inout/dump_cross_reference_field.out create mode 100644 vlib/v/slow_tests/inout/dump_cross_reference_field.vv diff --git a/vlib/builtin/autostr.v b/vlib/builtin/autostr.v new file mode 100644 index 000000000..47502cb70 --- /dev/null +++ b/vlib/builtin/autostr.v @@ -0,0 +1,32 @@ +module builtin + +const autostr_type_stack_max_depth = 64 + +__global g_autostr_type_stack = [autostr_type_stack_max_depth]int{} +__global g_autostr_type_stack_len = 0 + +@[markused] +fn autostr_type_in_stack(typ int) bool { + for i := 0; i < g_autostr_type_stack_len; i++ { + if g_autostr_type_stack[i] == typ { + return true + } + } + return false +} + +@[markused] +fn autostr_type_push(typ int) { + if g_autostr_type_stack_len >= autostr_type_stack_max_depth { + return + } + g_autostr_type_stack[g_autostr_type_stack_len] = typ + g_autostr_type_stack_len++ +} + +@[markused] +fn autostr_type_pop() { + if g_autostr_type_stack_len > 0 { + g_autostr_type_stack_len-- + } +} diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index b1f914f40..1c12e3bf8 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -179,7 +179,7 @@ fn (mut g Gen) final_gen_str(typ StrType) { } ast.Struct { g.gen_str_for_struct(sym.info, sym.language, styp, g.table.type_to_str(typ.typ), - str_fn_name) + str_fn_name, sym.idx) } ast.Map { g.gen_str_for_map(sym.info, styp, str_fn_name) @@ -995,7 +995,7 @@ fn (g &Gen) type_to_fmt(typ ast.Type) StrIntpType { return .si_i32 } -fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp string, typ_str string, str_fn_name string) { +fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp string, typ_str string, str_fn_name string, type_idx int) { $if trace_autostr ? { eprintln('> gen_str_for_struct: ${info.parent_type.debug()} | ${styp} | ${str_fn_name}') } @@ -1019,6 +1019,19 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin fn_builder.writeln('}') return } + allow_circular := info.attrs.any(it.name == 'autostr' && it.arg == 'allowrecurse') + if g.pref.hide_auto_str { + fn_builder.writeln('\tstring res = { .str ="str() used with -hide-auto-str", .len=30 };') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') + return + } + if !allow_circular { + fn_builder.writeln('\tif (builtin__autostr_type_in_stack(${type_idx})) {') + fn_builder.writeln('\t\treturn _S("");') + fn_builder.writeln('\t}') + fn_builder.writeln('\tbuiltin__autostr_type_push(${type_idx});') + } fn_builder.writeln('\tstring indents = builtin__string_repeat(_S(" "), indent_count);') @@ -1028,6 +1041,9 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin fn_body_surrounder.builder_write_befores(mut fn_builder) fn_builder << fn_body fn_body_surrounder.builder_write_afters(mut fn_builder) + if !allow_circular { + fn_builder.writeln('\tbuiltin__autostr_type_pop();') + } fn_builder.writeln('\tbuiltin__string_free(&indents);') fn_builder.writeln('\treturn res;') fn_builder.writeln('}') @@ -1041,15 +1057,9 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin } } } - // -hide-auto-str hides potential sensitive struct data from resulting binary files - if g.pref.hide_auto_str { - fn_body.writeln('\tstring res = { .str ="str() used with -hide-auto-str", .len=30 }; return res;') - return - } fn_body.writeln('\tstring res = builtin__str_intp( ${(info.fields.len - field_skips.len) * 4 + 3}, _MOV((StrIntpData[]){') - fn_body.writeln('\t\t{_S("${clean_struct_v_type_name}{\\n"), 0, {.d_c=0}, 0, 0, 0},') + fn_body.writeln('\t\t{_S("${clean_struct_v_type_name}{\\n"), 0, {.d_c=0}},') - allow_circular := info.attrs.any(it.name == 'autostr' && it.arg == 'allowrecurse') mut is_first := true for i, field in info.fields { // Skip `str:skip` fields diff --git a/vlib/v/gen/js/auto_str_methods.v b/vlib/v/gen/js/auto_str_methods.v index 9b7740497..45a47aaaa 100644 --- a/vlib/v/gen/js/auto_str_methods.v +++ b/vlib/v/gen/js/auto_str_methods.v @@ -80,7 +80,7 @@ fn (mut g JsGen) final_gen_str(typ StrType) { g.gen_str_for_fn_type(sym.info, styp, str_fn_name) } ast.Struct { - g.gen_str_for_struct(sym.info, styp, str_fn_name) + g.gen_str_for_struct(sym.info, styp, str_fn_name, sym.idx) } ast.Map { g.gen_str_for_map(sym.info, styp, str_fn_name) @@ -692,7 +692,7 @@ fn (g &JsGen) type_to_fmt(typ ast.Type) StrIntpType { return .si_i32 } -fn (mut g JsGen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { +fn (mut g JsGen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string, type_idx int) { // _str() functions should have a single argument, the indenting ones take 2: g.definitions.writeln('function ${str_fn_name}(it) { return indent_${str_fn_name}(it, 0);}') @@ -717,10 +717,15 @@ fn (mut g JsGen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name st fn_builder.writeln('}') return } + allow_circular := info.attrs.any(it.name == 'autostr' && it.arg == 'allowrecurse') + if !allow_circular { + fn_builder.writeln('\tif (builtin__autostr_type_in_stack(${type_idx})) {') + fn_builder.writeln('\t\treturn new string("")') + fn_builder.writeln('\t}') + fn_builder.writeln('\tbuiltin__autostr_type_push(${type_idx})') + } fn_builder.writeln('\tlet res = /*struct name*/new string("${clean_struct_v_type_name}{\\n")') - - allow_circular := info.attrs.any(it.name == 'autostr' && it.arg == 'allowrecurse') for i, field in info.fields { mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' } mut prefix := '' @@ -784,6 +789,9 @@ fn (mut g JsGen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name st fn_builder.writeln('') } fn_builder.writeln('res.str += "\\n}"') + if !allow_circular { + fn_builder.writeln('\tbuiltin__autostr_type_pop()') + } // fn_builder.writeln('\t\t{new string("\\n"), ${c.si_s_code}, {.d_s=indents}}, {new string("}"), 0, {.d_c=0}},') fn_builder.writeln('\treturn res;') fn_builder.writeln('}') diff --git a/vlib/v/slow_tests/inout/dump_cross_reference_field.out b/vlib/v/slow_tests/inout/dump_cross_reference_field.out new file mode 100644 index 000000000..b96032641 --- /dev/null +++ b/vlib/v/slow_tests/inout/dump_cross_reference_field.out @@ -0,0 +1,5 @@ +[vlib/v/slow_tests/inout/dump_cross_reference_field.vv:16] window: &Window{ + widgets: [Widget{ + parent: & + }] +} diff --git a/vlib/v/slow_tests/inout/dump_cross_reference_field.vv b/vlib/v/slow_tests/inout/dump_cross_reference_field.vv new file mode 100644 index 000000000..dd5021c17 --- /dev/null +++ b/vlib/v/slow_tests/inout/dump_cross_reference_field.vv @@ -0,0 +1,17 @@ +struct Window { +mut: + widgets []Widget +} + +struct Widget { +mut: + parent &Window = unsafe { nil } +} + +fn main() { + mut window := &Window{} + mut widget := &Widget{} + widget.parent = window + window.widgets << widget + dump(window) +} diff --git a/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_struct_test.v b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_struct_test.v index 26aeab997..7e2108dd5 100644 --- a/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_struct_test.v +++ b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_struct_test.v @@ -72,3 +72,22 @@ fn test_heap_circular_elem_auto_str() { s := '${elem}'.replace('\n', '|') assert s == '&Circular{| next: &|}' } + +struct CrossRefWindow { +mut: + widgets []CrossRefWidget +} + +struct CrossRefWidget { +mut: + parent &CrossRefWindow = unsafe { nil } +} + +fn test_cross_reference_field_auto_str() { + mut window := &CrossRefWindow{} + mut widget := &CrossRefWidget{} + widget.parent = window + window.widgets << widget + s := '${window}'.replace('\n', '|') + assert s == '&CrossRefWindow{| widgets: [CrossRefWidget{| parent: &| }]|}' +} -- 2.39.5