From 9f160f7679ee713a2486d9f4e2447a3a5e7e4cf6 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 02:06:12 +0300 Subject: [PATCH] all: fix Type/Struct loop causes SIGSEGV (segmentation fault) crash. (fixes #15338) --- vlib/v/ast/table.v | 48 +++++++++++++++---- vlib/v/checker/struct.v | 14 ++++++ .../recursive_struct_alias_embed_err.out | 5 ++ .../tests/recursive_struct_alias_embed_err.vv | 5 ++ vlib/v/parser/struct.v | 2 +- 5 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 vlib/v/checker/tests/recursive_struct_alias_embed_err.out create mode 100644 vlib/v/checker/tests/recursive_struct_alias_embed_err.vv diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 969b51f59..79b1c3665 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -2187,22 +2187,52 @@ pub fn (t &Table) known_type_names() []string { return res } -// has_deep_child_no_ref returns true if type is struct and has any child or nested child with the type of the given name -// the given name consists of module and name (`mod.Name`) -// it doesn't care about children that are references +// has_deep_child_no_ref returns true if type is struct and has any child or nested child with the type of the given name. +// The given name consists of module and name (`mod.Name`). +// It ignores children that are references, including aliases to references. pub fn (t &Table) has_deep_child_no_ref(ts &TypeSymbol, name string) bool { - if ts.info is Struct { - for field in ts.info.fields { - sym := t.sym(field.typ) - if !field.typ.is_ptr() && !field.typ.has_flag(.option) - && (sym.name == name || t.has_deep_child_no_ref(sym, name)) { - return true + mut seen := map[string]bool{} + return t.has_deep_child_no_ref_in_sym(ts, name, mut seen) +} + +fn (t &Table) has_deep_child_no_ref_in_sym(ts &TypeSymbol, name string, mut seen map[string]bool) bool { + if ts.kind == .placeholder || ts.name in seen { + return false + } + seen[ts.name] = true + match ts.info { + Struct { + for field in ts.info.fields { + sym := t.sym(field.typ) + if !field.typ.is_ptr() && !field.typ.has_flag(.option) && (sym.name == name + || (sym.info is Struct && t.has_deep_child_no_ref_in_sym(sym, name, mut seen))) { + return true + } + } + for embed in ts.info.embeds { + if t.has_deep_child_no_ref_in_embed(embed, name, mut seen) { + return true + } } } + else {} } + return false } +fn (t &Table) has_deep_child_no_ref_in_embed(typ Type, name string, mut seen map[string]bool) bool { + unaliased_typ := t.unaliased_type(typ) + if unaliased_typ.is_ptr() || unaliased_typ.has_flag(.option) { + return false + } + sym := t.sym(unaliased_typ) + if sym.name == name { + return true + } + return t.has_deep_child_no_ref_in_sym(sym, name, mut seen) +} + // complete_interface_check does a MxN check for all M interfaces vs all N types, to determine what types implement what interfaces. // It short circuits most checks when an interface can not possibly be implemented by a type. pub fn (mut t Table) complete_interface_check() { diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 1c9dce493..4f6d636eb 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -49,6 +49,20 @@ fn (mut c Checker) struct_decl(mut node ast.StructDecl) { if node.language == .v && !c.is_builtin_mod && !struct_sym.info.is_anon { c.check_valid_pascal_case(node.name, 'struct name', node.pos) } + if node.language == .v { + for embed in node.embeds { + embed_typ := c.table.unaliased_type(embed.typ) + if embed_typ.is_ptr() || embed_typ.has_flag(.option) { + continue + } + embed_sym := c.table.sym(embed_typ) + if embed_sym.name == struct_sym.name + || c.table.has_deep_child_no_ref(embed_sym, struct_sym.name) { + c.error('invalid recursive struct `${node.name}`', node.pos) + break + } + } + } for embed in node.embeds { // gotodef for embedded struct types if c.pref.is_vls && c.pref.linfo.method == .definition { diff --git a/vlib/v/checker/tests/recursive_struct_alias_embed_err.out b/vlib/v/checker/tests/recursive_struct_alias_embed_err.out new file mode 100644 index 000000000..2a2988eb1 --- /dev/null +++ b/vlib/v/checker/tests/recursive_struct_alias_embed_err.out @@ -0,0 +1,5 @@ +vlib/v/checker/tests/recursive_struct_alias_embed_err.vv:1:1: error: invalid recursive struct `PostKeys` + 1 | struct PostKeys { + | ~~~~~~~~~~~~~~~ + 2 | PostKeysType + 3 | } diff --git a/vlib/v/checker/tests/recursive_struct_alias_embed_err.vv b/vlib/v/checker/tests/recursive_struct_alias_embed_err.vv new file mode 100644 index 000000000..1bab4a703 --- /dev/null +++ b/vlib/v/checker/tests/recursive_struct_alias_embed_err.vv @@ -0,0 +1,5 @@ +struct PostKeys { + PostKeysType +} + +type PostKeysType = PostKeys diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index d56696229..90225a354 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -479,7 +479,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { is_pub: is_pub is_builtin: name in ast.builtins } - if p.table.has_deep_child_no_ref(&sym, name) { + if language == .v && p.table.has_deep_child_no_ref(&sym, name) { p.error_with_pos('invalid recursive struct `${orig_name}`', name_pos) return ast.StructDecl{} } -- 2.39.5