{"record":{"id":"ac8d9d307a6bc085","repo":"cilium/cilium","slug":"recursion-on-type-s","errorCode":null,"errorMessage":"recursion on type %s","messagePattern":"recursion on type (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bpf/analyze/fields.go","lineNumber":47,"sourceCode":"\n// structField represents a struct field containing the ebpf struct tag.\ntype structField struct {\n\treflect.StructField\n\tvalue reflect.Value\n}\n\nfunc ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) (*set.Set[string], error) {\n\tif visited == nil {\n\t\tvisited = make(map[reflect.Type]bool)\n\t}\n\n\tstructType := structVal.Type()\n\tif structType.Kind() != reflect.Struct {\n\t\treturn nil, fmt.Errorf(\"%s is not a struct\", structType)\n\t}\n\n\tif visited[structType] {\n\t\treturn nil, fmt.Errorf(\"recursion on type %s\", structType)\n\t}\n\n\tkeep := set.NewSet[string]()\n\tfor i := 0; i < structType.NumField(); i++ {\n\t\tfield := structField{structType.Field(i), structVal.Field(i)}\n\n\t\t// If the field is tagged, gather it and move on.\n\t\tname := field.Tag.Get(\"ebpf\")\n\t\tif name != \"\" {\n\t\t\tkeep.Insert(name)\n\t\t\tcontinue\n\t\t}\n\n\t\t// If the field does not have an ebpf tag, but is a struct or a pointer\n\t\t// to a struct, attempt to gather its fields as well.\n\t\tvar v reflect.Value\n\t\tswitch field.Type.Kind() {\n\t\tcase reflect.Pointer:","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/bpf/analyze/fields.go#L29-L65","documentation":"ebpfFields tracks visited reflect.Types to prevent infinite recursion when a struct type contains itself (directly or through a cycle of 'ebpf'-tagged pointer fields). Encountering an already-visited type aborts with this recursion error instead of looping forever.","triggerScenarios":"A struct with an 'ebpf'-tagged pointer field pointing to its own type (or a cycle A->B->A); mutual recursion between two tagged struct types.","commonSituations":"Linked-list or tree-like self-referential types accidentally carrying ebpf tags; refactoring that introduces a cycle in tagged nested structs.","solutions":["Remove the 'ebpf' tag from the field that creates the cycle.","Restructure types so tagged fields form a DAG (no cycles).","If recursion is intentional, hoist the nested struct out and call Fields on it separately."],"exampleFix":"// before\ntype Node struct {\n    Next *Node `ebpf:\"next\"`\n}\n\n// after\ntype Node struct {\n    Next *Node // no ebpf tag: breaks the cycle\n}","handlingStrategy":"validation","validationCode":"// detect cycles in tagged fields before calling Fields\nfunc hasSelfReference(t reflect.Type, seen map[reflect.Type]bool) bool {\n    if seen[t] { return true }\n    seen[t] = true\n    for i := 0; i < t.NumField(); i++ {\n        f := t.Field(i)\n        if _, ok := f.Tag.Lookup(\"ebpf\"); ok && f.Type.Kind() == reflect.Ptr {\n            if hasSelfReference(f.Type.Elem(), seen) { return true }\n        }\n    }\n    return false\n}","typeGuard":"func isAcyclicTaggedStruct(v any) bool {\n    t := reflect.TypeOf(v)\n    if t.Kind() == reflect.Ptr { t = t.Elem() }\n    return !hasSelfReference(t, map[reflect.Type]bool{})\n}","tryCatchPattern":"names, err := analyze.Fields(&cfg)\nif err != nil {\n    if strings.Contains(err.Error(), \"recursion on type\") {\n        return fmt.Errorf(\"ebpf-tagged struct cycle detected: %w\", err)\n    }\n    return err\n}","preventionTips":["Never put an 'ebpf' tag on a field whose type (transitively) contains itself.","Keep tagged nested structs as a tree/DAG.","Add a test that calls Fields on all tagged types in CI."],"tags":["reflection","go","recursion"],"backgroundTag":"recursive-struct-type","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}