staticcheck (#313)

* CI: use staticcheck for linting

This commit switches the linter for Go code from golint to staticcheck.
Golint has been deprecated since last year and staticcheck is a
recommended replacement.

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>

* revendor

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>

* cmd,pkg: fix lint warnings

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>
This commit is contained in:
Lucas Servén Marín
2022-05-19 19:45:43 +02:00
committed by GitHub
parent 93f46e03ea
commit 50fbc2eec2
227 changed files with 55458 additions and 2689 deletions

View File

@@ -0,0 +1,380 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains a modified copy of the encoding/xml encoder.
// All dynamic behavior has been removed, and reflecttion has been replaced with go/types.
// This allows us to statically find unmarshable types
// with the same rules for tags, shadowing and addressability as encoding/xml.
// This is used for SA1026 and SA5008.
// 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.
package fakexml
import (
"fmt"
"go/token"
"go/types"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/staticcheck/fakereflect"
)
func Marshal(v types.Type) error {
return NewEncoder().Encode(v)
}
type Encoder struct {
seen map[fakereflect.TypeAndCanAddr]struct{}
}
func NewEncoder() *Encoder {
e := &Encoder{
seen: map[fakereflect.TypeAndCanAddr]struct{}{},
}
return e
}
func (enc *Encoder) Encode(v types.Type) error {
rv := fakereflect.TypeAndCanAddr{Type: v}
return enc.marshalValue(rv, nil, nil, "x")
}
func implementsMarshaler(v fakereflect.TypeAndCanAddr) bool {
t := v.Type
obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXML")
if obj == nil {
return false
}
fn, ok := obj.(*types.Func)
if !ok {
return false
}
params := fn.Type().(*types.Signature).Params()
if params.Len() != 2 {
return false
}
if !typeutil.IsType(params.At(0).Type(), "*encoding/xml.Encoder") {
return false
}
if !typeutil.IsType(params.At(1).Type(), "encoding/xml.StartElement") {
return false
}
rets := fn.Type().(*types.Signature).Results()
if rets.Len() != 1 {
return false
}
if !typeutil.IsType(rets.At(0).Type(), "error") {
return false
}
return true
}
func implementsMarshalerAttr(v fakereflect.TypeAndCanAddr) bool {
t := v.Type
obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXMLAttr")
if obj == nil {
return false
}
fn, ok := obj.(*types.Func)
if !ok {
return false
}
params := fn.Type().(*types.Signature).Params()
if params.Len() != 1 {
return false
}
if !typeutil.IsType(params.At(0).Type(), "encoding/xml.Name") {
return false
}
rets := fn.Type().(*types.Signature).Results()
if rets.Len() != 2 {
return false
}
if !typeutil.IsType(rets.At(0).Type(), "encoding/xml.Attr") {
return false
}
if !typeutil.IsType(rets.At(1).Type(), "error") {
return false
}
return true
}
var textMarshalerType = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalText", types.NewSignature(nil,
types.NewTuple(),
types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])),
types.NewVar(0, nil, "", types.Universe.Lookup("error").Type())),
false,
)),
}, nil).Complete()
var N = 0
type CyclicTypeError struct {
Type types.Type
Path string
}
func (err *CyclicTypeError) Error() string {
return "cyclic type"
}
// marshalValue writes one or more XML elements representing val.
// If val was obtained from a struct field, finfo must have its details.
func (e *Encoder) marshalValue(val fakereflect.TypeAndCanAddr, finfo *fieldInfo, startTemplate *StartElement, stack string) error {
if _, ok := e.seen[val]; ok {
return nil
}
e.seen[val] = struct{}{}
// Drill into interfaces and pointers.
seen := map[fakereflect.TypeAndCanAddr]struct{}{}
for val.IsInterface() || val.IsPtr() {
if val.IsInterface() {
return nil
}
val = val.Elem()
if _, ok := seen[val]; ok {
// Loop in type graph, e.g. 'type P *P'
return &CyclicTypeError{val.Type, stack}
}
seen[val] = struct{}{}
}
// Check for marshaler.
if implementsMarshaler(val) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if implementsMarshaler(pv) {
return nil
}
}
// Check for text marshaler.
if val.Implements(textMarshalerType) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if pv.Implements(textMarshalerType) {
return nil
}
}
// Slices and arrays iterate over the elements. They do not have an enclosing tag.
if (val.IsSlice() || val.IsArray()) && !isByteArray(val) && !isByteSlice(val) {
if err := e.marshalValue(val.Elem(), finfo, startTemplate, stack+"[0]"); err != nil {
return err
}
return nil
}
tinfo, err := getTypeInfo(val)
if err != nil {
return err
}
// Create start element.
// Precedence for the XML element name is:
// 0. startTemplate
// 1. XMLName field in underlying struct;
// 2. field name/tag in the struct field; and
// 3. type name
var start StartElement
if startTemplate != nil {
start.Name = startTemplate.Name
start.Attr = append(start.Attr, startTemplate.Attr...)
} else if tinfo.xmlname != nil {
xmlname := tinfo.xmlname
if xmlname.name != "" {
start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name
}
}
// Attributes
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
if finfo.flags&fAttr == 0 {
continue
}
fv := finfo.value(val)
name := Name{Space: finfo.xmlns, Local: finfo.name}
if err := e.marshalAttr(&start, name, fv, stack+pathByIndex(val, finfo.idx)); err != nil {
return err
}
}
if val.IsStruct() {
return e.marshalStruct(tinfo, val, stack)
} else {
return e.marshalSimple(val, stack)
}
}
func isSlice(v fakereflect.TypeAndCanAddr) bool {
_, ok := v.Type.Underlying().(*types.Slice)
return ok
}
func isByteSlice(v fakereflect.TypeAndCanAddr) bool {
slice, ok := v.Type.Underlying().(*types.Slice)
if !ok {
return false
}
basic, ok := slice.Elem().Underlying().(*types.Basic)
if !ok {
return false
}
return basic.Kind() == types.Uint8
}
func isByteArray(v fakereflect.TypeAndCanAddr) bool {
slice, ok := v.Type.Underlying().(*types.Array)
if !ok {
return false
}
basic, ok := slice.Elem().Underlying().(*types.Basic)
if !ok {
return false
}
return basic.Kind() == types.Uint8
}
// marshalAttr marshals an attribute with the given name and value, adding to start.Attr.
func (e *Encoder) marshalAttr(start *StartElement, name Name, val fakereflect.TypeAndCanAddr, stack string) error {
if implementsMarshalerAttr(val) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if implementsMarshalerAttr(pv) {
return nil
}
}
if val.Implements(textMarshalerType) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if pv.Implements(textMarshalerType) {
return nil
}
}
// Dereference or skip nil pointer
if val.IsPtr() {
val = val.Elem()
}
// Walk slices.
if isSlice(val) && !isByteSlice(val) {
if err := e.marshalAttr(start, name, val.Elem(), stack+"[0]"); err != nil {
return err
}
return nil
}
if typeutil.IsType(val.Type, "encoding/xml.Attr") {
return nil
}
return e.marshalSimple(val, stack)
}
func (e *Encoder) marshalSimple(val fakereflect.TypeAndCanAddr, stack string) error {
switch val.Type.Underlying().(type) {
case *types.Basic, *types.Interface:
return nil
case *types.Slice, *types.Array:
basic, ok := val.Elem().Type.Underlying().(*types.Basic)
if !ok || basic.Kind() != types.Uint8 {
return &UnsupportedTypeError{val.Type, stack}
}
return nil
default:
return &UnsupportedTypeError{val.Type, stack}
}
}
func indirect(vf fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
for vf.IsPtr() {
vf = vf.Elem()
}
return vf
}
func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string {
path := ""
for _, i := range index {
if t.IsPtr() {
t = t.Elem()
}
path += "." + t.Field(i).Name
t = t.Field(i).Type
}
return path
}
func (e *Encoder) marshalStruct(tinfo *typeInfo, val fakereflect.TypeAndCanAddr, stack string) error {
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
if finfo.flags&fAttr != 0 {
continue
}
vf := finfo.value(val)
switch finfo.flags & fMode {
case fCDATA, fCharData:
if vf.Implements(textMarshalerType) {
continue
}
if vf.CanAddr() {
pv := fakereflect.PtrTo(vf)
if pv.Implements(textMarshalerType) {
continue
}
}
continue
case fComment:
vf = indirect(vf)
if !(isByteSlice(vf) || isByteArray(vf)) {
return fmt.Errorf("xml: bad type for comment field of %s", val)
}
continue
case fInnerXML:
vf = indirect(vf)
if typeutil.IsType(vf.Type, "[]byte") || typeutil.IsType(vf.Type, "string") {
continue
}
case fElement, fElement | fAny:
}
if err := e.marshalValue(vf, finfo, nil, stack+pathByIndex(val, finfo.idx)); err != nil {
return err
}
}
return nil
}
// UnsupportedTypeError is returned when Marshal encounters a type
// that cannot be converted into XML.
type UnsupportedTypeError struct {
Type types.Type
Path string
}
func (e *UnsupportedTypeError) Error() string {
return fmt.Sprintf("xml: unsupported type %s, via %s ", e.Type, e.Path)
}

View File

@@ -0,0 +1,389 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fakexml
import (
"fmt"
"go/types"
"strconv"
"strings"
"sync"
"honnef.co/go/tools/staticcheck/fakereflect"
)
// typeInfo holds details for the xml representation of a type.
type typeInfo struct {
xmlname *fieldInfo
fields []fieldInfo
}
// fieldInfo holds details for the xml representation of a single field.
type fieldInfo struct {
idx []int
name string
xmlns string
flags fieldFlags
parents []string
}
type fieldFlags int
const (
fElement fieldFlags = 1 << iota
fAttr
fCDATA
fCharData
fInnerXML
fComment
fAny
fOmitEmpty
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
xmlName = "XMLName"
)
func (f fieldFlags) String() string {
switch f {
case fAttr:
return "attr"
case fCDATA:
return "cdata"
case fCharData:
return "chardata"
case fInnerXML:
return "innerxml"
case fComment:
return "comment"
case fAny:
return "any"
case fOmitEmpty:
return "omitempty"
case fAny | fAttr:
return "any,attr"
default:
return strconv.Itoa(int(f))
}
}
var tinfoMap sync.Map // map[reflect.Type]*typeInfo
// getTypeInfo returns the typeInfo structure with details necessary
// for marshaling and unmarshaling typ.
func getTypeInfo(typ fakereflect.TypeAndCanAddr) (*typeInfo, error) {
if ti, ok := tinfoMap.Load(typ); ok {
return ti.(*typeInfo), nil
}
tinfo := &typeInfo{}
named, ok := typ.Type.(*types.Named)
if typ.IsStruct() && !(ok && named.Obj().Pkg().Path() == "encoding/xml" && named.Obj().Name() == "Name") {
n := typ.NumField()
for i := 0; i < n; i++ {
f := typ.Field(i)
if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" {
continue // Private field
}
// For embedded structs, embed its fields.
if f.Anonymous {
t := f.Type
if t.IsPtr() {
t = t.Elem()
}
if t.IsStruct() {
inner, err := getTypeInfo(t)
if err != nil {
return nil, err
}
if tinfo.xmlname == nil {
tinfo.xmlname = inner.xmlname
}
for _, finfo := range inner.fields {
finfo.idx = append([]int{i}, finfo.idx...)
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
return nil, err
}
}
continue
}
}
finfo, err := StructFieldInfo(f)
if err != nil {
return nil, err
}
if f.Name == xmlName {
tinfo.xmlname = finfo
continue
}
// Add the field if it doesn't conflict with other fields.
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
return nil, err
}
}
}
ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
return ti.(*typeInfo), nil
}
// StructFieldInfo builds and returns a fieldInfo for f.
func StructFieldInfo(f fakereflect.StructField) (*fieldInfo, error) {
finfo := &fieldInfo{idx: f.Index}
// Split the tag from the xml namespace if necessary.
tag := f.Tag.Get("xml")
if i := strings.Index(tag, " "); i >= 0 {
finfo.xmlns, tag = tag[:i], tag[i+1:]
}
// Parse flags.
tokens := strings.Split(tag, ",")
if len(tokens) == 1 {
finfo.flags = fElement
} else {
tag = tokens[0]
for _, flag := range tokens[1:] {
switch flag {
case "attr":
finfo.flags |= fAttr
case "cdata":
finfo.flags |= fCDATA
case "chardata":
finfo.flags |= fCharData
case "innerxml":
finfo.flags |= fInnerXML
case "comment":
finfo.flags |= fComment
case "any":
finfo.flags |= fAny
case "omitempty":
finfo.flags |= fOmitEmpty
}
}
// Validate the flags used.
switch mode := finfo.flags & fMode; mode {
case 0:
finfo.flags |= fElement
case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr:
if f.Name == xmlName {
return nil, fmt.Errorf("cannot use option %s on XMLName field", mode)
} else if tag != "" && mode != fAttr {
return nil, fmt.Errorf("cannot specify name together with option ,%s", mode)
}
default:
// This will also catch multiple modes in a single field.
return nil, fmt.Errorf("invalid combination of options: %q", f.Tag.Get("xml"))
}
if finfo.flags&fMode == fAny {
finfo.flags |= fElement
}
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
return nil, fmt.Errorf("can only use omitempty on elements and attributes")
}
}
// Use of xmlns without a name is not allowed.
if finfo.xmlns != "" && tag == "" {
return nil, fmt.Errorf("namespace without name: %q", f.Tag.Get("xml"))
}
if f.Name == xmlName {
// The XMLName field records the XML element name. Don't
// process it as usual because its name should default to
// empty rather than to the field name.
finfo.name = tag
return finfo, nil
}
if tag == "" {
// If the name part of the tag is completely empty, get
// default from XMLName of underlying struct if feasible,
// or field name otherwise.
if xmlname := lookupXMLName(f.Type); xmlname != nil {
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
} else {
finfo.name = f.Name
}
return finfo, nil
}
// Prepare field name and parents.
parents := strings.Split(tag, ">")
if parents[0] == "" {
parents[0] = f.Name
}
if parents[len(parents)-1] == "" {
return nil, fmt.Errorf("trailing '>'")
}
finfo.name = parents[len(parents)-1]
if len(parents) > 1 {
if (finfo.flags & fElement) == 0 {
return nil, fmt.Errorf("%s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
}
finfo.parents = parents[:len(parents)-1]
}
// If the field type has an XMLName field, the names must match
// so that the behavior of both marshaling and unmarshaling
// is straightforward and unambiguous.
if finfo.flags&fElement != 0 {
ftyp := f.Type
xmlname := lookupXMLName(ftyp)
if xmlname != nil && xmlname.name != finfo.name {
return nil, fmt.Errorf("name %q conflicts with name %q in %s.XMLName", finfo.name, xmlname.name, ftyp)
}
}
return finfo, nil
}
// lookupXMLName returns the fieldInfo for typ's XMLName field
// in case it exists and has a valid xml field tag, otherwise
// it returns nil.
func lookupXMLName(typ fakereflect.TypeAndCanAddr) (xmlname *fieldInfo) {
seen := map[fakereflect.TypeAndCanAddr]struct{}{}
for typ.IsPtr() {
typ = typ.Elem()
if _, ok := seen[typ]; ok {
// Loop in type graph, e.g. 'type P *P'
return nil
}
seen[typ] = struct{}{}
}
if !typ.IsStruct() {
return nil
}
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
if f.Name != xmlName {
continue
}
finfo, err := StructFieldInfo(f)
if err == nil && finfo.name != "" {
return finfo
}
// Also consider errors as a non-existent field tag
// and let getTypeInfo itself report the error.
break
}
return nil
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
// addFieldInfo adds finfo to tinfo.fields if there are no
// conflicts, or if conflicts arise from previous fields that were
// obtained from deeper embedded structures than finfo. In the latter
// case, the conflicting entries are dropped.
// A conflict occurs when the path (parent + name) to a field is
// itself a prefix of another path, or when two paths match exactly.
// It is okay for field paths to share a common, shorter prefix.
func addFieldInfo(typ fakereflect.TypeAndCanAddr, tinfo *typeInfo, newf *fieldInfo) error {
var conflicts []int
Loop:
// First, figure all conflicts. Most working code will have none.
for i := range tinfo.fields {
oldf := &tinfo.fields[i]
if oldf.flags&fMode != newf.flags&fMode {
continue
}
if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
continue
}
minl := min(len(newf.parents), len(oldf.parents))
for p := 0; p < minl; p++ {
if oldf.parents[p] != newf.parents[p] {
continue Loop
}
}
if len(oldf.parents) > len(newf.parents) {
if oldf.parents[len(newf.parents)] == newf.name {
conflicts = append(conflicts, i)
}
} else if len(oldf.parents) < len(newf.parents) {
if newf.parents[len(oldf.parents)] == oldf.name {
conflicts = append(conflicts, i)
}
} else {
if newf.name == oldf.name {
conflicts = append(conflicts, i)
}
}
}
// Without conflicts, add the new field and return.
if conflicts == nil {
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// If any conflict is shallower, ignore the new field.
// This matches the Go field resolution on embedding.
for _, i := range conflicts {
if len(tinfo.fields[i].idx) < len(newf.idx) {
return nil
}
}
// Otherwise, if any of them is at the same depth level, it's an error.
for _, i := range conflicts {
oldf := &tinfo.fields[i]
if len(oldf.idx) == len(newf.idx) {
f1 := typ.FieldByIndex(oldf.idx)
f2 := typ.FieldByIndex(newf.idx)
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
}
}
// Otherwise, the new field is shallower, and thus takes precedence,
// so drop the conflicting fields from tinfo and append the new one.
for c := len(conflicts) - 1; c >= 0; c-- {
i := conflicts[c]
copy(tinfo.fields[i:], tinfo.fields[i+1:])
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
}
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// A TagPathError represents an error in the unmarshaling process
// caused by the use of field tags with conflicting paths.
type TagPathError struct {
Struct fakereflect.TypeAndCanAddr
Field1, Tag1 string
Field2, Tag2 string
}
func (e *TagPathError) Error() string {
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)
}
// value returns v's field value corresponding to finfo.
// It's equivalent to v.FieldByIndex(finfo.idx), but when passed
// initNilPointers, it initializes and dereferences pointers as necessary.
// When passed dontInitNilPointers and a nil pointer is reached, the function
// returns a zero reflect.Value.
func (finfo *fieldInfo) value(v fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
for i, x := range finfo.idx {
if i > 0 {
t := v
if t.IsPtr() && t.Elem().IsStruct() {
v = v.Elem()
}
}
v = v.Field(x).Type
}
return v
}

View File

@@ -0,0 +1,33 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fakexml
// References:
// Annotated XML spec: https://www.xml.com/axml/testaxml.htm
// XML name spaces: https://www.w3.org/TR/REC-xml-names/
// TODO(rsc):
// Test error handling.
// A Name represents an XML name (Local) annotated
// with a name space identifier (Space).
// In tokens returned by Decoder.Token, the Space identifier
// is given as a canonical URL, not the short prefix used
// in the document being parsed.
type Name struct {
Space, Local string
}
// An Attr represents an attribute in an XML element (Name=Value).
type Attr struct {
Name Name
Value string
}
// A StartElement represents an XML start element.
type StartElement struct {
Name Name
Attr []Attr
}