Skip to content

Commit a7f4cb1

Browse files
committed
SA1026, SA5008: reimplement XML checking using encoding/xml rules
Same as previous commit, but for XML instead of JSON. Because we rely on encoding/xml, we can now flag a plethora of invalid struct tags. Closes gh-1088 (cherry picked from commit a62bc8e)
1 parent 3cd9c86 commit a7f4cb1

File tree

7 files changed

+921
-76
lines changed

7 files changed

+921
-76
lines changed

staticcheck/fakexml/marshal.go

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

0 commit comments

Comments
 (0)