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,327 @@
package staticcheck
import (
"honnef.co/go/tools/analysis/facts"
"honnef.co/go/tools/analysis/facts/nilness"
"honnef.co/go/tools/analysis/facts/typedness"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
func makeCallCheckerAnalyzer(rules map[string]CallCheck, extraReqs ...*analysis.Analyzer) *analysis.Analyzer {
reqs := []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile}
reqs = append(reqs, extraReqs...)
return &analysis.Analyzer{
Run: callChecker(rules),
Requires: reqs,
}
}
var Analyzers = lint.InitializeAnalyzers(Docs, map[string]*analysis.Analyzer{
"SA1000": makeCallCheckerAnalyzer(checkRegexpRules),
"SA1001": {
Run: CheckTemplate,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1002": makeCallCheckerAnalyzer(checkTimeParseRules),
"SA1003": makeCallCheckerAnalyzer(checkEncodingBinaryRules),
"SA1004": {
Run: CheckTimeSleepConstant,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1005": {
Run: CheckExec,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1006": {
Run: CheckUnsafePrintf,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1007": makeCallCheckerAnalyzer(checkURLsRules),
"SA1008": {
Run: CheckCanonicalHeaderKey,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1010": makeCallCheckerAnalyzer(checkRegexpFindAllRules),
"SA1011": makeCallCheckerAnalyzer(checkUTF8CutsetRules),
"SA1012": {
Run: CheckNilContext,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1013": {
Run: CheckSeeker,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1014": makeCallCheckerAnalyzer(checkUnmarshalPointerRules),
"SA1015": {
Run: CheckLeakyTimeTick,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1016": {
Run: CheckUntrappableSignal,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA1017": makeCallCheckerAnalyzer(checkUnbufferedSignalChanRules),
"SA1018": makeCallCheckerAnalyzer(checkStringsReplaceZeroRules),
"SA1019": {
Run: CheckDeprecated,
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Deprecated, facts.Generated},
},
"SA1020": makeCallCheckerAnalyzer(checkListenAddressRules),
"SA1021": makeCallCheckerAnalyzer(checkBytesEqualIPRules),
"SA1023": {
Run: CheckWriterBufferModified,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1024": makeCallCheckerAnalyzer(checkUniqueCutsetRules),
"SA1025": {
Run: CheckTimerResetReturnValue,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1026": makeCallCheckerAnalyzer(checkUnsupportedMarshal),
"SA1027": makeCallCheckerAnalyzer(checkAtomicAlignment),
"SA1028": makeCallCheckerAnalyzer(checkSortSliceRules),
"SA1029": makeCallCheckerAnalyzer(checkWithValueKeyRules),
"SA1030": makeCallCheckerAnalyzer(checkStrconvRules),
"SA2000": {
Run: CheckWaitgroupAdd,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA2001": {
Run: CheckEmptyCriticalSection,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA2002": {
Run: CheckConcurrentTesting,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA2003": {
Run: CheckDeferLock,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA3000": {
Run: CheckTestMainExit,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA3001": {
Run: CheckBenchmarkN,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4000": {
Run: CheckLhsRhsIdentical,
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.TokenFile, facts.Generated},
},
"SA4001": {
Run: CheckIneffectiveCopy,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4003": {
Run: CheckExtremeComparison,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4004": {
Run: CheckIneffectiveLoop,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4005": {
Run: CheckIneffectiveFieldAssignments,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4006": {
Run: CheckUnreadVariableValues,
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.Generated},
},
"SA4008": {
Run: CheckLoopCondition,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4009": {
Run: CheckArgOverwritten,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4010": {
Run: CheckIneffectiveAppend,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4011": {
Run: CheckScopedBreak,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4012": {
Run: CheckNaNComparison,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4013": {
Run: CheckDoubleNegation,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4014": {
Run: CheckRepeatedIfElse,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4015": makeCallCheckerAnalyzer(checkMathIntRules),
"SA4016": {
Run: CheckSillyBitwiseOps,
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.TokenFile},
},
"SA4017": {
Run: CheckPureFunctions,
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.Purity},
},
"SA4018": {
Run: CheckSelfAssignment,
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.TokenFile, facts.Purity},
},
"SA4019": {
Run: CheckDuplicateBuildConstraints,
Requires: []*analysis.Analyzer{facts.Generated},
},
"SA4020": {
Run: CheckUnreachableTypeCases,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4021": {
Run: CheckSingleArgAppend,
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.TokenFile},
},
"SA4022": {
Run: CheckAddressIsNil,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4023": {
Run: CheckTypedNilInterface,
Requires: []*analysis.Analyzer{buildir.Analyzer, typedness.Analysis, nilness.Analysis},
},
"SA4024": {
Run: CheckBuiltinZeroComparison,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4025": {
Run: CheckIntegerDivisionEqualsZero,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4026": {
Run: CheckNegativeZeroFloat,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4027": {
Run: CheckIneffectiveURLQueryModification,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4028": {
Run: CheckModuloOne,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4029": {
Run: CheckIneffectiveSort,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4030": {
Run: CheckIneffectiveRandInt,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA4031": {
Run: CheckAllocationNilCheck,
Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer, facts.TokenFile},
},
"SA5000": {
Run: CheckNilMaps,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5001": {
Run: CheckEarlyDefer,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA5002": {
Run: CheckInfiniteEmptyLoop,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA5003": {
Run: CheckDeferInInfiniteLoop,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA5004": {
Run: CheckLoopEmptyDefault,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA5005": {
Run: CheckCyclicFinalizer,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5007": {
Run: CheckInfiniteRecursion,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5008": {
Run: CheckStructTags,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA5009": makeCallCheckerAnalyzer(checkPrintfRules),
"SA5010": {
Run: CheckImpossibleTypeAssertion,
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile},
},
"SA5011": {
Run: CheckMaybeNil,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5012": {
Run: CheckEvenSliceLength,
FactTypes: []analysis.Fact{new(evenElements)},
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6000": makeCallCheckerAnalyzer(checkRegexpMatchLoopRules),
"SA6001": {
Run: CheckMapBytesKey,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6002": makeCallCheckerAnalyzer(checkSyncPoolValueRules),
"SA6003": {
Run: CheckRangeStringRunes,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6005": {
Run: CheckToLowerToUpperComparison,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA9001": {
Run: CheckDubiousDeferInChannelRangeLoop,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA9002": {
Run: CheckNonOctalFileMode,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA9003": {
Run: CheckEmptyBranch,
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile, facts.Generated},
},
"SA9004": {
Run: CheckMissingEnumTypesInDeclaration,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
// Filtering generated code because it may include empty structs generated from data models.
"SA9005": makeCallCheckerAnalyzer(checkNoopMarshal, facts.Generated),
"SA9006": {
Run: CheckStaticBitShift,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
"SA9007": {
Run: CheckBadRemoveAll,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA9008": {
Run: CheckTypeAssertionShadowingElse,
Requires: []*analysis.Analyzer{inspect.Analyzer, buildir.Analyzer, facts.TokenFile},
},
})

View File

@@ -0,0 +1,21 @@
package staticcheck
import (
"go/ast"
"strings"
"honnef.co/go/tools/go/ast/astutil"
)
func buildTags(f *ast.File) [][]string {
var out [][]string
for _, line := range strings.Split(astutil.Preamble(f), "\n") {
if !strings.HasPrefix(line, "+build ") {
continue
}
line = strings.TrimSpace(strings.TrimPrefix(line, "+build "))
fields := strings.Fields(line)
out = append(out, fields)
}
return out
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,382 @@
// Copyright 2010 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/json 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/json.
// This is used for SA1026.
package fakejson
import (
"go/token"
"go/types"
"sort"
"strings"
"unicode"
"golang.org/x/exp/typeparams"
"honnef.co/go/tools/staticcheck/fakereflect"
)
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) string {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx]
}
return tag
}
func Marshal(v types.Type) *UnsupportedTypeError {
enc := encoder{
seen: map[fakereflect.TypeAndCanAddr]struct{}{},
}
return enc.newTypeEncoder(fakereflect.TypeAndCanAddr{Type: v}, "x")
}
// An UnsupportedTypeError is returned by Marshal when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type types.Type
Path string
}
var marshalerType = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalJSON", 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 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()
type encoder struct {
seen map[fakereflect.TypeAndCanAddr]struct{}
}
func (enc *encoder) newTypeEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
if _, ok := enc.seen[t]; ok {
return nil
}
enc.seen[t] = struct{}{}
if t.Implements(marshalerType) {
return nil
}
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(marshalerType) {
return nil
}
if t.Implements(textMarshalerType) {
return nil
}
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(textMarshalerType) {
return nil
}
switch t.Type.Underlying().(type) {
case *types.Basic, *types.Interface:
return nil
case *types.Struct:
return enc.typeFields(t, stack)
case *types.Map:
return enc.newMapEncoder(t, stack)
case *types.Slice:
return enc.newSliceEncoder(t, stack)
case *types.Array:
return enc.newArrayEncoder(t, stack)
case *types.Pointer:
// we don't have to express the pointer dereference in the path; x.f is syntactic sugar for (*x).f
return enc.newTypeEncoder(t.Elem(), stack)
default:
return &UnsupportedTypeError{t.Type, stack}
}
}
func (enc *encoder) newMapEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
if typeparams.IsTypeParam(t.Key().Type) {
// We don't know enough about the concrete instantiation to say much about the key. The only time we could make
// a definite "this key is bad" statement is if the type parameter is constrained by type terms, none of which
// are tilde terms, none of which are a basic type. In all other cases, the key might implement TextMarshaler.
// It doesn't seem worth checking for that one single case.
return enc.newTypeEncoder(t.Elem(), stack+"[k]")
}
switch t.Key().Type.Underlying().(type) {
case *types.Basic:
default:
if !t.Key().Implements(textMarshalerType) {
return &UnsupportedTypeError{
Type: t.Type,
Path: stack,
}
}
}
return enc.newTypeEncoder(t.Elem(), stack+"[k]")
}
func (enc *encoder) newSliceEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
// Byte slices get special treatment; arrays don't.
basic, ok := t.Elem().Type.Underlying().(*types.Basic)
if ok && basic.Kind() == types.Uint8 {
p := fakereflect.PtrTo(t.Elem())
if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) {
return nil
}
}
return enc.newArrayEncoder(t, stack)
}
func (enc *encoder) newArrayEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
return enc.newTypeEncoder(t.Elem(), stack+"[0]")
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
return false
}
}
return true
}
func typeByIndex(t fakereflect.TypeAndCanAddr, index []int) fakereflect.TypeAndCanAddr {
for _, i := range index {
if t.IsPtr() {
t = t.Elem()
}
t = t.Field(i).Type
}
return t
}
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
}
// A field represents a single field found in a struct.
type field struct {
name string
tag bool
index []int
typ fakereflect.TypeAndCanAddr
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func (enc *encoder) typeFields(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
var count, nextCount map[fakereflect.TypeAndCanAddr]int
// Types already visited at an earlier level.
visited := map[fakereflect.TypeAndCanAddr]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[fakereflect.TypeAndCanAddr]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.Anonymous {
t := sf.Type
if t.IsPtr() {
t = t.Elem()
}
if !sf.IsExported() && !t.IsStruct() {
// Ignore embedded fields of unexported non-struct types.
continue
}
// Do not ignore embedded fields of unexported struct types
// since they may have exported fields.
} else if !sf.IsExported() {
// Ignore unexported non-embedded fields.
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.IsPtr() {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || !ft.IsStruct() {
tagged := name != ""
if name == "" {
name = sf.Name
}
field := field{
name: name,
tag: tagged,
index: index,
typ: ft,
}
fields = append(fields, field)
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, field{name: ft.Name(), index: index, typ: ft})
}
}
}
}
sort.Slice(fields, func(i, j int) bool {
x := fields
// sort field by name, breaking ties with depth, then
// breaking ties with "name came from json tag", then
// breaking ties with index sequence.
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
})
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
for i := range fields {
f := &fields[i]
err := enc.newTypeEncoder(typeByIndex(t, f.index), stack+pathByIndex(t, f.index))
if err != nil {
return err
}
}
return nil
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order, then by presence of tag.
// That means that the first field is the dominant one. We need only check
// for error cases: two fields at top level, either both tagged or neither tagged.
if len(fields) > 1 && len(fields[0].index) == len(fields[1].index) && fields[0].tag == fields[1].tag {
return field{}, false
}
return fields[0], true
}

View File

@@ -0,0 +1,131 @@
package fakereflect
import (
"fmt"
"go/types"
"reflect"
)
type TypeAndCanAddr struct {
Type types.Type
canAddr bool
}
type StructField struct {
Index []int
Name string
Anonymous bool
Tag reflect.StructTag
f *types.Var
Type TypeAndCanAddr
}
func (sf StructField) IsExported() bool { return sf.f.Exported() }
func (t TypeAndCanAddr) Field(i int) StructField {
st := t.Type.Underlying().(*types.Struct)
f := st.Field(i)
return StructField{
f: f,
Index: []int{i},
Name: f.Name(),
Anonymous: f.Anonymous(),
Tag: reflect.StructTag(st.Tag(i)),
Type: TypeAndCanAddr{
Type: f.Type(),
canAddr: t.canAddr,
},
}
}
func (t TypeAndCanAddr) FieldByIndex(index []int) StructField {
f := t.Field(index[0])
for _, idx := range index[1:] {
f = f.Type.Field(idx)
}
f.Index = index
return f
}
func PtrTo(t TypeAndCanAddr) TypeAndCanAddr {
// Note that we don't care about canAddr here because it's irrelevant to all uses of PtrTo
return TypeAndCanAddr{Type: types.NewPointer(t.Type)}
}
func (t TypeAndCanAddr) CanAddr() bool { return t.canAddr }
func (t TypeAndCanAddr) Implements(ityp *types.Interface) bool {
return types.Implements(t.Type, ityp)
}
func (t TypeAndCanAddr) IsSlice() bool {
_, ok := t.Type.Underlying().(*types.Slice)
return ok
}
func (t TypeAndCanAddr) IsArray() bool {
_, ok := t.Type.Underlying().(*types.Array)
return ok
}
func (t TypeAndCanAddr) IsPtr() bool {
_, ok := t.Type.Underlying().(*types.Pointer)
return ok
}
func (t TypeAndCanAddr) IsInterface() bool {
_, ok := t.Type.Underlying().(*types.Interface)
return ok
}
func (t TypeAndCanAddr) IsStruct() bool {
_, ok := t.Type.Underlying().(*types.Struct)
return ok
}
func (t TypeAndCanAddr) Name() string {
named, ok := t.Type.(*types.Named)
if !ok {
return ""
}
return named.Obj().Name()
}
func (t TypeAndCanAddr) NumField() int {
return t.Type.Underlying().(*types.Struct).NumFields()
}
func (t TypeAndCanAddr) String() string {
return t.Type.String()
}
func (t TypeAndCanAddr) Key() TypeAndCanAddr {
return TypeAndCanAddr{Type: t.Type.Underlying().(*types.Map).Key()}
}
func (t TypeAndCanAddr) Elem() TypeAndCanAddr {
switch typ := t.Type.Underlying().(type) {
case *types.Pointer:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: true,
}
case *types.Slice:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: true,
}
case *types.Array:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: t.canAddr,
}
case *types.Map:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: false,
}
default:
panic(fmt.Sprintf("unhandled type %T", typ))
}
}

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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
package staticcheck
import (
"fmt"
"go/constant"
"go/types"
"net"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
)
const (
MsgInvalidHostPort = "invalid port or service name in host:port pair"
MsgInvalidUTF8 = "argument is not a valid UTF-8 encoded string"
MsgNonUniqueCutset = "cutset contains duplicate characters"
)
type Call struct {
Pass *analysis.Pass
Instr ir.CallInstruction
Args []*Argument
Parent *ir.Function
invalids []string
}
func (c *Call) Invalid(msg string) {
c.invalids = append(c.invalids, msg)
}
type Argument struct {
Value Value
invalids []string
}
type Value struct {
Value ir.Value
}
func (arg *Argument) Invalid(msg string) {
arg.invalids = append(arg.invalids, msg)
}
type CallCheck func(call *Call)
func extractConstExpectKind(v ir.Value, kind constant.Kind) *ir.Const {
k := extractConst(v)
if k == nil || k.Value == nil || k.Value.Kind() != kind {
return nil
}
return k
}
func extractConst(v ir.Value) *ir.Const {
v = irutil.Flatten(v)
switch v := v.(type) {
case *ir.Const:
return v
case *ir.MakeInterface:
return extractConst(v.X)
default:
return nil
}
}
func ValidateRegexp(v Value) error {
if c := extractConstExpectKind(v.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
if _, err := regexp.Compile(s); err != nil {
return err
}
}
return nil
}
func ValidateTimeLayout(v Value) error {
if c := extractConstExpectKind(v.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
s = strings.Replace(s, "_", " ", -1)
s = strings.Replace(s, "Z", "-", -1)
_, err := time.Parse(s, s)
if err != nil {
return err
}
}
return nil
}
func ValidateURL(v Value) error {
if c := extractConstExpectKind(v.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
_, err := url.Parse(s)
if err != nil {
return fmt.Errorf("%q is not a valid URL: %s", s, err)
}
}
return nil
}
func InvalidUTF8(v Value) bool {
if c := extractConstExpectKind(v.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
if !utf8.ValidString(s) {
return true
}
}
return false
}
func UnbufferedChannel(v Value) bool {
// TODO(dh): this check of course misses many cases of unbuffered
// channels, such as any in phi or sigma nodes. We'll eventually
// replace this function.
val := v.Value
if ct, ok := val.(*ir.ChangeType); ok {
val = ct.X
}
mk, ok := val.(*ir.MakeChan)
if !ok {
return false
}
if k, ok := mk.Size.(*ir.Const); ok && k.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(k.Value); ok && v == 0 {
return true
}
}
return false
}
func Pointer(v Value) bool {
switch v.Value.Type().Underlying().(type) {
case *types.Pointer, *types.Interface:
return true
}
return false
}
func ConvertedFromInt(v Value) bool {
conv, ok := v.Value.(*ir.Convert)
if !ok {
return false
}
b, ok := conv.X.Type().Underlying().(*types.Basic)
if !ok {
return false
}
if (b.Info() & types.IsInteger) == 0 {
return false
}
return true
}
func validEncodingBinaryType(pass *analysis.Pass, typ types.Type) bool {
typ = typ.Underlying()
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Uint8, types.Uint16, types.Uint32, types.Uint64,
types.Int8, types.Int16, types.Int32, types.Int64,
types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid:
return true
case types.Bool:
return code.IsGoVersion(pass, 8)
}
return false
case *types.Struct:
n := typ.NumFields()
for i := 0; i < n; i++ {
if !validEncodingBinaryType(pass, typ.Field(i).Type()) {
return false
}
}
return true
case *types.Array:
return validEncodingBinaryType(pass, typ.Elem())
case *types.Interface:
// we can't determine if it's a valid type or not
return true
}
return false
}
func CanBinaryMarshal(pass *analysis.Pass, v Value) bool {
typ := v.Value.Type().Underlying()
if ttyp, ok := typ.(*types.Pointer); ok {
typ = ttyp.Elem().Underlying()
}
if ttyp, ok := typ.(interface {
Elem() types.Type
}); ok {
if _, ok := ttyp.(*types.Pointer); !ok {
typ = ttyp.Elem()
}
}
return validEncodingBinaryType(pass, typ)
}
func RepeatZeroTimes(name string, arg int) CallCheck {
return func(call *Call) {
arg := call.Args[arg]
if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(k.Value); ok && v == 0 {
arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name))
}
}
}
}
func validateServiceName(s string) bool {
if len(s) < 1 || len(s) > 15 {
return false
}
if s[0] == '-' || s[len(s)-1] == '-' {
return false
}
if strings.Contains(s, "--") {
return false
}
hasLetter := false
for _, r := range s {
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
hasLetter = true
continue
}
if r >= '0' && r <= '9' {
continue
}
return false
}
return hasLetter
}
func validatePort(s string) bool {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return validateServiceName(s)
}
return n >= 0 && n <= 65535
}
func ValidHostPort(v Value) bool {
if k := extractConstExpectKind(v.Value, constant.String); k != nil {
s := constant.StringVal(k.Value)
if s == "" {
return true
}
_, port, err := net.SplitHostPort(s)
if err != nil {
return false
}
// TODO(dh): check hostname
if !validatePort(port) {
return false
}
}
return true
}
// ConvertedFrom reports whether value v was converted from type typ.
func ConvertedFrom(v Value, typ string) bool {
change, ok := v.Value.(*ir.ChangeType)
return ok && typeutil.IsType(change.X.Type(), typ)
}
func UniqueStringCutset(v Value) bool {
if c := extractConstExpectKind(v.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
rs := runeSlice(s)
if len(rs) < 2 {
return true
}
sort.Sort(rs)
for i, r := range rs[1:] {
if rs[i] == r {
return false
}
}
}
return true
}

View File

@@ -0,0 +1,58 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package staticcheck
import "strconv"
func parseStructTag(tag string) (map[string][]string, error) {
// FIXME(dh): detect missing closing quote
out := map[string][]string{}
for tag != "" {
// Skip leading space.
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// Scan to colon. A space, a quote or a control character is a syntax error.
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
// as it is simpler to inspect the tag's bytes than the tag's runes.
i = 0
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
i++
}
if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
break
}
name := string(tag[:i])
tag = tag[i+1:]
// Scan quoted string to find value.
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
break
}
qvalue := string(tag[:i+1])
tag = tag[i+1:]
value, err := strconv.Unquote(qvalue)
if err != nil {
return nil, err
}
out[name] = append(out[name], value)
}
return out, nil
}