src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

marshal.go (9235B)


      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 // This file contains a modified copy of the encoding/xml encoder.
      6 // All dynamic behavior has been removed, and reflecttion has been replaced with go/types.
      7 // This allows us to statically find unmarshable types
      8 // with the same rules for tags, shadowing and addressability as encoding/xml.
      9 // This is used for SA1026 and SA5008.
     10 
     11 // NOTE(dh): we do not check CanInterface in various places, which means we'll accept more marshaler implementations than encoding/xml does. This will lead to a small amount of false negatives.
     12 
     13 package fakexml
     14 
     15 import (
     16 	"fmt"
     17 	"go/types"
     18 	"strings"
     19 
     20 	"honnef.co/go/tools/go/types/typeutil"
     21 	"honnef.co/go/tools/knowledge"
     22 	"honnef.co/go/tools/staticcheck/fakereflect"
     23 )
     24 
     25 func Marshal(v types.Type) error {
     26 	return NewEncoder().Encode(v)
     27 }
     28 
     29 type Encoder struct {
     30 	// TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know
     31 	// if this is actually required for correctness.
     32 	seenCanAddr  typeutil.Map[struct{}]
     33 	seenCantAddr typeutil.Map[struct{}]
     34 }
     35 
     36 func NewEncoder() *Encoder {
     37 	e := &Encoder{}
     38 	return e
     39 }
     40 
     41 func (enc *Encoder) Encode(v types.Type) error {
     42 	rv := fakereflect.TypeAndCanAddr{Type: v}
     43 	return enc.marshalValue(rv, nil, nil, "x")
     44 }
     45 
     46 func implementsMarshaler(v fakereflect.TypeAndCanAddr) bool {
     47 	t := v.Type
     48 	obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXML")
     49 	if obj == nil {
     50 		return false
     51 	}
     52 	fn, ok := obj.(*types.Func)
     53 	if !ok {
     54 		return false
     55 	}
     56 	params := fn.Type().(*types.Signature).Params()
     57 	if params.Len() != 2 {
     58 		return false
     59 	}
     60 	if !typeutil.IsPointerToTypeWithName(params.At(0).Type(), "encoding/xml.Encoder") {
     61 		return false
     62 	}
     63 	if !typeutil.IsTypeWithName(params.At(1).Type(), "encoding/xml.StartElement") {
     64 		return false
     65 	}
     66 	rets := fn.Type().(*types.Signature).Results()
     67 	if rets.Len() != 1 {
     68 		return false
     69 	}
     70 	if !typeutil.IsTypeWithName(rets.At(0).Type(), "error") {
     71 		return false
     72 	}
     73 	return true
     74 }
     75 
     76 func implementsMarshalerAttr(v fakereflect.TypeAndCanAddr) bool {
     77 	t := v.Type
     78 	obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXMLAttr")
     79 	if obj == nil {
     80 		return false
     81 	}
     82 	fn, ok := obj.(*types.Func)
     83 	if !ok {
     84 		return false
     85 	}
     86 	params := fn.Type().(*types.Signature).Params()
     87 	if params.Len() != 1 {
     88 		return false
     89 	}
     90 	if !typeutil.IsTypeWithName(params.At(0).Type(), "encoding/xml.Name") {
     91 		return false
     92 	}
     93 	rets := fn.Type().(*types.Signature).Results()
     94 	if rets.Len() != 2 {
     95 		return false
     96 	}
     97 	if !typeutil.IsTypeWithName(rets.At(0).Type(), "encoding/xml.Attr") {
     98 		return false
     99 	}
    100 	if !typeutil.IsTypeWithName(rets.At(1).Type(), "error") {
    101 		return false
    102 	}
    103 	return true
    104 }
    105 
    106 type CyclicTypeError struct {
    107 	Type types.Type
    108 	Path string
    109 }
    110 
    111 func (err *CyclicTypeError) Error() string {
    112 	return "cyclic type"
    113 }
    114 
    115 // marshalValue writes one or more XML elements representing val.
    116 // If val was obtained from a struct field, finfo must have its details.
    117 func (e *Encoder) marshalValue(val fakereflect.TypeAndCanAddr, finfo *fieldInfo, startTemplate *StartElement, stack string) error {
    118 	var m *typeutil.Map[struct{}]
    119 	if val.CanAddr() {
    120 		m = &e.seenCanAddr
    121 	} else {
    122 		m = &e.seenCantAddr
    123 	}
    124 	if _, ok := m.At(val.Type); ok {
    125 		return nil
    126 	}
    127 	m.Set(val.Type, struct{}{})
    128 
    129 	// Drill into interfaces and pointers.
    130 	seen := map[fakereflect.TypeAndCanAddr]struct{}{}
    131 	for val.IsInterface() || val.IsPtr() {
    132 		if val.IsInterface() {
    133 			return nil
    134 		}
    135 		val = val.Elem()
    136 		if _, ok := seen[val]; ok {
    137 			// Loop in type graph, e.g. 'type P *P'
    138 			return &CyclicTypeError{val.Type, stack}
    139 		}
    140 		seen[val] = struct{}{}
    141 	}
    142 
    143 	// Check for marshaler.
    144 	if implementsMarshaler(val) {
    145 		return nil
    146 	}
    147 	if val.CanAddr() {
    148 		pv := fakereflect.PtrTo(val)
    149 		if implementsMarshaler(pv) {
    150 			return nil
    151 		}
    152 	}
    153 
    154 	// Check for text marshaler.
    155 	if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    156 		return nil
    157 	}
    158 	if val.CanAddr() {
    159 		pv := fakereflect.PtrTo(val)
    160 		if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    161 			return nil
    162 		}
    163 	}
    164 
    165 	// Slices and arrays iterate over the elements. They do not have an enclosing tag.
    166 	if (val.IsSlice() || val.IsArray()) && !isByteArray(val) && !isByteSlice(val) {
    167 		if err := e.marshalValue(val.Elem(), finfo, startTemplate, stack+"[0]"); err != nil {
    168 			return err
    169 		}
    170 		return nil
    171 	}
    172 
    173 	tinfo, err := getTypeInfo(val)
    174 	if err != nil {
    175 		return err
    176 	}
    177 
    178 	// Create start element.
    179 	// Precedence for the XML element name is:
    180 	// 0. startTemplate
    181 	// 1. XMLName field in underlying struct;
    182 	// 2. field name/tag in the struct field; and
    183 	// 3. type name
    184 	var start StartElement
    185 
    186 	if startTemplate != nil {
    187 		start.Name = startTemplate.Name
    188 		start.Attr = append(start.Attr, startTemplate.Attr...)
    189 	} else if tinfo.xmlname != nil {
    190 		xmlname := tinfo.xmlname
    191 		if xmlname.name != "" {
    192 			start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name
    193 		}
    194 	}
    195 
    196 	// Attributes
    197 	for i := range tinfo.fields {
    198 		finfo := &tinfo.fields[i]
    199 		if finfo.flags&fAttr == 0 {
    200 			continue
    201 		}
    202 		fv := finfo.value(val)
    203 
    204 		name := Name{Space: finfo.xmlns, Local: finfo.name}
    205 		if err := e.marshalAttr(&start, name, fv, stack+pathByIndex(val, finfo.idx)); err != nil {
    206 			return err
    207 		}
    208 	}
    209 
    210 	if val.IsStruct() {
    211 		return e.marshalStruct(tinfo, val, stack)
    212 	} else {
    213 		return e.marshalSimple(val, stack)
    214 	}
    215 }
    216 
    217 func isSlice(v fakereflect.TypeAndCanAddr) bool {
    218 	_, ok := v.Type.Underlying().(*types.Slice)
    219 	return ok
    220 }
    221 
    222 func isByteSlice(v fakereflect.TypeAndCanAddr) bool {
    223 	slice, ok := v.Type.Underlying().(*types.Slice)
    224 	if !ok {
    225 		return false
    226 	}
    227 	basic, ok := slice.Elem().Underlying().(*types.Basic)
    228 	if !ok {
    229 		return false
    230 	}
    231 	return basic.Kind() == types.Uint8
    232 }
    233 
    234 func isByteArray(v fakereflect.TypeAndCanAddr) bool {
    235 	slice, ok := v.Type.Underlying().(*types.Array)
    236 	if !ok {
    237 		return false
    238 	}
    239 	basic, ok := slice.Elem().Underlying().(*types.Basic)
    240 	if !ok {
    241 		return false
    242 	}
    243 	return basic.Kind() == types.Uint8
    244 }
    245 
    246 // marshalAttr marshals an attribute with the given name and value, adding to start.Attr.
    247 func (e *Encoder) marshalAttr(start *StartElement, name Name, val fakereflect.TypeAndCanAddr, stack string) error {
    248 	if implementsMarshalerAttr(val) {
    249 		return nil
    250 	}
    251 
    252 	if val.CanAddr() {
    253 		pv := fakereflect.PtrTo(val)
    254 		if implementsMarshalerAttr(pv) {
    255 			return nil
    256 		}
    257 	}
    258 
    259 	if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    260 		return nil
    261 	}
    262 
    263 	if val.CanAddr() {
    264 		pv := fakereflect.PtrTo(val)
    265 		if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    266 			return nil
    267 		}
    268 	}
    269 
    270 	// Dereference or skip nil pointer
    271 	if val.IsPtr() {
    272 		val = val.Elem()
    273 	}
    274 
    275 	// Walk slices.
    276 	if isSlice(val) && !isByteSlice(val) {
    277 		if err := e.marshalAttr(start, name, val.Elem(), stack+"[0]"); err != nil {
    278 			return err
    279 		}
    280 		return nil
    281 	}
    282 
    283 	if typeutil.IsTypeWithName(val.Type, "encoding/xml.Attr") {
    284 		return nil
    285 	}
    286 
    287 	return e.marshalSimple(val, stack)
    288 }
    289 
    290 func (e *Encoder) marshalSimple(val fakereflect.TypeAndCanAddr, stack string) error {
    291 	switch val.Type.Underlying().(type) {
    292 	case *types.Basic, *types.Interface:
    293 		return nil
    294 	case *types.Slice, *types.Array:
    295 		basic, ok := val.Elem().Type.Underlying().(*types.Basic)
    296 		if !ok || basic.Kind() != types.Uint8 {
    297 			return &UnsupportedTypeError{val.Type, stack}
    298 		}
    299 		return nil
    300 	default:
    301 		return &UnsupportedTypeError{val.Type, stack}
    302 	}
    303 }
    304 
    305 func indirect(vf fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
    306 	for vf.IsPtr() {
    307 		vf = vf.Elem()
    308 	}
    309 	return vf
    310 }
    311 
    312 func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string {
    313 	var path strings.Builder
    314 	for _, i := range index {
    315 		if t.IsPtr() {
    316 			t = t.Elem()
    317 		}
    318 		path.WriteString("." + t.Field(i).Name)
    319 		t = t.Field(i).Type
    320 	}
    321 	return path.String()
    322 }
    323 
    324 func (e *Encoder) marshalStruct(tinfo *typeInfo, val fakereflect.TypeAndCanAddr, stack string) error {
    325 	for i := range tinfo.fields {
    326 		finfo := &tinfo.fields[i]
    327 		if finfo.flags&fAttr != 0 {
    328 			continue
    329 		}
    330 		vf := finfo.value(val)
    331 
    332 		switch finfo.flags & fMode {
    333 		case fCDATA, fCharData:
    334 			if vf.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    335 				continue
    336 			}
    337 			if vf.CanAddr() {
    338 				pv := fakereflect.PtrTo(vf)
    339 				if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
    340 					continue
    341 				}
    342 			}
    343 			continue
    344 
    345 		case fComment:
    346 			vf = indirect(vf)
    347 			if !(isByteSlice(vf) || isByteArray(vf)) {
    348 				return fmt.Errorf("xml: bad type for comment field of %s", val)
    349 			}
    350 			continue
    351 
    352 		case fInnerXML:
    353 			vf = indirect(vf)
    354 			if t, ok := vf.Type.(*types.Slice); (ok && types.Identical(t.Elem(), types.Typ[types.Byte])) || types.Identical(vf.Type, types.Typ[types.String]) {
    355 				continue
    356 			}
    357 
    358 		case fElement, fElement | fAny:
    359 		}
    360 		if err := e.marshalValue(vf, finfo, nil, stack+pathByIndex(val, finfo.idx)); err != nil {
    361 			return err
    362 		}
    363 	}
    364 	return nil
    365 }
    366 
    367 // UnsupportedTypeError is returned when Marshal encounters a type
    368 // that cannot be converted into XML.
    369 type UnsupportedTypeError struct {
    370 	Type types.Type
    371 	Path string
    372 }
    373 
    374 func (e *UnsupportedTypeError) Error() string {
    375 	return fmt.Sprintf("xml: unsupported type %s, via %s ", e.Type, e.Path)
    376 }