github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/fakexml/typeinfo.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fakexml 6 7 import ( 8 "fmt" 9 "go/types" 10 "strconv" 11 "strings" 12 "sync" 13 14 "github.com/amarpal/go-tools/staticcheck/fakereflect" 15 ) 16 17 // typeInfo holds details for the xml representation of a type. 18 type typeInfo struct { 19 xmlname *fieldInfo 20 fields []fieldInfo 21 } 22 23 // fieldInfo holds details for the xml representation of a single field. 24 type fieldInfo struct { 25 idx []int 26 name string 27 xmlns string 28 flags fieldFlags 29 parents []string 30 } 31 32 type fieldFlags int 33 34 const ( 35 fElement fieldFlags = 1 << iota 36 fAttr 37 fCDATA 38 fCharData 39 fInnerXML 40 fComment 41 fAny 42 43 fOmitEmpty 44 45 fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny 46 47 xmlName = "XMLName" 48 ) 49 50 func (f fieldFlags) String() string { 51 switch f { 52 case fAttr: 53 return "attr" 54 case fCDATA: 55 return "cdata" 56 case fCharData: 57 return "chardata" 58 case fInnerXML: 59 return "innerxml" 60 case fComment: 61 return "comment" 62 case fAny: 63 return "any" 64 case fOmitEmpty: 65 return "omitempty" 66 case fAny | fAttr: 67 return "any,attr" 68 default: 69 return strconv.Itoa(int(f)) 70 } 71 } 72 73 var tinfoMap sync.Map // map[reflect.Type]*typeInfo 74 75 // getTypeInfo returns the typeInfo structure with details necessary 76 // for marshaling and unmarshaling typ. 77 func getTypeInfo(typ fakereflect.TypeAndCanAddr) (*typeInfo, error) { 78 if ti, ok := tinfoMap.Load(typ); ok { 79 return ti.(*typeInfo), nil 80 } 81 82 tinfo := &typeInfo{} 83 named, ok := typ.Type.(*types.Named) 84 if typ.IsStruct() && !(ok && named.Obj().Pkg().Path() == "encoding/xml" && named.Obj().Name() == "Name") { 85 n := typ.NumField() 86 for i := 0; i < n; i++ { 87 f := typ.Field(i) 88 if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" { 89 continue // Private field 90 } 91 92 // For embedded structs, embed its fields. 93 if f.Anonymous { 94 t := f.Type 95 if t.IsPtr() { 96 t = t.Elem() 97 } 98 if t.IsStruct() { 99 inner, err := getTypeInfo(t) 100 if err != nil { 101 return nil, err 102 } 103 if tinfo.xmlname == nil { 104 tinfo.xmlname = inner.xmlname 105 } 106 for _, finfo := range inner.fields { 107 finfo.idx = append([]int{i}, finfo.idx...) 108 if err := addFieldInfo(typ, tinfo, &finfo); err != nil { 109 return nil, err 110 } 111 } 112 continue 113 } 114 } 115 116 finfo, err := StructFieldInfo(f) 117 if err != nil { 118 return nil, err 119 } 120 121 if f.Name == xmlName { 122 tinfo.xmlname = finfo 123 continue 124 } 125 126 // Add the field if it doesn't conflict with other fields. 127 if err := addFieldInfo(typ, tinfo, finfo); err != nil { 128 return nil, err 129 } 130 } 131 } 132 133 ti, _ := tinfoMap.LoadOrStore(typ, tinfo) 134 return ti.(*typeInfo), nil 135 } 136 137 // StructFieldInfo builds and returns a fieldInfo for f. 138 func StructFieldInfo(f fakereflect.StructField) (*fieldInfo, error) { 139 finfo := &fieldInfo{idx: f.Index} 140 141 // Split the tag from the xml namespace if necessary. 142 tag := f.Tag.Get("xml") 143 if i := strings.Index(tag, " "); i >= 0 { 144 finfo.xmlns, tag = tag[:i], tag[i+1:] 145 } 146 147 // Parse flags. 148 tokens := strings.Split(tag, ",") 149 if len(tokens) == 1 { 150 finfo.flags = fElement 151 } else { 152 tag = tokens[0] 153 for _, flag := range tokens[1:] { 154 switch flag { 155 case "attr": 156 finfo.flags |= fAttr 157 case "cdata": 158 finfo.flags |= fCDATA 159 case "chardata": 160 finfo.flags |= fCharData 161 case "innerxml": 162 finfo.flags |= fInnerXML 163 case "comment": 164 finfo.flags |= fComment 165 case "any": 166 finfo.flags |= fAny 167 case "omitempty": 168 finfo.flags |= fOmitEmpty 169 } 170 } 171 172 // Validate the flags used. 173 switch mode := finfo.flags & fMode; mode { 174 case 0: 175 finfo.flags |= fElement 176 case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr: 177 if f.Name == xmlName { 178 return nil, fmt.Errorf("cannot use option %s on XMLName field", mode) 179 } else if tag != "" && mode != fAttr { 180 return nil, fmt.Errorf("cannot specify name together with option ,%s", mode) 181 } 182 default: 183 // This will also catch multiple modes in a single field. 184 return nil, fmt.Errorf("invalid combination of options: %q", f.Tag.Get("xml")) 185 } 186 if finfo.flags&fMode == fAny { 187 finfo.flags |= fElement 188 } 189 if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { 190 return nil, fmt.Errorf("can only use omitempty on elements and attributes") 191 } 192 } 193 194 // Use of xmlns without a name is not allowed. 195 if finfo.xmlns != "" && tag == "" { 196 return nil, fmt.Errorf("namespace without name: %q", f.Tag.Get("xml")) 197 } 198 199 if f.Name == xmlName { 200 // The XMLName field records the XML element name. Don't 201 // process it as usual because its name should default to 202 // empty rather than to the field name. 203 finfo.name = tag 204 return finfo, nil 205 } 206 207 if tag == "" { 208 // If the name part of the tag is completely empty, get 209 // default from XMLName of underlying struct if feasible, 210 // or field name otherwise. 211 if xmlname := lookupXMLName(f.Type); xmlname != nil { 212 finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name 213 } else { 214 finfo.name = f.Name 215 } 216 return finfo, nil 217 } 218 219 // Prepare field name and parents. 220 parents := strings.Split(tag, ">") 221 if parents[0] == "" { 222 parents[0] = f.Name 223 } 224 if parents[len(parents)-1] == "" { 225 return nil, fmt.Errorf("trailing '>'") 226 } 227 finfo.name = parents[len(parents)-1] 228 if len(parents) > 1 { 229 if (finfo.flags & fElement) == 0 { 230 return nil, fmt.Errorf("%s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) 231 } 232 finfo.parents = parents[:len(parents)-1] 233 } 234 235 // If the field type has an XMLName field, the names must match 236 // so that the behavior of both marshaling and unmarshaling 237 // is straightforward and unambiguous. 238 if finfo.flags&fElement != 0 { 239 ftyp := f.Type 240 xmlname := lookupXMLName(ftyp) 241 if xmlname != nil && xmlname.name != finfo.name { 242 return nil, fmt.Errorf("name %q conflicts with name %q in %s.XMLName", finfo.name, xmlname.name, ftyp) 243 } 244 } 245 return finfo, nil 246 } 247 248 // lookupXMLName returns the fieldInfo for typ's XMLName field 249 // in case it exists and has a valid xml field tag, otherwise 250 // it returns nil. 251 func lookupXMLName(typ fakereflect.TypeAndCanAddr) (xmlname *fieldInfo) { 252 seen := map[fakereflect.TypeAndCanAddr]struct{}{} 253 for typ.IsPtr() { 254 typ = typ.Elem() 255 if _, ok := seen[typ]; ok { 256 // Loop in type graph, e.g. 'type P *P' 257 return nil 258 } 259 seen[typ] = struct{}{} 260 } 261 if !typ.IsStruct() { 262 return nil 263 } 264 for i, n := 0, typ.NumField(); i < n; i++ { 265 f := typ.Field(i) 266 if f.Name != xmlName { 267 continue 268 } 269 finfo, err := StructFieldInfo(f) 270 if err == nil && finfo.name != "" { 271 return finfo 272 } 273 // Also consider errors as a non-existent field tag 274 // and let getTypeInfo itself report the error. 275 break 276 } 277 return nil 278 } 279 280 func min(a, b int) int { 281 if a <= b { 282 return a 283 } 284 return b 285 } 286 287 // addFieldInfo adds finfo to tinfo.fields if there are no 288 // conflicts, or if conflicts arise from previous fields that were 289 // obtained from deeper embedded structures than finfo. In the latter 290 // case, the conflicting entries are dropped. 291 // A conflict occurs when the path (parent + name) to a field is 292 // itself a prefix of another path, or when two paths match exactly. 293 // It is okay for field paths to share a common, shorter prefix. 294 func addFieldInfo(typ fakereflect.TypeAndCanAddr, tinfo *typeInfo, newf *fieldInfo) error { 295 var conflicts []int 296 Loop: 297 // First, figure all conflicts. Most working code will have none. 298 for i := range tinfo.fields { 299 oldf := &tinfo.fields[i] 300 if oldf.flags&fMode != newf.flags&fMode { 301 continue 302 } 303 if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { 304 continue 305 } 306 minl := min(len(newf.parents), len(oldf.parents)) 307 for p := 0; p < minl; p++ { 308 if oldf.parents[p] != newf.parents[p] { 309 continue Loop 310 } 311 } 312 if len(oldf.parents) > len(newf.parents) { 313 if oldf.parents[len(newf.parents)] == newf.name { 314 conflicts = append(conflicts, i) 315 } 316 } else if len(oldf.parents) < len(newf.parents) { 317 if newf.parents[len(oldf.parents)] == oldf.name { 318 conflicts = append(conflicts, i) 319 } 320 } else { 321 if newf.name == oldf.name { 322 conflicts = append(conflicts, i) 323 } 324 } 325 } 326 // Without conflicts, add the new field and return. 327 if conflicts == nil { 328 tinfo.fields = append(tinfo.fields, *newf) 329 return nil 330 } 331 332 // If any conflict is shallower, ignore the new field. 333 // This matches the Go field resolution on embedding. 334 for _, i := range conflicts { 335 if len(tinfo.fields[i].idx) < len(newf.idx) { 336 return nil 337 } 338 } 339 340 // Otherwise, if any of them is at the same depth level, it's an error. 341 for _, i := range conflicts { 342 oldf := &tinfo.fields[i] 343 if len(oldf.idx) == len(newf.idx) { 344 f1 := typ.FieldByIndex(oldf.idx) 345 f2 := typ.FieldByIndex(newf.idx) 346 return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} 347 } 348 } 349 350 // Otherwise, the new field is shallower, and thus takes precedence, 351 // so drop the conflicting fields from tinfo and append the new one. 352 for c := len(conflicts) - 1; c >= 0; c-- { 353 i := conflicts[c] 354 copy(tinfo.fields[i:], tinfo.fields[i+1:]) 355 tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] 356 } 357 tinfo.fields = append(tinfo.fields, *newf) 358 return nil 359 } 360 361 // A TagPathError represents an error in the unmarshaling process 362 // caused by the use of field tags with conflicting paths. 363 type TagPathError struct { 364 Struct fakereflect.TypeAndCanAddr 365 Field1, Tag1 string 366 Field2, Tag2 string 367 } 368 369 func (e *TagPathError) Error() string { 370 return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2) 371 } 372 373 // value returns v's field value corresponding to finfo. 374 // It's equivalent to v.FieldByIndex(finfo.idx), but when passed 375 // initNilPointers, it initializes and dereferences pointers as necessary. 376 // When passed dontInitNilPointers and a nil pointer is reached, the function 377 // returns a zero reflect.Value. 378 func (finfo *fieldInfo) value(v fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr { 379 for i, x := range finfo.idx { 380 if i > 0 { 381 t := v 382 if t.IsPtr() && t.Elem().IsStruct() { 383 v = v.Elem() 384 } 385 } 386 v = v.Field(x).Type 387 } 388 return v 389 }