Add staticcheck tool
This commit is contained in:
200
vendor/honnef.co/go/tools/staticcheck/analysis.go
vendored
Normal file
200
vendor/honnef.co/go/tools/staticcheck/analysis.go
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
// Code generated by generate.go. DO NOT EDIT.
|
||||
|
||||
package staticcheck
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/staticcheck/sa1000"
|
||||
"honnef.co/go/tools/staticcheck/sa1001"
|
||||
"honnef.co/go/tools/staticcheck/sa1002"
|
||||
"honnef.co/go/tools/staticcheck/sa1003"
|
||||
"honnef.co/go/tools/staticcheck/sa1004"
|
||||
"honnef.co/go/tools/staticcheck/sa1005"
|
||||
"honnef.co/go/tools/staticcheck/sa1006"
|
||||
"honnef.co/go/tools/staticcheck/sa1007"
|
||||
"honnef.co/go/tools/staticcheck/sa1008"
|
||||
"honnef.co/go/tools/staticcheck/sa1010"
|
||||
"honnef.co/go/tools/staticcheck/sa1011"
|
||||
"honnef.co/go/tools/staticcheck/sa1012"
|
||||
"honnef.co/go/tools/staticcheck/sa1013"
|
||||
"honnef.co/go/tools/staticcheck/sa1014"
|
||||
"honnef.co/go/tools/staticcheck/sa1015"
|
||||
"honnef.co/go/tools/staticcheck/sa1016"
|
||||
"honnef.co/go/tools/staticcheck/sa1017"
|
||||
"honnef.co/go/tools/staticcheck/sa1018"
|
||||
"honnef.co/go/tools/staticcheck/sa1019"
|
||||
"honnef.co/go/tools/staticcheck/sa1020"
|
||||
"honnef.co/go/tools/staticcheck/sa1021"
|
||||
"honnef.co/go/tools/staticcheck/sa1023"
|
||||
"honnef.co/go/tools/staticcheck/sa1024"
|
||||
"honnef.co/go/tools/staticcheck/sa1025"
|
||||
"honnef.co/go/tools/staticcheck/sa1026"
|
||||
"honnef.co/go/tools/staticcheck/sa1027"
|
||||
"honnef.co/go/tools/staticcheck/sa1028"
|
||||
"honnef.co/go/tools/staticcheck/sa1029"
|
||||
"honnef.co/go/tools/staticcheck/sa1030"
|
||||
"honnef.co/go/tools/staticcheck/sa1031"
|
||||
"honnef.co/go/tools/staticcheck/sa1032"
|
||||
"honnef.co/go/tools/staticcheck/sa2000"
|
||||
"honnef.co/go/tools/staticcheck/sa2001"
|
||||
"honnef.co/go/tools/staticcheck/sa2002"
|
||||
"honnef.co/go/tools/staticcheck/sa2003"
|
||||
"honnef.co/go/tools/staticcheck/sa3000"
|
||||
"honnef.co/go/tools/staticcheck/sa3001"
|
||||
"honnef.co/go/tools/staticcheck/sa4000"
|
||||
"honnef.co/go/tools/staticcheck/sa4001"
|
||||
"honnef.co/go/tools/staticcheck/sa4003"
|
||||
"honnef.co/go/tools/staticcheck/sa4004"
|
||||
"honnef.co/go/tools/staticcheck/sa4005"
|
||||
"honnef.co/go/tools/staticcheck/sa4006"
|
||||
"honnef.co/go/tools/staticcheck/sa4008"
|
||||
"honnef.co/go/tools/staticcheck/sa4009"
|
||||
"honnef.co/go/tools/staticcheck/sa4010"
|
||||
"honnef.co/go/tools/staticcheck/sa4011"
|
||||
"honnef.co/go/tools/staticcheck/sa4012"
|
||||
"honnef.co/go/tools/staticcheck/sa4013"
|
||||
"honnef.co/go/tools/staticcheck/sa4014"
|
||||
"honnef.co/go/tools/staticcheck/sa4015"
|
||||
"honnef.co/go/tools/staticcheck/sa4016"
|
||||
"honnef.co/go/tools/staticcheck/sa4017"
|
||||
"honnef.co/go/tools/staticcheck/sa4018"
|
||||
"honnef.co/go/tools/staticcheck/sa4019"
|
||||
"honnef.co/go/tools/staticcheck/sa4020"
|
||||
"honnef.co/go/tools/staticcheck/sa4021"
|
||||
"honnef.co/go/tools/staticcheck/sa4022"
|
||||
"honnef.co/go/tools/staticcheck/sa4023"
|
||||
"honnef.co/go/tools/staticcheck/sa4024"
|
||||
"honnef.co/go/tools/staticcheck/sa4025"
|
||||
"honnef.co/go/tools/staticcheck/sa4026"
|
||||
"honnef.co/go/tools/staticcheck/sa4027"
|
||||
"honnef.co/go/tools/staticcheck/sa4028"
|
||||
"honnef.co/go/tools/staticcheck/sa4029"
|
||||
"honnef.co/go/tools/staticcheck/sa4030"
|
||||
"honnef.co/go/tools/staticcheck/sa4031"
|
||||
"honnef.co/go/tools/staticcheck/sa4032"
|
||||
"honnef.co/go/tools/staticcheck/sa5000"
|
||||
"honnef.co/go/tools/staticcheck/sa5001"
|
||||
"honnef.co/go/tools/staticcheck/sa5002"
|
||||
"honnef.co/go/tools/staticcheck/sa5003"
|
||||
"honnef.co/go/tools/staticcheck/sa5004"
|
||||
"honnef.co/go/tools/staticcheck/sa5005"
|
||||
"honnef.co/go/tools/staticcheck/sa5007"
|
||||
"honnef.co/go/tools/staticcheck/sa5008"
|
||||
"honnef.co/go/tools/staticcheck/sa5009"
|
||||
"honnef.co/go/tools/staticcheck/sa5010"
|
||||
"honnef.co/go/tools/staticcheck/sa5011"
|
||||
"honnef.co/go/tools/staticcheck/sa5012"
|
||||
"honnef.co/go/tools/staticcheck/sa6000"
|
||||
"honnef.co/go/tools/staticcheck/sa6001"
|
||||
"honnef.co/go/tools/staticcheck/sa6002"
|
||||
"honnef.co/go/tools/staticcheck/sa6003"
|
||||
"honnef.co/go/tools/staticcheck/sa6005"
|
||||
"honnef.co/go/tools/staticcheck/sa6006"
|
||||
"honnef.co/go/tools/staticcheck/sa9001"
|
||||
"honnef.co/go/tools/staticcheck/sa9002"
|
||||
"honnef.co/go/tools/staticcheck/sa9003"
|
||||
"honnef.co/go/tools/staticcheck/sa9004"
|
||||
"honnef.co/go/tools/staticcheck/sa9005"
|
||||
"honnef.co/go/tools/staticcheck/sa9006"
|
||||
"honnef.co/go/tools/staticcheck/sa9007"
|
||||
"honnef.co/go/tools/staticcheck/sa9008"
|
||||
"honnef.co/go/tools/staticcheck/sa9009"
|
||||
)
|
||||
|
||||
var Analyzers = []*lint.Analyzer{
|
||||
sa1000.SCAnalyzer,
|
||||
sa1001.SCAnalyzer,
|
||||
sa1002.SCAnalyzer,
|
||||
sa1003.SCAnalyzer,
|
||||
sa1004.SCAnalyzer,
|
||||
sa1005.SCAnalyzer,
|
||||
sa1006.SCAnalyzer,
|
||||
sa1007.SCAnalyzer,
|
||||
sa1008.SCAnalyzer,
|
||||
sa1010.SCAnalyzer,
|
||||
sa1011.SCAnalyzer,
|
||||
sa1012.SCAnalyzer,
|
||||
sa1013.SCAnalyzer,
|
||||
sa1014.SCAnalyzer,
|
||||
sa1015.SCAnalyzer,
|
||||
sa1016.SCAnalyzer,
|
||||
sa1017.SCAnalyzer,
|
||||
sa1018.SCAnalyzer,
|
||||
sa1019.SCAnalyzer,
|
||||
sa1020.SCAnalyzer,
|
||||
sa1021.SCAnalyzer,
|
||||
sa1023.SCAnalyzer,
|
||||
sa1024.SCAnalyzer,
|
||||
sa1025.SCAnalyzer,
|
||||
sa1026.SCAnalyzer,
|
||||
sa1027.SCAnalyzer,
|
||||
sa1028.SCAnalyzer,
|
||||
sa1029.SCAnalyzer,
|
||||
sa1030.SCAnalyzer,
|
||||
sa1031.SCAnalyzer,
|
||||
sa1032.SCAnalyzer,
|
||||
sa2000.SCAnalyzer,
|
||||
sa2001.SCAnalyzer,
|
||||
sa2002.SCAnalyzer,
|
||||
sa2003.SCAnalyzer,
|
||||
sa3000.SCAnalyzer,
|
||||
sa3001.SCAnalyzer,
|
||||
sa4000.SCAnalyzer,
|
||||
sa4001.SCAnalyzer,
|
||||
sa4003.SCAnalyzer,
|
||||
sa4004.SCAnalyzer,
|
||||
sa4005.SCAnalyzer,
|
||||
sa4006.SCAnalyzer,
|
||||
sa4008.SCAnalyzer,
|
||||
sa4009.SCAnalyzer,
|
||||
sa4010.SCAnalyzer,
|
||||
sa4011.SCAnalyzer,
|
||||
sa4012.SCAnalyzer,
|
||||
sa4013.SCAnalyzer,
|
||||
sa4014.SCAnalyzer,
|
||||
sa4015.SCAnalyzer,
|
||||
sa4016.SCAnalyzer,
|
||||
sa4017.SCAnalyzer,
|
||||
sa4018.SCAnalyzer,
|
||||
sa4019.SCAnalyzer,
|
||||
sa4020.SCAnalyzer,
|
||||
sa4021.SCAnalyzer,
|
||||
sa4022.SCAnalyzer,
|
||||
sa4023.SCAnalyzer,
|
||||
sa4024.SCAnalyzer,
|
||||
sa4025.SCAnalyzer,
|
||||
sa4026.SCAnalyzer,
|
||||
sa4027.SCAnalyzer,
|
||||
sa4028.SCAnalyzer,
|
||||
sa4029.SCAnalyzer,
|
||||
sa4030.SCAnalyzer,
|
||||
sa4031.SCAnalyzer,
|
||||
sa4032.SCAnalyzer,
|
||||
sa5000.SCAnalyzer,
|
||||
sa5001.SCAnalyzer,
|
||||
sa5002.SCAnalyzer,
|
||||
sa5003.SCAnalyzer,
|
||||
sa5004.SCAnalyzer,
|
||||
sa5005.SCAnalyzer,
|
||||
sa5007.SCAnalyzer,
|
||||
sa5008.SCAnalyzer,
|
||||
sa5009.SCAnalyzer,
|
||||
sa5010.SCAnalyzer,
|
||||
sa5011.SCAnalyzer,
|
||||
sa5012.SCAnalyzer,
|
||||
sa6000.SCAnalyzer,
|
||||
sa6001.SCAnalyzer,
|
||||
sa6002.SCAnalyzer,
|
||||
sa6003.SCAnalyzer,
|
||||
sa6005.SCAnalyzer,
|
||||
sa6006.SCAnalyzer,
|
||||
sa9001.SCAnalyzer,
|
||||
sa9002.SCAnalyzer,
|
||||
sa9003.SCAnalyzer,
|
||||
sa9004.SCAnalyzer,
|
||||
sa9005.SCAnalyzer,
|
||||
sa9006.SCAnalyzer,
|
||||
sa9007.SCAnalyzer,
|
||||
sa9008.SCAnalyzer,
|
||||
sa9009.SCAnalyzer,
|
||||
}
|
||||
5
vendor/honnef.co/go/tools/staticcheck/doc.go
vendored
Normal file
5
vendor/honnef.co/go/tools/staticcheck/doc.go
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:generate go run ../generate.go
|
||||
|
||||
// Package staticcheck contains analyzes that find bugs and performance issues.
|
||||
// Barring the rare false positive, any code flagged by these analyzes needs to be fixed.
|
||||
package staticcheck
|
||||
370
vendor/honnef.co/go/tools/staticcheck/fakejson/encode.go
vendored
Normal file
370
vendor/honnef.co/go/tools/staticcheck/fakejson/encode.go
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
// 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/types"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"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 before, _, ok := strings.Cut(tag, ","); ok {
|
||||
return before
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func Marshal(v types.Type) *UnsupportedTypeError {
|
||||
enc := encoder{}
|
||||
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
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
// TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know
|
||||
// if this is actually required for correctness.
|
||||
seenCanAddr typeutil.Map[struct{}]
|
||||
seenCantAddr typeutil.Map[struct{}]
|
||||
}
|
||||
|
||||
func (enc *encoder) newTypeEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
|
||||
var m *typeutil.Map[struct{}]
|
||||
if t.CanAddr() {
|
||||
m = &enc.seenCanAddr
|
||||
} else {
|
||||
m = &enc.seenCantAddr
|
||||
}
|
||||
if _, ok := m.At(t.Type); ok {
|
||||
return nil
|
||||
}
|
||||
m.Set(t.Type, struct{}{})
|
||||
|
||||
if t.Implements(knowledge.Interfaces["encoding/json.Marshaler"]) {
|
||||
return nil
|
||||
}
|
||||
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding/json.Marshaler"]) {
|
||||
return nil
|
||||
}
|
||||
if t.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
return nil
|
||||
}
|
||||
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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(knowledge.Interfaces["encoding/json.Marshaler"]) && !p.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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 {
|
||||
var path strings.Builder
|
||||
for _, i := range index {
|
||||
if t.IsPtr() {
|
||||
t = t.Elem()
|
||||
}
|
||||
path.WriteString("." + t.Field(i).Name)
|
||||
t = t.Field(i).Type
|
||||
}
|
||||
return path.String()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
131
vendor/honnef.co/go/tools/staticcheck/fakereflect/fakereflect.go
vendored
Normal file
131
vendor/honnef.co/go/tools/staticcheck/fakereflect/fakereflect.go
vendored
Normal 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 := types.Unalias(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))
|
||||
}
|
||||
}
|
||||
376
vendor/honnef.co/go/tools/staticcheck/fakexml/marshal.go
vendored
Normal file
376
vendor/honnef.co/go/tools/staticcheck/fakexml/marshal.go
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
// 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/types"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/staticcheck/fakereflect"
|
||||
)
|
||||
|
||||
func Marshal(v types.Type) error {
|
||||
return NewEncoder().Encode(v)
|
||||
}
|
||||
|
||||
type Encoder struct {
|
||||
// TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know
|
||||
// if this is actually required for correctness.
|
||||
seenCanAddr typeutil.Map[struct{}]
|
||||
seenCantAddr typeutil.Map[struct{}]
|
||||
}
|
||||
|
||||
func NewEncoder() *Encoder {
|
||||
e := &Encoder{}
|
||||
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.IsPointerToTypeWithName(params.At(0).Type(), "encoding/xml.Encoder") {
|
||||
return false
|
||||
}
|
||||
if !typeutil.IsTypeWithName(params.At(1).Type(), "encoding/xml.StartElement") {
|
||||
return false
|
||||
}
|
||||
rets := fn.Type().(*types.Signature).Results()
|
||||
if rets.Len() != 1 {
|
||||
return false
|
||||
}
|
||||
if !typeutil.IsTypeWithName(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.IsTypeWithName(params.At(0).Type(), "encoding/xml.Name") {
|
||||
return false
|
||||
}
|
||||
rets := fn.Type().(*types.Signature).Results()
|
||||
if rets.Len() != 2 {
|
||||
return false
|
||||
}
|
||||
if !typeutil.IsTypeWithName(rets.At(0).Type(), "encoding/xml.Attr") {
|
||||
return false
|
||||
}
|
||||
if !typeutil.IsTypeWithName(rets.At(1).Type(), "error") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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 {
|
||||
var m *typeutil.Map[struct{}]
|
||||
if val.CanAddr() {
|
||||
m = &e.seenCanAddr
|
||||
} else {
|
||||
m = &e.seenCantAddr
|
||||
}
|
||||
if _, ok := m.At(val.Type); ok {
|
||||
return nil
|
||||
}
|
||||
m.Set(val.Type, 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(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
return nil
|
||||
}
|
||||
if val.CanAddr() {
|
||||
pv := fakereflect.PtrTo(val)
|
||||
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.CanAddr() {
|
||||
pv := fakereflect.PtrTo(val)
|
||||
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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.IsTypeWithName(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 {
|
||||
var path strings.Builder
|
||||
for _, i := range index {
|
||||
if t.IsPtr() {
|
||||
t = t.Elem()
|
||||
}
|
||||
path.WriteString("." + t.Field(i).Name)
|
||||
t = t.Field(i).Type
|
||||
}
|
||||
return path.String()
|
||||
}
|
||||
|
||||
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(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
continue
|
||||
}
|
||||
if vf.CanAddr() {
|
||||
pv := fakereflect.PtrTo(vf)
|
||||
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
|
||||
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 t, ok := vf.Type.(*types.Slice); (ok && types.Identical(t.Elem(), types.Typ[types.Byte])) || types.Identical(vf.Type, types.Typ[types.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)
|
||||
}
|
||||
381
vendor/honnef.co/go/tools/staticcheck/fakexml/typeinfo.go
vendored
Normal file
381
vendor/honnef.co/go/tools/staticcheck/fakexml/typeinfo.go
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"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{}
|
||||
if typ.IsStruct() && !typeutil.IsTypeWithName(typ.Type, "encoding/xml.Name") {
|
||||
n := typ.NumField()
|
||||
for i := range n {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 := range minl {
|
||||
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
|
||||
}
|
||||
33
vendor/honnef.co/go/tools/staticcheck/fakexml/xml.go
vendored
Normal file
33
vendor/honnef.co/go/tools/staticcheck/fakexml/xml.go
vendored
Normal 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
|
||||
}
|
||||
46
vendor/honnef.co/go/tools/staticcheck/sa1000/sa1000.go
vendored
Normal file
46
vendor/honnef.co/go/tools/staticcheck/sa1000/sa1000.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package sa1000
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"regexp"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1000",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid regular expression`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"regexp.MustCompile": check,
|
||||
"regexp.Compile": check,
|
||||
"regexp.Match": check,
|
||||
"regexp.MatchReader": check,
|
||||
"regexp.MatchString": check,
|
||||
}
|
||||
|
||||
func check(call *callcheck.Call) {
|
||||
arg := call.Args[0]
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
|
||||
s := constant.StringVal(c.Value)
|
||||
if _, err := regexp.Compile(s); err != nil {
|
||||
arg.Invalid(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
83
vendor/honnef.co/go/tools/staticcheck/sa1001/sa1001.go
vendored
Normal file
83
vendor/honnef.co/go/tools/staticcheck/sa1001/sa1001.go
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package sa1001
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
htmltemplate "html/template"
|
||||
"strings"
|
||||
texttemplate "text/template"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1001",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid template`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`
|
||||
(CallExpr
|
||||
(Symbol
|
||||
name@(Or
|
||||
"(*text/template.Template).Parse"
|
||||
"(*html/template.Template).Parse"))
|
||||
[s])`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, query) {
|
||||
name := m.State["name"].(string)
|
||||
var kind string
|
||||
switch name {
|
||||
case "(*text/template.Template).Parse":
|
||||
kind = "text"
|
||||
case "(*html/template.Template).Parse":
|
||||
kind = "html"
|
||||
}
|
||||
|
||||
call := node.(*ast.CallExpr)
|
||||
sel := call.Fun.(*ast.SelectorExpr)
|
||||
if !code.IsCallToAny(pass, sel.X, "text/template.New", "html/template.New") {
|
||||
// TODO(dh): this is a cheap workaround for templates with
|
||||
// different delims. A better solution with less false
|
||||
// negatives would use data flow analysis to see where the
|
||||
// template comes from and where it has been
|
||||
continue
|
||||
}
|
||||
|
||||
s, ok := code.ExprToString(pass, m.State["s"].(ast.Expr))
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
switch kind {
|
||||
case "text":
|
||||
_, err = texttemplate.New("").Parse(s)
|
||||
case "html":
|
||||
_, err = htmltemplate.New("").Parse(s)
|
||||
}
|
||||
if err != nil {
|
||||
// TODO(dominikh): whitelist other parse errors, if any
|
||||
if strings.Contains(err.Error(), "unexpected") ||
|
||||
strings.Contains(err.Error(), "bad character") {
|
||||
report.Report(pass, call.Args[knowledge.Arg("(*text/template.Template).Parse.text")], err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
45
vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go
vendored
Normal file
45
vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package sa1002
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1002",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid format in \'time.Parse\'`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"time.Parse": func(call *callcheck.Call) {
|
||||
arg := call.Args[knowledge.Arg("time.Parse.layout")]
|
||||
if c := callcheck.ExtractConstExpectKind(arg.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 {
|
||||
arg.Invalid(err.Error())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
92
vendor/honnef.co/go/tools/staticcheck/sa1003/sa1003.go
vendored
Normal file
92
vendor/honnef.co/go/tools/staticcheck/sa1003/sa1003.go
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package sa1003
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"go/version"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1003",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkEncodingBinaryRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Unsupported argument to functions in \'encoding/binary\'`,
|
||||
Text: `The \'encoding/binary\' package can only serialize types with known sizes.
|
||||
This precludes the use of the \'int\' and \'uint\' types, as their sizes
|
||||
differ on different architectures. Furthermore, it doesn't support
|
||||
serializing maps, channels, strings, or functions.
|
||||
|
||||
Before Go 1.8, \'bool\' wasn't supported, either.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkEncodingBinaryRules = map[string]callcheck.Check{
|
||||
"encoding/binary.Write": func(call *callcheck.Call) {
|
||||
arg := call.Args[knowledge.Arg("encoding/binary.Write.data")]
|
||||
if !CanBinaryMarshal(call.Pass, call.Parent, arg.Value) {
|
||||
arg.Invalid(fmt.Sprintf("value of type %s cannot be used with binary.Write", arg.Value.Value.Type()))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func CanBinaryMarshal(pass *analysis.Pass, node code.Positioner, v callcheck.Value) bool {
|
||||
typ := v.Value.Type().Underlying()
|
||||
if ttyp, ok := typ.(*types.Pointer); ok {
|
||||
typ = ttyp.Elem().Underlying()
|
||||
}
|
||||
if ttyp, ok := types.Unalias(typ).(interface {
|
||||
Elem() types.Type
|
||||
}); ok {
|
||||
if _, ok := ttyp.(*types.Pointer); !ok {
|
||||
typ = ttyp.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
return validEncodingBinaryType(pass, node, typ)
|
||||
}
|
||||
|
||||
func validEncodingBinaryType(pass *analysis.Pass, node code.Positioner, 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 version.Compare(code.StdlibVersion(pass, node), "go1.8") >= 0
|
||||
}
|
||||
return false
|
||||
case *types.Struct:
|
||||
n := typ.NumFields()
|
||||
for i := range n {
|
||||
if !validEncodingBinaryType(pass, node, typ.Field(i).Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *types.Array:
|
||||
return validEncodingBinaryType(pass, node, typ.Elem())
|
||||
case *types.Interface:
|
||||
// we can't determine if it's a valid type or not
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
72
vendor/honnef.co/go/tools/staticcheck/sa1004/sa1004.go
vendored
Normal file
72
vendor/honnef.co/go/tools/staticcheck/sa1004/sa1004.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package sa1004
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1004",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Suspiciously small untyped constant in \'time.Sleep\'`,
|
||||
Text: `The \'time\'.Sleep function takes a \'time.Duration\' as its only argument.
|
||||
Durations are expressed in nanoseconds. Thus, calling \'time.Sleep(1)\'
|
||||
will sleep for 1 nanosecond. This is a common source of bugs, as sleep
|
||||
functions in other languages often accept seconds or milliseconds.
|
||||
|
||||
The \'time\' package provides constants such as \'time.Second\' to express
|
||||
large durations. These can be combined with arithmetic to express
|
||||
arbitrary durations, for example \'5 * time.Second\' for 5 seconds.
|
||||
|
||||
If you truly meant to sleep for a tiny amount of time, use
|
||||
\'n * time.Nanosecond\' to signal to Staticcheck that you did mean to sleep
|
||||
for some amount of nanoseconds.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
checkTimeSleepConstantPatternQ = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`)
|
||||
checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`)
|
||||
checkTimeSleepConstantPatternRs = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`)
|
||||
)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, m := range code.Matches(pass, checkTimeSleepConstantPatternQ) {
|
||||
n, ok := constant.Int64Val(m.State["value"].(types.TypeAndValue).Value)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if n == 0 || n > 120 {
|
||||
// time.Sleep(0) is a seldom used pattern in concurrency
|
||||
// tests. >120 might be intentional. 120 was chosen
|
||||
// because the user could've meant 2 minutes.
|
||||
continue
|
||||
}
|
||||
|
||||
lit := m.State["lit"].(ast.Node)
|
||||
report.Report(pass, lit,
|
||||
fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes(
|
||||
edit.Fix("Explicitly use nanoseconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit})),
|
||||
edit.Fix("Use seconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit}))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
65
vendor/honnef.co/go/tools/staticcheck/sa1005/sa1005.go
vendored
Normal file
65
vendor/honnef.co/go/tools/staticcheck/sa1005/sa1005.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package sa1005
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1005",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid first argument to \'exec.Command\'`,
|
||||
Text: `\'os/exec\' runs programs directly (using variants of the fork and exec
|
||||
system calls on Unix systems). This shouldn't be confused with running
|
||||
a command in a shell. The shell will allow for features such as input
|
||||
redirection, pipes, and general scripting. The shell is also
|
||||
responsible for splitting the user's input into a program name and its
|
||||
arguments. For example, the equivalent to
|
||||
|
||||
ls / /tmp
|
||||
|
||||
would be
|
||||
|
||||
exec.Command("ls", "/", "/tmp")
|
||||
|
||||
If you want to run a command in a shell, consider using something like
|
||||
the following – but be aware that not all systems, particularly
|
||||
Windows, will have a \'/bin/sh\' program:
|
||||
|
||||
exec.Command("/bin/sh", "-c", "ls | grep Awesome")`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`(CallExpr (Symbol "os/exec.Command") arg1:_)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, m := range code.Matches(pass, query) {
|
||||
arg1 := m.State["arg1"].(ast.Expr)
|
||||
val, ok := code.ExprToString(pass, arg1)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(val, " ") || strings.Contains(val, `\`) || strings.Contains(val, "/") {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, arg1,
|
||||
"first argument to exec.Command looks like a shell command, but a program name or path are expected")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
115
vendor/honnef.co/go/tools/staticcheck/sa1006/sa1006.go
vendored
Normal file
115
vendor/honnef.co/go/tools/staticcheck/sa1006/sa1006.go
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package sa1006
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1006",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'Printf\' with dynamic first argument and no further arguments`,
|
||||
Text: `Using \'fmt.Printf\' with a dynamic first argument can lead to unexpected
|
||||
output. The first argument is a format string, where certain character
|
||||
combinations have special meaning. If, for example, a user were to
|
||||
enter a string such as
|
||||
|
||||
Interest rate: 5%
|
||||
|
||||
and you printed it with
|
||||
|
||||
fmt.Printf(s)
|
||||
|
||||
it would lead to the following output:
|
||||
|
||||
Interest rate: 5%!(NOVERB).
|
||||
|
||||
Similarly, forming the first parameter via string concatenation with
|
||||
user input should be avoided for the same reason. When printing user
|
||||
input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb
|
||||
and pass the string as an argument.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query1 = pattern.MustParse(`
|
||||
(CallExpr
|
||||
(Symbol
|
||||
name@(Or
|
||||
"fmt.Errorf"
|
||||
"fmt.Printf"
|
||||
"fmt.Sprintf"
|
||||
"log.Fatalf"
|
||||
"log.Panicf"
|
||||
"log.Printf"
|
||||
"(*log.Logger).Printf"
|
||||
"(*testing.common).Logf"
|
||||
"(*testing.common).Errorf"
|
||||
"(*testing.common).Fatalf"
|
||||
"(*testing.common).Skipf"
|
||||
"(testing.TB).Logf"
|
||||
"(testing.TB).Errorf"
|
||||
"(testing.TB).Fatalf"
|
||||
"(testing.TB).Skipf"))
|
||||
format:[])
|
||||
`)
|
||||
|
||||
var query2 = pattern.MustParse(`(CallExpr (Symbol "fmt.Fprintf") _:format:[])`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, query1, query2) {
|
||||
call := node.(*ast.CallExpr)
|
||||
name, ok := m.State["name"].(string)
|
||||
if !ok {
|
||||
name = "fmt.Fprintf"
|
||||
}
|
||||
|
||||
arg := m.State["format"].(ast.Expr)
|
||||
switch arg.(type) {
|
||||
case *ast.CallExpr, *ast.Ident:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := pass.TypesInfo.TypeOf(arg).(*types.Tuple); ok {
|
||||
// the called function returns multiple values and got
|
||||
// splatted into the call. for all we know, it is
|
||||
// returning good arguments.
|
||||
continue
|
||||
}
|
||||
|
||||
var alt string
|
||||
if name == "fmt.Errorf" {
|
||||
// The alternative to fmt.Errorf isn't fmt.Error but errors.New
|
||||
alt = "errors.New"
|
||||
} else {
|
||||
// This can be either a function call like log.Printf or a method call with an
|
||||
// arbitrarily complex selector, such as foo.bar[0].Printf. In either case,
|
||||
// all we have to do is remove the final 'f' from the existing call.Fun
|
||||
// expression.
|
||||
alt = report.Render(pass, call.Fun)
|
||||
alt = alt[:len(alt)-1]
|
||||
}
|
||||
report.Report(pass, call,
|
||||
"printf-style function with dynamic format string and no further arguments should use print-style function instead",
|
||||
report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead of %s", alt, name), edit.ReplaceWithString(call.Fun, alt))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
43
vendor/honnef.co/go/tools/staticcheck/sa1007/sa1007.go
vendored
Normal file
43
vendor/honnef.co/go/tools/staticcheck/sa1007/sa1007.go
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package sa1007
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"net/url"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1007",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid URL in \'net/url.Parse\'`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"net/url.Parse": func(call *callcheck.Call) {
|
||||
arg := call.Args[knowledge.Arg("net/url.Parse.rawurl")]
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
|
||||
s := constant.StringVal(c.Value)
|
||||
_, err := url.Parse(s)
|
||||
if err != nil {
|
||||
arg.Invalid(fmt.Sprintf("%q is not a valid URL: %s", s, err))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
111
vendor/honnef.co/go/tools/staticcheck/sa1008/sa1008.go
vendored
Normal file
111
vendor/honnef.co/go/tools/staticcheck/sa1008/sa1008.go
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package sa1008
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1008",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Non-canonical key in \'http.Header\' map`,
|
||||
Text: `Keys in \'http.Header\' maps are canonical, meaning they follow a specific
|
||||
combination of uppercase and lowercase letters. Methods such as
|
||||
\'http.Header.Add\' and \'http.Header.Del\' convert inputs into this canonical
|
||||
form before manipulating the map.
|
||||
|
||||
When manipulating \'http.Header\' maps directly, as opposed to using the
|
||||
provided methods, care should be taken to stick to canonical form in
|
||||
order to avoid inconsistencies. The following piece of code
|
||||
demonstrates one such inconsistency:
|
||||
|
||||
h := http.Header{}
|
||||
h["etag"] = []string{"1234"}
|
||||
h.Add("etag", "5678")
|
||||
fmt.Println(h)
|
||||
|
||||
// Output:
|
||||
// map[Etag:[5678] etag:[1234]]
|
||||
|
||||
The easiest way of obtaining the canonical form of a key is to use
|
||||
\'http.CanonicalHeaderKey\'.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
return false
|
||||
}
|
||||
if assign, ok := node.(*ast.AssignStmt); ok {
|
||||
// TODO(dh): This risks missing some Header reads, for
|
||||
// example in `h1["foo"] = h2["foo"]` – these edge
|
||||
// cases are probably rare enough to ignore for now.
|
||||
for _, expr := range assign.Lhs {
|
||||
op, ok := expr.(*ast.IndexExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
op, ok := node.(*ast.IndexExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if !code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
|
||||
return true
|
||||
}
|
||||
s, ok := code.ExprToString(pass, op.Index)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
canonical := http.CanonicalHeaderKey(s)
|
||||
if s == canonical {
|
||||
return true
|
||||
}
|
||||
var fix analysis.SuggestedFix
|
||||
switch op.Index.(type) {
|
||||
case *ast.BasicLit:
|
||||
fix = edit.Fix("Canonicalize header key", edit.ReplaceWithString(op.Index, strconv.Quote(canonical)))
|
||||
case *ast.Ident:
|
||||
call := &ast.CallExpr{
|
||||
Fun: edit.Selector("http", "CanonicalHeaderKey"),
|
||||
Args: []ast.Expr{op.Index},
|
||||
}
|
||||
fix = edit.Fix("Wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
|
||||
}
|
||||
msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s)
|
||||
if fix.Message != "" {
|
||||
report.Report(pass, op, msg, report.Fixes(fix))
|
||||
} else {
|
||||
report.Report(pass, op, msg)
|
||||
}
|
||||
return true
|
||||
}
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn)
|
||||
return nil, nil
|
||||
}
|
||||
53
vendor/honnef.co/go/tools/staticcheck/sa1010/sa1010.go
vendored
Normal file
53
vendor/honnef.co/go/tools/staticcheck/sa1010/sa1010.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package sa1010
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1010",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkRegexpFindAllRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'(*regexp.Regexp).FindAll\' called with \'n == 0\', which will always return zero results`,
|
||||
Text: `If \'n >= 0\', the function returns at most \'n\' matches/submatches. To
|
||||
return all results, specify a negative number.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkRegexpFindAllRules = map[string]callcheck.Check{
|
||||
"(*regexp.Regexp).FindAll": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllIndex": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllString": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllStringIndex": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllStringSubmatch": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllStringSubmatchIndex": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllSubmatch": RepeatZeroTimes("a FindAll method", 1),
|
||||
"(*regexp.Regexp).FindAllSubmatchIndex": RepeatZeroTimes("a FindAll method", 1),
|
||||
}
|
||||
|
||||
func RepeatZeroTimes(name string, arg int) callcheck.Check {
|
||||
return func(call *callcheck.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
vendor/honnef.co/go/tools/staticcheck/sa1011/sa1011.go
vendored
Normal file
47
vendor/honnef.co/go/tools/staticcheck/sa1011/sa1011.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package sa1011
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"unicode/utf8"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1011",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkUTF8CutsetRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Various methods in the \"strings\" package expect valid UTF-8, but invalid input is provided`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkUTF8CutsetRules = map[string]callcheck.Check{
|
||||
"strings.IndexAny": check,
|
||||
"strings.LastIndexAny": check,
|
||||
"strings.ContainsAny": check,
|
||||
"strings.Trim": check,
|
||||
"strings.TrimLeft": check,
|
||||
"strings.TrimRight": check,
|
||||
}
|
||||
|
||||
func check(call *callcheck.Call) {
|
||||
arg := call.Args[1]
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
|
||||
s := constant.StringVal(c.Value)
|
||||
if !utf8.ValidString(s) {
|
||||
arg.Invalid("argument is not a valid UTF-8 encoded string")
|
||||
}
|
||||
}
|
||||
}
|
||||
65
vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go
vendored
Normal file
65
vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package sa1012
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1012",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Symbol _) (Builtin "nil"):_)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
todo := &ast.CallExpr{
|
||||
Fun: edit.Selector("context", "TODO"),
|
||||
}
|
||||
bg := &ast.CallExpr{
|
||||
Fun: edit.Selector("context", "Background"),
|
||||
}
|
||||
for node, m := range code.Matches(pass, checkNilContextQ) {
|
||||
call := node.(*ast.CallExpr)
|
||||
fun, ok := m.State["fun"].(*types.Func)
|
||||
if !ok {
|
||||
// it might also be a builtin
|
||||
continue
|
||||
}
|
||||
sig := fun.Type().(*types.Signature)
|
||||
if sig.Params().Len() == 0 {
|
||||
// Our CallExpr might've matched a method expression, like
|
||||
// (*T).Foo(nil) – here, nil isn't the first argument of
|
||||
// the Foo method, but the method receiver.
|
||||
continue
|
||||
}
|
||||
if !typeutil.IsTypeWithName(sig.Params().At(0).Type(), "context.Context") {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, call.Args[0],
|
||||
"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes(
|
||||
edit.Fix("Use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)),
|
||||
edit.Fix("Use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
47
vendor/honnef.co/go/tools/staticcheck/sa1013/sa1013.go
vendored
Normal file
47
vendor/honnef.co/go/tools/staticcheck/sa1013/sa1013.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package sa1013
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1013",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'io.Seeker.Seek\' is being called with the whence constant as the first argument, but it should be the second`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
checkSeekerQ = pattern.MustParse(`(CallExpr fun@(SelectorExpr _ (Ident "Seek")) [arg1@(SelectorExpr _ (Symbol (Or "io.SeekStart" "io.SeekCurrent" "io.SeekEnd"))) arg2])`)
|
||||
checkSeekerR = pattern.MustParse(`(CallExpr fun [arg2 arg1])`)
|
||||
)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, checkSeekerQ) {
|
||||
if !code.IsMethod(pass, m.State["fun"].(*ast.SelectorExpr), "Seek", knowledge.Signatures["(io.Seeker).Seek"]) {
|
||||
continue
|
||||
}
|
||||
edits := code.EditMatch(pass, node, m, checkSeekerR)
|
||||
report.Report(pass, node, "the first argument of io.Seeker is the offset, but an io.Seek* constant is being used instead",
|
||||
report.Fixes(edit.Fix("Swap arguments", edits...)))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
52
vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go
vendored
Normal file
52
vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package sa1014
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1014",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkUnmarshalPointerRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkUnmarshalPointerRules = map[string]callcheck.Check{
|
||||
"encoding/xml.Unmarshal": unmarshalPointer("xml.Unmarshal", 1),
|
||||
"(*encoding/xml.Decoder).Decode": unmarshalPointer("Decode", 0),
|
||||
"(*encoding/xml.Decoder).DecodeElement": unmarshalPointer("DecodeElement", 0),
|
||||
"encoding/json.Unmarshal": unmarshalPointer("json.Unmarshal", 1),
|
||||
"(*encoding/json.Decoder).Decode": unmarshalPointer("Decode", 0),
|
||||
}
|
||||
|
||||
func unmarshalPointer(name string, arg int) callcheck.Check {
|
||||
return func(call *callcheck.Call) {
|
||||
if !Pointer(call.Args[arg].Value) {
|
||||
call.Args[arg].Invalid(fmt.Sprintf("%s expects to unmarshal into a pointer, but the provided value is not a pointer", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Pointer(v callcheck.Value) bool {
|
||||
switch v.Value.Type().Underlying().(type) {
|
||||
case *types.Pointer, *types.Interface:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
69
vendor/honnef.co/go/tools/staticcheck/sa1015/sa1015.go
vendored
Normal file
69
vendor/honnef.co/go/tools/staticcheck/sa1015/sa1015.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package sa1015
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/version"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1015",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using \'time.Tick\' in a way that will leak. Consider using \'time.NewTicker\', and only use \'time.Tick\' in tests, commands and endless functions`,
|
||||
|
||||
Text: `Before Go 1.23, \'time.Ticker\'s had to be closed to be able to be garbage
|
||||
collected. Since \'time.Tick\' doesn't make it possible to close the underlying
|
||||
ticker, using it repeatedly would leak memory.
|
||||
|
||||
Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
if fn.Pos() == token.NoPos || version.Compare(code.StdlibVersion(pass, fn), "go1.23") >= 0 {
|
||||
// Beginning with Go 1.23, the GC is able to collect unreferenced, unclosed
|
||||
// tickers, which makes time.Tick safe(r) to use.
|
||||
//
|
||||
// When we don't have a valid position, we err on the side of false negatives.
|
||||
// This shouldn't actually lead to any false negatives, as no functions
|
||||
// without valid positions (such as the synthesized init function) should be
|
||||
// able to use time.Tick.
|
||||
continue
|
||||
}
|
||||
|
||||
if code.IsMainLike(pass) || code.IsInTest(pass, fn) {
|
||||
continue
|
||||
}
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok || !irutil.IsCallTo(call.Common(), "time.Tick") {
|
||||
continue
|
||||
}
|
||||
if !irutil.Terminates(call.Parent()) {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, call, "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
115
vendor/honnef.co/go/tools/staticcheck/sa1016/sa1016.go
vendored
Normal file
115
vendor/honnef.co/go/tools/staticcheck/sa1016/sa1016.go
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package sa1016
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1016",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Trapping a signal that cannot be trapped`,
|
||||
Text: `Not all signals can be intercepted by a process. Specifically, on
|
||||
UNIX-like systems, the \'syscall.SIGKILL\' and \'syscall.SIGSTOP\' signals are
|
||||
never passed to the process, but instead handled directly by the
|
||||
kernel. It is therefore pointless to try and handle these signals.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`
|
||||
(CallExpr
|
||||
(Symbol
|
||||
(Or
|
||||
"os/signal.Ignore"
|
||||
"os/signal.Notify"
|
||||
"os/signal.Reset"))
|
||||
_)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
isSignal := func(pass *analysis.Pass, expr ast.Expr, name string) bool {
|
||||
if expr, ok := expr.(*ast.SelectorExpr); ok {
|
||||
return code.SelectorName(pass, expr) == name
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for node := range code.Matches(pass, query) {
|
||||
call := node.(*ast.CallExpr)
|
||||
hasSigterm := false
|
||||
for _, arg := range call.Args {
|
||||
if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
|
||||
arg = conv.Args[0]
|
||||
}
|
||||
|
||||
if isSignal(pass, arg, "syscall.SIGTERM") {
|
||||
hasSigterm = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
for i, arg := range call.Args {
|
||||
if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
|
||||
arg = conv.Args[0]
|
||||
}
|
||||
|
||||
if isSignal(pass, arg, "os.Kill") || isSignal(pass, arg, "syscall.SIGKILL") {
|
||||
var fixes []analysis.SuggestedFix
|
||||
if !hasSigterm {
|
||||
nargs := make([]ast.Expr, len(call.Args))
|
||||
for j, a := range call.Args {
|
||||
if i == j {
|
||||
nargs[j] = edit.Selector("syscall", "SIGTERM")
|
||||
} else {
|
||||
nargs[j] = a
|
||||
}
|
||||
}
|
||||
ncall := *call
|
||||
ncall.Args = nargs
|
||||
fixes = append(fixes, edit.Fix(fmt.Sprintf("Use syscall.SIGTERM instead of %s", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
|
||||
}
|
||||
nargs := make([]ast.Expr, 0, len(call.Args))
|
||||
for j, a := range call.Args {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
nargs = append(nargs, a)
|
||||
}
|
||||
ncall := *call
|
||||
ncall.Args = nargs
|
||||
fixes = append(fixes, edit.Fix(fmt.Sprintf("Remove %s from list of arguments", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
|
||||
report.Report(pass, arg, fmt.Sprintf("%s cannot be trapped (did you mean syscall.SIGTERM?)", report.Render(pass, arg)), report.Fixes(fixes...))
|
||||
}
|
||||
if isSignal(pass, arg, "syscall.SIGSTOP") {
|
||||
nargs := make([]ast.Expr, 0, len(call.Args)-1)
|
||||
for j, a := range call.Args {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
nargs = append(nargs, a)
|
||||
}
|
||||
ncall := *call
|
||||
ncall.Args = nargs
|
||||
report.Report(pass, arg, "syscall.SIGSTOP cannot be trapped", report.Fixes(edit.Fix("Remove syscall.SIGSTOP from list of arguments", edit.ReplaceWithNode(pass.Fset, call, &ncall))))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
64
vendor/honnef.co/go/tools/staticcheck/sa1017/sa1017.go
vendored
Normal file
64
vendor/honnef.co/go/tools/staticcheck/sa1017/sa1017.go
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package sa1017
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1017",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Channels used with \'os/signal.Notify\' should be buffered`,
|
||||
Text: `The \'os/signal\' package uses non-blocking channel sends when delivering
|
||||
signals. If the receiving end of the channel isn't ready and the
|
||||
channel is either unbuffered or full, the signal will be dropped. To
|
||||
avoid missing signals, the channel should be buffered and of the
|
||||
appropriate size. For a channel used for notification of just one
|
||||
signal value, a buffer of size 1 is sufficient.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"os/signal.Notify": func(call *callcheck.Call) {
|
||||
arg := call.Args[knowledge.Arg("os/signal.Notify.c")]
|
||||
if isUnbufferedChannel(arg.Value) {
|
||||
arg.Invalid("the channel used with signal.Notify should be buffered")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func isUnbufferedChannel(v callcheck.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
|
||||
}
|
||||
47
vendor/honnef.co/go/tools/staticcheck/sa1018/sa1018.go
vendored
Normal file
47
vendor/honnef.co/go/tools/staticcheck/sa1018/sa1018.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package sa1018
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1018",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'strings.Replace\' called with \'n == 0\', which does nothing`,
|
||||
Text: `With \'n == 0\', zero instances will be replaced. To replace all
|
||||
instances, use a negative number, or use \'strings.ReplaceAll\'.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"strings.Replace": check("strings.Replace", 3),
|
||||
"bytes.Replace": check("bytes.Replace", 3),
|
||||
}
|
||||
|
||||
func check(name string, arg int) callcheck.Check {
|
||||
return func(call *callcheck.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
212
vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go
vendored
Normal file
212
vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package sa1019
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"go/version"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/deprecated"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1019",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, deprecated.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using a deprecated function, variable, constant or field`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityDeprecated,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func formatGoVersion(s string) string {
|
||||
return "Go " + strings.TrimPrefix(s, "go")
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
deprs := pass.ResultOf[deprecated.Analyzer].(deprecated.Result)
|
||||
|
||||
// Selectors can appear outside of function literals, e.g. when
|
||||
// declaring package level variables.
|
||||
|
||||
isStdlibPath := func(path string) bool {
|
||||
// Modules with no dot in the first path element are reserved for the standard library and tooling.
|
||||
// This is the best we can currently do.
|
||||
// Nobody tells us which import paths are part of the standard library.
|
||||
//
|
||||
// We check the entire path instead of just the first path element, because the standard library doesn't contain paths with any dots, anyway.
|
||||
|
||||
return !strings.Contains(path, ".")
|
||||
}
|
||||
|
||||
handleDeprecation := func(depr *deprecated.IsDeprecated, node ast.Node, deprecatedObjName string, pkgPath string, tfn types.Object) {
|
||||
std, ok := knowledge.StdlibDeprecations[deprecatedObjName]
|
||||
if !ok && isStdlibPath(pkgPath) {
|
||||
// Deprecated object in the standard library, but we don't know the details of the deprecation.
|
||||
// Don't flag it at all, to avoid flagging an object that was deprecated in 1.N when targeting 1.N-1.
|
||||
// See https://staticcheck.dev/issues/1108 for the background on this.
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
// In the past, we made use of the AlternativeAvailableSince field. If a function was deprecated in Go
|
||||
// 1.6 and an alternative had been available in Go 1.0, then we'd recommend using the alternative even
|
||||
// if targeting Go 1.2. The idea was to suggest writing future-proof code by using already-existing
|
||||
// alternatives. This had a major flaw, however: the user would need to use at least Go 1.6 for
|
||||
// Staticcheck to know that the function had been deprecated. Thus, targeting Go 1.2 and using Go 1.2
|
||||
// would behave differently from targeting Go 1.2 and using Go 1.6. This is especially a problem if the
|
||||
// user tries to ignore the warning. Depending on the Go version in use, the ignore directive may or may
|
||||
// not match, causing a warning of its own.
|
||||
//
|
||||
// To avoid this issue, we no longer try to be smart. We now only compare the targeted version against
|
||||
// the version that deprecated an object.
|
||||
//
|
||||
// Unfortunately, this issue also applies to AlternativeAvailableSince == DeprecatedNeverUse. Even though it
|
||||
// is only applied to seriously flawed API, such as broken cryptography, users may wish to ignore those
|
||||
// warnings.
|
||||
//
|
||||
// See also https://staticcheck.dev/issues/1318.
|
||||
if version.Compare(code.StdlibVersion(pass, node), std.DeprecatedSince) == -1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tfn != nil {
|
||||
if _, ok := deprs.Objects[tfn]; ok {
|
||||
// functions that are deprecated may use deprecated
|
||||
// symbols
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
switch std.AlternativeAvailableSince {
|
||||
case knowledge.DeprecatedNeverUse:
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s has been deprecated since %s because it shouldn't be used: %s",
|
||||
report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg))
|
||||
case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger:
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s has been deprecated since %s: %s",
|
||||
report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg))
|
||||
default:
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s has been deprecated since %s and an alternative has been available since %s: %s",
|
||||
report.Render(pass, node), formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg))
|
||||
}
|
||||
} else {
|
||||
report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", report.Render(pass, node), depr.Msg))
|
||||
}
|
||||
}
|
||||
|
||||
var tfn types.Object
|
||||
stack := 0
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
stack--
|
||||
return false
|
||||
}
|
||||
stack++
|
||||
if stack == 1 {
|
||||
tfn = nil
|
||||
}
|
||||
if fn, ok := node.(*ast.FuncDecl); ok {
|
||||
tfn = pass.TypesInfo.ObjectOf(fn.Name)
|
||||
}
|
||||
|
||||
// FIXME(dh): this misses dot-imported objects
|
||||
sel, ok := node.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
obj := pass.TypesInfo.ObjectOf(sel.Sel)
|
||||
if obj_, ok := obj.(*types.Func); ok {
|
||||
obj = obj_.Origin()
|
||||
}
|
||||
if obj.Pkg() == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if obj.Pkg() == pass.Pkg {
|
||||
// A package is allowed to use its own deprecated objects
|
||||
return true
|
||||
}
|
||||
|
||||
// A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
|
||||
// generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
|
||||
// and "foo_test".
|
||||
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
|
||||
// foo_test (the external tests of foo) can use objects from foo.
|
||||
return true
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo.
|
||||
return true
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
|
||||
// foo.test (the main package of foo's tests) can use objects from foo's external tests.
|
||||
return true
|
||||
}
|
||||
|
||||
if depr, ok := deprs.Objects[obj]; ok {
|
||||
handleDeprecation(depr, sel, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn2 := func(node ast.Node) {
|
||||
spec := node.(*ast.ImportSpec)
|
||||
var imp *types.Package
|
||||
if spec.Name != nil {
|
||||
imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported()
|
||||
} else {
|
||||
imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported()
|
||||
}
|
||||
|
||||
p := spec.Path.Value
|
||||
path := p[1 : len(p)-1]
|
||||
if depr, ok := deprs.Packages[imp]; ok {
|
||||
if path == "github.com/golang/protobuf/proto" {
|
||||
gen, ok := code.Generator(pass, spec.Path.Pos())
|
||||
if ok && gen == generated.ProtocGenGo {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path {
|
||||
// foo_test can import foo
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path {
|
||||
// foo.test can import foo
|
||||
return
|
||||
}
|
||||
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") {
|
||||
// foo.test can import foo_test
|
||||
return
|
||||
}
|
||||
|
||||
handleDeprecation(depr, spec.Path, path, path, nil)
|
||||
}
|
||||
}
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn)
|
||||
code.Preorder(pass, fn2, (*ast.ImportSpec)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
94
vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go
vendored
Normal file
94
vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package sa1020
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1020",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkListenAddressRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkListenAddressRules = map[string]callcheck.Check{
|
||||
"net/http.ListenAndServe": checkValidHostPort(0),
|
||||
"net/http.ListenAndServeTLS": checkValidHostPort(0),
|
||||
}
|
||||
|
||||
func checkValidHostPort(arg int) callcheck.Check {
|
||||
return func(call *callcheck.Call) {
|
||||
if !ValidHostPort(call.Args[arg].Value) {
|
||||
const MsgInvalidHostPort = "invalid port or service name in host:port pair"
|
||||
call.Args[arg].Invalid(MsgInvalidHostPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ValidHostPort(v callcheck.Value) bool {
|
||||
if k := callcheck.ExtractConstExpectKind(v, 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
49
vendor/honnef.co/go/tools/staticcheck/sa1021/sa1021.go
vendored
Normal file
49
vendor/honnef.co/go/tools/staticcheck/sa1021/sa1021.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package sa1021
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1021",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using \'bytes.Equal\' to compare two \'net.IP\'`,
|
||||
Text: `A \'net.IP\' stores an IPv4 or IPv6 address as a slice of bytes. The
|
||||
length of the slice for an IPv4 address, however, can be either 4 or
|
||||
16 bytes long, using different ways of representing IPv4 addresses. In
|
||||
order to correctly compare two \'net.IP\'s, the \'net.IP.Equal\' method should
|
||||
be used, as it takes both representations into account.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"bytes.Equal": func(call *callcheck.Call) {
|
||||
if isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.a")].Value, "net.IP") &&
|
||||
isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.b")].Value, "net.IP") {
|
||||
call.Invalid("use net.IP.Equal to compare net.IPs, not bytes.Equal")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// ConvertedFrom reports whether value v was converted from type typ.
|
||||
func isConvertedFrom(v callcheck.Value, typ string) bool {
|
||||
change, ok := v.Value.(*ir.ChangeType)
|
||||
return ok && types.TypeString(types.Unalias(change.X.Type()), nil) == typ
|
||||
}
|
||||
72
vendor/honnef.co/go/tools/staticcheck/sa1023/sa1023.go
vendored
Normal file
72
vendor/honnef.co/go/tools/staticcheck/sa1023/sa1023.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package sa1023
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1023",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Modifying the buffer in an \'io.Writer\' implementation`,
|
||||
Text: `\'Write\' must not modify the slice data, even temporarily.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// TODO(dh): this might be a good candidate for taint analysis.
|
||||
// Taint the argument as MUST_NOT_MODIFY, then propagate that
|
||||
// through functions like bytes.Split
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
sig := fn.Signature
|
||||
if fn.Name() != "Write" || sig.Recv() == nil {
|
||||
continue
|
||||
}
|
||||
if !types.Identical(sig, knowledge.Signatures["(io.Writer).Write"]) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
switch ins := ins.(type) {
|
||||
case *ir.Store:
|
||||
addr, ok := ins.Addr.(*ir.IndexAddr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if addr.X != fn.Params[1] {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
|
||||
case *ir.Call:
|
||||
if !irutil.IsCallTo(ins.Common(), "append") {
|
||||
continue
|
||||
}
|
||||
if ins.Common().Args[0] != fn.Params[1] {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
75
vendor/honnef.co/go/tools/staticcheck/sa1024/sa1024.go
vendored
Normal file
75
vendor/honnef.co/go/tools/staticcheck/sa1024/sa1024.go
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package sa1024
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"sort"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1024",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A string cutset contains duplicate characters`,
|
||||
Text: `The \'strings.TrimLeft\' and \'strings.TrimRight\' functions take cutsets, not
|
||||
prefixes. A cutset is treated as a set of characters to remove from a
|
||||
string. For example,
|
||||
|
||||
strings.TrimLeft("42133word", "1234")
|
||||
|
||||
will result in the string \'"word"\' – any characters that are 1, 2, 3 or
|
||||
4 are cut from the left of the string.
|
||||
|
||||
In order to remove one string from another, use \'strings.TrimPrefix\' instead.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"strings.Trim": check,
|
||||
"strings.TrimLeft": check,
|
||||
"strings.TrimRight": check,
|
||||
}
|
||||
|
||||
func check(call *callcheck.Call) {
|
||||
arg := call.Args[1]
|
||||
if !isUniqueStringCutset(arg.Value) {
|
||||
const MsgNonUniqueCutset = "cutset contains duplicate characters"
|
||||
arg.Invalid(MsgNonUniqueCutset)
|
||||
}
|
||||
}
|
||||
|
||||
func isUniqueStringCutset(v callcheck.Value) bool {
|
||||
if c := callcheck.ExtractConstExpectKind(v, 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
|
||||
}
|
||||
|
||||
type runeSlice []rune
|
||||
|
||||
func (rs runeSlice) Len() int { return len(rs) }
|
||||
func (rs runeSlice) Less(i int, j int) bool { return rs[i] < rs[j] }
|
||||
func (rs runeSlice) Swap(i int, j int) { rs[i], rs[j] = rs[j], rs[i] }
|
||||
92
vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go
vendored
Normal file
92
vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package sa1025
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1025",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`,
|
||||
Since: "2019.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !irutil.IsCallTo(call.Common(), "(*time.Timer).Reset") {
|
||||
continue
|
||||
}
|
||||
refs := call.Referrers()
|
||||
if refs == nil {
|
||||
continue
|
||||
}
|
||||
for _, ref := range irutil.FilterDebug(*refs) {
|
||||
ifstmt, ok := ref.(*ir.If)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, succ := range ifstmt.Block().Succs {
|
||||
if len(succ.Preds) != 1 {
|
||||
// Merge point, not a branch in the
|
||||
// syntactical sense.
|
||||
|
||||
// FIXME(dh): this is broken for if
|
||||
// statements a la "if x || y"
|
||||
continue
|
||||
}
|
||||
irutil.Walk(succ, func(b *ir.BasicBlock) bool {
|
||||
if !succ.Dominates(b) {
|
||||
// We've reached the end of the branch
|
||||
return false
|
||||
}
|
||||
for _, ins := range b.Instrs {
|
||||
// TODO(dh): we should check that we're receiving from the
|
||||
// channel of a time.Timer to further reduce false
|
||||
// positives. Not a key priority, considering the rarity
|
||||
// of Reset and the tiny likeliness of a false positive
|
||||
//
|
||||
// We intentionally don't handle aliases here, because
|
||||
// we're only interested in time.Timer.C.
|
||||
if ins, ok := ins.(*ir.Recv); ok && types.TypeString(ins.Chan.Type(), nil) == "<-chan time.Time" {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if found {
|
||||
report.Report(pass, call, "it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
77
vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go
vendored
Normal file
77
vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package sa1026
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/staticcheck/fakejson"
|
||||
"honnef.co/go/tools/staticcheck/fakexml"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1026",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Cannot marshal channels or functions`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"encoding/json.Marshal": checkJSON,
|
||||
"encoding/xml.Marshal": checkXML,
|
||||
"(*encoding/json.Encoder).Encode": checkJSON,
|
||||
"(*encoding/xml.Encoder).Encode": checkXML,
|
||||
}
|
||||
|
||||
func checkJSON(call *callcheck.Call) {
|
||||
arg := call.Args[0]
|
||||
T := arg.Value.Value.Type()
|
||||
if err := fakejson.Marshal(T); err != nil {
|
||||
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
|
||||
if err.Path == "x" {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ))
|
||||
} else {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkXML(call *callcheck.Call) {
|
||||
arg := call.Args[0]
|
||||
T := arg.Value.Value.Type()
|
||||
if err := fakexml.Marshal(T); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *fakexml.UnsupportedTypeError:
|
||||
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
|
||||
if err.Path == "x" {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ))
|
||||
} else {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path))
|
||||
}
|
||||
case *fakexml.CyclicTypeError:
|
||||
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
|
||||
if err.Path == "x" {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s", typ))
|
||||
} else {
|
||||
arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s, via %s", typ, err.Path))
|
||||
}
|
||||
case *fakexml.TagPathError:
|
||||
// Vet does a better job at reporting this error, because it can flag the actual struct tags, not just the call to Marshal
|
||||
default:
|
||||
// These errors get reported by SA5008 instead, which can flag the actual fields, independently of calls to xml.Marshal
|
||||
}
|
||||
}
|
||||
}
|
||||
76
vendor/honnef.co/go/tools/staticcheck/sa1027/sa1027.go
vendored
Normal file
76
vendor/honnef.co/go/tools/staticcheck/sa1027/sa1027.go
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package sa1027
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1027",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkAtomicAlignment),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Atomic access to 64-bit variable must be 64-bit aligned`,
|
||||
Text: `On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to
|
||||
arrange for 64-bit alignment of 64-bit words accessed atomically. The
|
||||
first word in a variable or in an allocated struct, array, or slice
|
||||
can be relied upon to be 64-bit aligned.
|
||||
|
||||
You can use the structlayout tool to inspect the alignment of fields
|
||||
in a struct.`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkAtomicAlignment = map[string]callcheck.Check{
|
||||
"sync/atomic.AddInt64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.AddUint64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.CompareAndSwapInt64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.CompareAndSwapUint64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.LoadInt64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.LoadUint64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.StoreInt64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.StoreUint64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.SwapInt64": checkAtomicAlignmentImpl,
|
||||
"sync/atomic.SwapUint64": checkAtomicAlignmentImpl,
|
||||
}
|
||||
|
||||
func checkAtomicAlignmentImpl(call *callcheck.Call) {
|
||||
sizes := call.Pass.TypesSizes
|
||||
if sizes.Sizeof(types.Typ[types.Uintptr]) != 4 {
|
||||
// Not running on a 32-bit platform
|
||||
return
|
||||
}
|
||||
v, ok := irutil.Flatten(call.Args[0].Value.Value).(*ir.FieldAddr)
|
||||
if !ok {
|
||||
// TODO(dh): also check indexing into arrays and slices
|
||||
return
|
||||
}
|
||||
T := v.X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct)
|
||||
fields := make([]*types.Var, 0, T.NumFields())
|
||||
for i := 0; i < T.NumFields() && i <= v.Field; i++ {
|
||||
fields = append(fields, T.Field(i))
|
||||
}
|
||||
|
||||
off := sizes.Offsetsof(fields)[v.Field]
|
||||
if off%8 != 0 {
|
||||
msg := fmt.Sprintf("address of non 64-bit aligned field %s passed to %s",
|
||||
T.Field(v.Field).Name(),
|
||||
irutil.CallName(call.Instr.Common()))
|
||||
call.Invalid(msg)
|
||||
}
|
||||
}
|
||||
57
vendor/honnef.co/go/tools/staticcheck/sa1028/sa1028.go
vendored
Normal file
57
vendor/honnef.co/go/tools/staticcheck/sa1028/sa1028.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package sa1028
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1028",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'sort.Slice\' can only be used on slices`,
|
||||
Text: `The first argument of \'sort.Slice\' must be a slice.`,
|
||||
Since: "2020.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"sort.Slice": check,
|
||||
"sort.SliceIsSorted": check,
|
||||
"sort.SliceStable": check,
|
||||
}
|
||||
|
||||
func check(call *callcheck.Call) {
|
||||
c := call.Instr.Common().StaticCallee()
|
||||
arg := call.Args[0]
|
||||
|
||||
T := arg.Value.Value.Type().Underlying()
|
||||
switch T.(type) {
|
||||
case *types.Interface:
|
||||
// we don't know.
|
||||
// TODO(dh): if the value is a phi node we can look at its edges
|
||||
if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value == nil {
|
||||
// literal nil, e.g. sort.Sort(nil, ...)
|
||||
arg.Invalid(fmt.Sprintf("cannot call %s on nil literal", c))
|
||||
}
|
||||
case *types.Slice:
|
||||
// this is fine
|
||||
default:
|
||||
// this is not fine
|
||||
arg.Invalid(fmt.Sprintf("%s must only be called on slices, was called on %s", c, T))
|
||||
}
|
||||
}
|
||||
61
vendor/honnef.co/go/tools/staticcheck/sa1029/sa1029.go
vendored
Normal file
61
vendor/honnef.co/go/tools/staticcheck/sa1029/sa1029.go
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package sa1029
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1029",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkWithValueKeyRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Inappropriate key in call to \'context.WithValue\'`,
|
||||
Text: `The provided key must be comparable and should not be
|
||||
of type \'string\' or any other built-in type to avoid collisions between
|
||||
packages using context. Users of \'WithValue\' should define their own
|
||||
types for keys.
|
||||
|
||||
To avoid allocating when assigning to an \'interface{}\',
|
||||
context keys often have concrete type \'struct{}\'. Alternatively,
|
||||
exported context key variables' static type should be a pointer or
|
||||
interface.`,
|
||||
Since: "2020.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkWithValueKeyRules = map[string]callcheck.Check{
|
||||
"context.WithValue": checkWithValueKey,
|
||||
}
|
||||
|
||||
func checkWithValueKey(call *callcheck.Call) {
|
||||
arg := call.Args[1]
|
||||
T := arg.Value.Value.Type()
|
||||
if typ, ok := types.Unalias(T).(*types.Basic); ok {
|
||||
if _, ok := T.(*types.Alias); ok {
|
||||
arg.Invalid(
|
||||
fmt.Sprintf("should not use built-in type %s (via alias %s) as key for value; define your own type to avoid collisions", typ, types.TypeString(T, types.RelativeTo(call.Pass.Pkg))))
|
||||
} else {
|
||||
arg.Invalid(
|
||||
fmt.Sprintf("should not use built-in type %s as key for value; define your own type to avoid collisions", typ))
|
||||
}
|
||||
}
|
||||
// TODO(dh): we should probably flag all anonymous structs, as they all risk collisions
|
||||
if s, ok := T.(*types.Struct); ok && s.NumFields() == 0 {
|
||||
arg.Invalid("should not use empty anonymous struct as key for value; define your own type to avoid collisions")
|
||||
} else if !types.Comparable(T) {
|
||||
arg.Invalid(fmt.Sprintf("keys used with context.WithValue must be comparable, but type %s is not comparable", T))
|
||||
}
|
||||
}
|
||||
134
vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go
vendored
Normal file
134
vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package sa1030
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1030",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid argument in call to a \'strconv\' function`,
|
||||
Text: `This check validates the format, number base and bit size arguments of
|
||||
the various parsing and formatting functions in \'strconv\'.`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"strconv.ParseComplex": func(call *callcheck.Call) {
|
||||
validateComplexBitSize(call.Args[knowledge.Arg("strconv.ParseComplex.bitSize")])
|
||||
},
|
||||
"strconv.ParseFloat": func(call *callcheck.Call) {
|
||||
validateFloatBitSize(call.Args[knowledge.Arg("strconv.ParseFloat.bitSize")])
|
||||
},
|
||||
"strconv.ParseInt": func(call *callcheck.Call) {
|
||||
validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseInt.bitSize")], 0, 64)
|
||||
validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseInt.base")])
|
||||
},
|
||||
"strconv.ParseUint": func(call *callcheck.Call) {
|
||||
validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseUint.bitSize")], 0, 64)
|
||||
validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseUint.base")])
|
||||
},
|
||||
|
||||
"strconv.FormatComplex": func(call *callcheck.Call) {
|
||||
validateComplexFormat(call.Args[knowledge.Arg("strconv.FormatComplex.fmt")])
|
||||
validateComplexBitSize(call.Args[knowledge.Arg("strconv.FormatComplex.bitSize")])
|
||||
},
|
||||
"strconv.FormatFloat": func(call *callcheck.Call) {
|
||||
validateFloatFormat(call.Args[knowledge.Arg("strconv.FormatFloat.fmt")])
|
||||
validateFloatBitSize(call.Args[knowledge.Arg("strconv.FormatFloat.bitSize")])
|
||||
},
|
||||
"strconv.FormatInt": func(call *callcheck.Call) {
|
||||
validateIntBase(call.Args[knowledge.Arg("strconv.FormatInt.base")])
|
||||
},
|
||||
"strconv.FormatUint": func(call *callcheck.Call) {
|
||||
validateIntBase(call.Args[knowledge.Arg("strconv.FormatUint.base")])
|
||||
},
|
||||
|
||||
"strconv.AppendFloat": func(call *callcheck.Call) {
|
||||
validateFloatFormat(call.Args[knowledge.Arg("strconv.AppendFloat.fmt")])
|
||||
validateFloatBitSize(call.Args[knowledge.Arg("strconv.AppendFloat.bitSize")])
|
||||
},
|
||||
"strconv.AppendInt": func(call *callcheck.Call) {
|
||||
validateIntBase(call.Args[knowledge.Arg("strconv.AppendInt.base")])
|
||||
},
|
||||
"strconv.AppendUint": func(call *callcheck.Call) {
|
||||
validateIntBase(call.Args[knowledge.Arg("strconv.AppendUint.base")])
|
||||
},
|
||||
}
|
||||
|
||||
func validateDiscreetBitSize(arg *callcheck.Argument, size1 int, size2 int) {
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
|
||||
val, _ := constant.Int64Val(c.Value)
|
||||
if val != int64(size1) && val != int64(size2) {
|
||||
arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be either %d or %d", size1, size2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateComplexBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 64, 128) }
|
||||
func validateFloatBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 32, 64) }
|
||||
|
||||
func validateContinuousBitSize(arg *callcheck.Argument, min int, max int) {
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
|
||||
val, _ := constant.Int64Val(c.Value)
|
||||
if val < int64(min) || val > int64(max) {
|
||||
arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be within %d and %d", min, max))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateIntBase(arg *callcheck.Argument) {
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
|
||||
val, _ := constant.Int64Val(c.Value)
|
||||
if val < 2 {
|
||||
arg.Invalid("'base' must not be smaller than 2")
|
||||
}
|
||||
if val > 36 {
|
||||
arg.Invalid("'base' must not be larger than 36")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateIntBaseAllowZero(arg *callcheck.Argument) {
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
|
||||
val, _ := constant.Int64Val(c.Value)
|
||||
if val < 2 && val != 0 {
|
||||
arg.Invalid("'base' must not be smaller than 2, unless it is 0")
|
||||
}
|
||||
if val > 36 {
|
||||
arg.Invalid("'base' must not be larger than 36")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateComplexFormat(arg *callcheck.Argument) {
|
||||
validateFloatFormat(arg)
|
||||
}
|
||||
|
||||
func validateFloatFormat(arg *callcheck.Argument) {
|
||||
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
|
||||
val, _ := constant.Int64Val(c.Value)
|
||||
switch val {
|
||||
case 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X':
|
||||
default:
|
||||
arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", val))
|
||||
}
|
||||
}
|
||||
}
|
||||
81
vendor/honnef.co/go/tools/staticcheck/sa1031/sa1031.go
vendored
Normal file
81
vendor/honnef.co/go/tools/staticcheck/sa1031/sa1031.go
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package sa1031
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"go/token"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1031",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkEncodeRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Overlapping byte slices passed to an encoder`,
|
||||
Text: `In an encoding function of the form \'Encode(dst, src)\', \'dst\' and
|
||||
\'src\' were found to reference the same memory. This can result in
|
||||
\'src\' bytes being overwritten before they are read, when the encoder
|
||||
writes more than one byte per \'src\' byte.`,
|
||||
Since: "2024.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkEncodeRules = map[string]callcheck.Check{
|
||||
"encoding/ascii85.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/ascii85.Encode.dst"), knowledge.Arg("encoding/ascii85.Encode.src")),
|
||||
"(*encoding/base32.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base32.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base32.Encoding).Encode.src")),
|
||||
"(*encoding/base64.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base64.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base64.Encoding).Encode.src")),
|
||||
"encoding/hex.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/hex.Encode.dst"), knowledge.Arg("encoding/hex.Encode.src")),
|
||||
}
|
||||
|
||||
func checkNonOverlappingDstSrc(dstArg, srcArg int) callcheck.Check {
|
||||
return func(call *callcheck.Call) {
|
||||
dst := call.Args[dstArg]
|
||||
src := call.Args[srcArg]
|
||||
_, dstConst := irutil.Flatten(dst.Value.Value).(*ir.Const)
|
||||
_, srcConst := irutil.Flatten(src.Value.Value).(*ir.Const)
|
||||
if dstConst || srcConst {
|
||||
// one of the arguments is nil, therefore overlap is not possible
|
||||
return
|
||||
}
|
||||
if dst.Value == src.Value {
|
||||
// simple case of f(b, b)
|
||||
dst.Invalid("overlapping dst and src")
|
||||
return
|
||||
}
|
||||
dstSlice, ok := irutil.Flatten(dst.Value.Value).(*ir.Slice)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
srcSlice, ok := irutil.Flatten(src.Value.Value).(*ir.Slice)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if irutil.Flatten(dstSlice.X) != irutil.Flatten(srcSlice.X) {
|
||||
// differing underlying arrays, all is well
|
||||
return
|
||||
}
|
||||
l1 := irutil.Flatten(dstSlice.Low)
|
||||
l2 := irutil.Flatten(srcSlice.Low)
|
||||
c1, ok1 := l1.(*ir.Const)
|
||||
c2, ok2 := l2.(*ir.Const)
|
||||
if l1 == l2 || (ok1 && ok2 && constant.Compare(c1.Value, token.EQL, c2.Value)) {
|
||||
// dst and src are the same slice, and have the same lower bound
|
||||
dst.Invalid("overlapping dst and src")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
79
vendor/honnef.co/go/tools/staticcheck/sa1032/sa1032.go
vendored
Normal file
79
vendor/honnef.co/go/tools/staticcheck/sa1032/sa1032.go
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package sa1032
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA1032",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Wrong order of arguments to \'errors.Is\'`,
|
||||
Text: `
|
||||
The first argument of the function \'errors.Is\' is the error
|
||||
that we have and the second argument is the error we're trying to match against.
|
||||
For example:
|
||||
|
||||
if errors.Is(err, io.EOF) { ... }
|
||||
|
||||
This check detects some cases where the two arguments have been swapped. It
|
||||
flags any calls where the first argument is referring to a package-level error
|
||||
variable, such as
|
||||
|
||||
if errors.Is(io.EOF, err) { /* this is wrong */ }`,
|
||||
Since: "2024.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"errors.Is": validateIs,
|
||||
}
|
||||
|
||||
func validateIs(call *callcheck.Call) {
|
||||
if len(call.Args) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
global := func(arg *callcheck.Argument) *ir.Global {
|
||||
v, ok := arg.Value.Value.(*ir.Load)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
g, _ := v.X.(*ir.Global)
|
||||
return g
|
||||
}
|
||||
|
||||
x, y := call.Args[0], call.Args[1]
|
||||
gx := global(x)
|
||||
if gx == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pkgx := gx.Package().Pkg; pkgx != nil && pkgx.Path() != call.Pass.Pkg.Path() {
|
||||
// x is a global that's not in this package
|
||||
|
||||
if gy := global(y); gy != nil {
|
||||
if pkgy := gy.Package().Pkg; pkgy != nil && pkgy.Path() != call.Pass.Pkg.Path() {
|
||||
// Both arguments refer to globals that aren't in this package. This can
|
||||
// genuinely happen for external tests that check that one error "is"
|
||||
// another one. net/http's external tests, for example, do
|
||||
// `errors.Is(http.ErrNotSupported, errors.ErrUnsupported)`.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
call.Invalid("arguments have the wrong order")
|
||||
}
|
||||
}
|
||||
44
vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go
vendored
Normal file
44
vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package sa2000
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA2000",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'sync.WaitGroup.Add\' called inside the goroutine, leading to a race condition`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkWaitgroupAddQ = pattern.MustParse(`
|
||||
(GoStmt
|
||||
(CallExpr
|
||||
(FuncLit
|
||||
_
|
||||
call@(CallExpr (Symbol "(*sync.WaitGroup).Add") _):_) _))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, m := range code.Matches(pass, checkWaitgroupAddQ) {
|
||||
call := m.State["call"].(ast.Node)
|
||||
report.Report(pass, call, fmt.Sprintf("should call %s before starting the goroutine to avoid a race", report.Render(pass, call)))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
111
vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go
vendored
Normal file
111
vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package sa2001
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA2001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Empty critical section, did you mean to defer the unlock?`,
|
||||
Text: `Empty critical sections of the kind
|
||||
|
||||
mu.Lock()
|
||||
mu.Unlock()
|
||||
|
||||
are very often a typo, and the following was intended instead:
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Do note that sometimes empty critical sections can be useful, as a
|
||||
form of signaling to wait on another goroutine. Many times, there are
|
||||
simpler ways of achieving the same effect. When that isn't the case,
|
||||
the code should be amply commented to avoid confusion. Combining such
|
||||
comments with a \'//lint:ignore\' directive can be used to suppress this
|
||||
rare false positive.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
if pass.Pkg.Path() == "sync_test" {
|
||||
// exception for the sync package's tests
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initially it might seem like this check would be easier to
|
||||
// implement using IR. After all, we're only checking for two
|
||||
// consecutive method calls. In reality, however, there may be any
|
||||
// number of other instructions between the lock and unlock, while
|
||||
// still constituting an empty critical section. For example,
|
||||
// given `m.x().Lock(); m.x().Unlock()`, there will be a call to
|
||||
// x(). In the AST-based approach, this has a tiny potential for a
|
||||
// false positive (the second call to x might be doing work that
|
||||
// is protected by the mutex). In an IR-based approach, however,
|
||||
// it would miss a lot of real bugs.
|
||||
|
||||
mutexParams := func(s ast.Stmt) (x ast.Expr, funcName string, ok bool) {
|
||||
expr, ok := s.(*ast.ExprStmt)
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
call, ok := astutil.Unparen(expr.X).(*ast.CallExpr)
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
fn, ok := pass.TypesInfo.ObjectOf(sel.Sel).(*types.Func)
|
||||
if !ok {
|
||||
return nil, "", false
|
||||
}
|
||||
sig := fn.Type().(*types.Signature)
|
||||
if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
return sel.X, fn.Name(), true
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
block := node.(*ast.BlockStmt)
|
||||
if len(block.List) < 2 {
|
||||
return
|
||||
}
|
||||
for i := range block.List[:len(block.List)-1] {
|
||||
sel1, method1, ok1 := mutexParams(block.List[i])
|
||||
sel2, method2, ok2 := mutexParams(block.List[i+1])
|
||||
|
||||
if !ok1 || !ok2 || report.Render(pass, sel1) != report.Render(pass, sel2) {
|
||||
continue
|
||||
}
|
||||
if (method1 == "Lock" && method2 == "Unlock") ||
|
||||
(method1 == "RLock" && method2 == "RUnlock") {
|
||||
report.Report(pass, block.List[i+1], "empty critical section")
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
93
vendor/honnef.co/go/tools/staticcheck/sa2002/sa2002.go
vendored
Normal file
93
vendor/honnef.co/go/tools/staticcheck/sa2002/sa2002.go
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
package sa2002
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA2002",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Called \'testing.T.FailNow\' or \'SkipNow\' in a goroutine, which isn't allowed`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
gostmt, ok := ins.(*ir.Go)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var fn *ir.Function
|
||||
switch val := gostmt.Call.Value.(type) {
|
||||
case *ir.Function:
|
||||
fn = val
|
||||
case *ir.MakeClosure:
|
||||
fn = val.Fn.(*ir.Function)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if fn.Blocks == nil {
|
||||
continue
|
||||
}
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if call.Call.IsInvoke() {
|
||||
continue
|
||||
}
|
||||
callee := call.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
recv := callee.Signature.Recv()
|
||||
if recv == nil {
|
||||
continue
|
||||
}
|
||||
if !typeutil.IsPointerToTypeWithName(recv.Type(), "testing.common") {
|
||||
continue
|
||||
}
|
||||
fn, ok := call.Call.StaticCallee().Object().(*types.Func)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name := fn.Name()
|
||||
switch name {
|
||||
case "FailNow", "Fatal", "Fatalf", "SkipNow", "Skip", "Skipf":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// TODO(dh): don't report multiple diagnostics
|
||||
// for multiple calls to T.Fatal, but do
|
||||
// collect all of them as related information
|
||||
report.Report(pass, gostmt, fmt.Sprintf("the goroutine calls T.%s, which must be called in the same goroutine as the test", name),
|
||||
report.Related(call, fmt.Sprintf("call to T.%s", name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
87
vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go
vendored
Normal file
87
vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package sa2003
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA2003",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
instrs := irutil.FilterDebug(block.Instrs)
|
||||
if len(instrs) < 2 {
|
||||
continue
|
||||
}
|
||||
for i, ins := range instrs[:len(instrs)-1] {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
|
||||
continue
|
||||
}
|
||||
nins, ok := instrs[i+1].(*ir.Defer)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
|
||||
continue
|
||||
}
|
||||
if call.Common().Args[0] != nins.Call.Args[0] {
|
||||
continue
|
||||
}
|
||||
name := shortCallName(call.Common())
|
||||
alt := ""
|
||||
switch name {
|
||||
case "Lock":
|
||||
alt = "Unlock"
|
||||
case "RLock":
|
||||
alt = "RUnlock"
|
||||
}
|
||||
report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func shortCallName(call *ir.CallCommon) string {
|
||||
if call.IsInvoke() {
|
||||
return ""
|
||||
}
|
||||
switch v := call.Value.(type) {
|
||||
case *ir.Function:
|
||||
fn, ok := v.Object().(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fn.Name()
|
||||
case *ir.Builtin:
|
||||
return v.Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
118
vendor/honnef.co/go/tools/staticcheck/sa3000/sa3000.go
vendored
Normal file
118
vendor/honnef.co/go/tools/staticcheck/sa3000/sa3000.go
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
package sa3000
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"go/version"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA3000",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'TestMain\' doesn't call \'os.Exit\', hiding test failures`,
|
||||
Text: `Test executables (and in turn \"go test\") exit with a non-zero status
|
||||
code if any tests failed. When specifying your own \'TestMain\' function,
|
||||
it is your responsibility to arrange for this, by calling \'os.Exit\' with
|
||||
the correct code. The correct code is returned by \'(*testing.M).Run\', so
|
||||
the usual way of implementing \'TestMain\' is to end it with
|
||||
\'os.Exit(m.Run())\'.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
var (
|
||||
fnmain ast.Node
|
||||
callsExit bool
|
||||
callsRun bool
|
||||
arg types.Object
|
||||
)
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
if fnmain != nil && node == fnmain {
|
||||
if !callsExit && callsRun {
|
||||
report.Report(pass, fnmain, "TestMain should call os.Exit to set exit code")
|
||||
}
|
||||
fnmain = nil
|
||||
callsExit = false
|
||||
callsRun = false
|
||||
arg = nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch node := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if fnmain != nil {
|
||||
return true
|
||||
}
|
||||
if !isTestMain(pass, node) {
|
||||
return false
|
||||
}
|
||||
if version.Compare(code.StdlibVersion(pass, node), "go1.15") >= 0 {
|
||||
// Beginning with Go 1.15, the test framework will call
|
||||
// os.Exit for us.
|
||||
return false
|
||||
}
|
||||
fnmain = node
|
||||
arg = pass.TypesInfo.ObjectOf(node.Type.Params.List[0].Names[0])
|
||||
return true
|
||||
case *ast.CallExpr:
|
||||
if code.IsCallTo(pass, node, "os.Exit") {
|
||||
callsExit = true
|
||||
return false
|
||||
}
|
||||
sel, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
ident, ok := sel.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if arg != pass.TypesInfo.ObjectOf(ident) {
|
||||
return true
|
||||
}
|
||||
if sel.Sel.Name == "Run" {
|
||||
callsRun = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.FuncDecl)(nil), (*ast.CallExpr)(nil)}, fn)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func isTestMain(pass *analysis.Pass, decl *ast.FuncDecl) bool {
|
||||
if decl.Name.Name != "TestMain" {
|
||||
return false
|
||||
}
|
||||
if len(decl.Type.Params.List) != 1 {
|
||||
return false
|
||||
}
|
||||
arg := decl.Type.Params.List[0]
|
||||
if len(arg.Names) != 1 {
|
||||
return false
|
||||
}
|
||||
return code.IsOfPointerToTypeWithName(pass, arg.Type, "testing.M")
|
||||
}
|
||||
47
vendor/honnef.co/go/tools/staticcheck/sa3001/sa3001.go
vendored
Normal file
47
vendor/honnef.co/go/tools/staticcheck/sa3001/sa3001.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package sa3001
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA3001",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Assigning to \'b.N\' in benchmarks distorts the results`,
|
||||
Text: `The testing package dynamically sets \'b.N\' to improve the reliability of
|
||||
benchmarks and uses it in computations to determine the duration of a
|
||||
single operation. Benchmark code must not alter \'b.N\' as this would
|
||||
falsify results.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`(AssignStmt sel@(SelectorExpr selX (Ident "N")) "=" [_] )`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, query) {
|
||||
assign := node.(*ast.AssignStmt)
|
||||
if !code.IsOfPointerToTypeWithName(pass, m.State["selX"].(ast.Expr), "testing.B") {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, assign,
|
||||
fmt.Sprintf("should not assign to %s", report.Render(pass, m.State["sel"])))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
217
vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go
vendored
Normal file
217
vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
package sa4000
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4000",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Binary operator has identical expressions on both sides`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
var isFloat func(T types.Type) bool
|
||||
isFloat = func(T types.Type) bool {
|
||||
tset := typeutil.NewTypeSet(T)
|
||||
if len(tset.Terms) == 0 {
|
||||
// no terms, so floats are a possibility
|
||||
return true
|
||||
}
|
||||
return tset.Any(func(term *types.Term) bool {
|
||||
switch typ := term.Type().Underlying().(type) {
|
||||
case *types.Basic:
|
||||
kind := typ.Kind()
|
||||
return kind == types.Float32 || kind == types.Float64
|
||||
case *types.Array:
|
||||
return isFloat(typ.Elem())
|
||||
case *types.Struct:
|
||||
for field := range typ.Fields() {
|
||||
if isFloat(field.Type()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(dh): this check ignores the existence of side-effects and
|
||||
// happily flags fn() == fn() – so far, we've had only two complains
|
||||
// about false positives, and it's caught several bugs in real
|
||||
// code.
|
||||
//
|
||||
// We special case functions from the math/rand package. Someone ran
|
||||
// into the following false positive: "rand.Intn(2) - rand.Intn(2), which I wrote to generate values {-1, 0, 1} with {0.25, 0.5, 0.25} probability."
|
||||
|
||||
skipComparableCheck := func(c inspector.Cursor) bool {
|
||||
op, ok := c.Node().(*ast.BinaryExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if clit, ok := op.X.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
|
||||
return false
|
||||
}
|
||||
if clit, ok := op.Y.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(dh): we should probably skip ParenExprs, but users should
|
||||
// probably not use unnecessary ParenExprs.
|
||||
vspec, ok := c.Parent().Node().(*ast.ValueSpec)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
e, i := c.ParentEdge()
|
||||
if e != edge.ValueSpec_Values {
|
||||
return false
|
||||
}
|
||||
if vspec.Names[i].Name == "_" {
|
||||
// `var _ = T{} == T{}` is permitted, as a compile-time
|
||||
// check that T implements comparable.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for c := range code.Cursor(pass).Preorder((*ast.BinaryExpr)(nil)) {
|
||||
node := c.Node()
|
||||
op := node.(*ast.BinaryExpr)
|
||||
switch op.Op {
|
||||
case token.EQL, token.NEQ:
|
||||
if skipComparableCheck(c) {
|
||||
continue
|
||||
}
|
||||
case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
|
||||
token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
|
||||
default:
|
||||
// For some ops, such as + and *, it can make sense to
|
||||
// have identical operands
|
||||
continue
|
||||
}
|
||||
|
||||
if isFloat(pass.TypesInfo.TypeOf(op.X)) {
|
||||
// 'float <op> float' makes sense for several operators.
|
||||
// We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead.
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
|
||||
continue
|
||||
}
|
||||
if report.Render(pass, op.X) != report.Render(pass, op.Y) {
|
||||
continue
|
||||
}
|
||||
l1, ok1 := op.X.(*ast.BasicLit)
|
||||
l2, ok2 := op.Y.(*ast.BasicLit)
|
||||
if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
|
||||
// cgo generates the following function call:
|
||||
// _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0
|
||||
// instead of true in case the user shadowed the
|
||||
// identifier. Ideally we'd restrict this exception to
|
||||
// calls of _cgoCheckPointer, but it's not worth the
|
||||
// hassle of keeping track of the stack. <lit> <op> <lit>
|
||||
// are very rare to begin with, and we're mostly checking
|
||||
// for them to catch typos such as 1 == 1 where the user
|
||||
// meant to type i == 1. The odds of a false negative for
|
||||
// 0 == 0 are slim.
|
||||
continue
|
||||
}
|
||||
|
||||
if expr, ok := op.X.(*ast.CallExpr); ok {
|
||||
call := code.CallName(pass, expr)
|
||||
switch call {
|
||||
case "math/rand.Int",
|
||||
"math/rand.Int31",
|
||||
"math/rand.Int31n",
|
||||
"math/rand.Int63",
|
||||
"math/rand.Int63n",
|
||||
"math/rand.Intn",
|
||||
"math/rand.Uint32",
|
||||
"math/rand.Uint64",
|
||||
"math/rand.ExpFloat64",
|
||||
"math/rand.Float32",
|
||||
"math/rand.Float64",
|
||||
"math/rand.NormFloat64",
|
||||
"(*math/rand.Rand).Int",
|
||||
"(*math/rand.Rand).Int31",
|
||||
"(*math/rand.Rand).Int31n",
|
||||
"(*math/rand.Rand).Int63",
|
||||
"(*math/rand.Rand).Int63n",
|
||||
"(*math/rand.Rand).Intn",
|
||||
"(*math/rand.Rand).Uint32",
|
||||
"(*math/rand.Rand).Uint64",
|
||||
"(*math/rand.Rand).ExpFloat64",
|
||||
"(*math/rand.Rand).Float32",
|
||||
"(*math/rand.Rand).Float64",
|
||||
"(*math/rand.Rand).NormFloat64",
|
||||
"math/rand/v2.Int",
|
||||
"math/rand/v2.Int32",
|
||||
"math/rand/v2.Int32N",
|
||||
"math/rand/v2.Int64",
|
||||
"math/rand/v2.Int64N",
|
||||
"math/rand/v2.IntN",
|
||||
"math/rand/v2.N",
|
||||
"math/rand/v2.Uint",
|
||||
"math/rand/v2.Uint32",
|
||||
"math/rand/v2.Uint32N",
|
||||
"math/rand/v2.Uint64",
|
||||
"math/rand/v2.Uint64N",
|
||||
"math/rand/v2.UintN",
|
||||
"math/rand/v2.ExpFloat64",
|
||||
"math/rand/v2.Float32",
|
||||
"math/rand/v2.Float64",
|
||||
"math/rand/v2.NormFloat64",
|
||||
"(*math/rand/v2.Rand).Int",
|
||||
"(*math/rand/v2.Rand).Int32",
|
||||
"(*math/rand/v2.Rand).Int32N",
|
||||
"(*math/rand/v2.Rand).Int64",
|
||||
"(*math/rand/v2.Rand).Int64N",
|
||||
"(*math/rand/v2.Rand).IntN",
|
||||
"(*math/rand/v2.Rand).N",
|
||||
"(*math/rand/v2.Rand).Uint",
|
||||
"(*math/rand/v2.Rand).Uint32",
|
||||
"(*math/rand/v2.Rand).Uint32N",
|
||||
"(*math/rand/v2.Rand).Uint64",
|
||||
"(*math/rand/v2.Rand).Uint64N",
|
||||
"(*math/rand/v2.Rand).UintN",
|
||||
"(*math/rand/v2.Rand).ExpFloat64",
|
||||
"(*math/rand/v2.Rand).Float32",
|
||||
"(*math/rand/v2.Rand).Float64",
|
||||
"(*math/rand/v2.Rand).NormFloat64":
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
52
vendor/honnef.co/go/tools/staticcheck/sa4001/sa4001.go
vendored
Normal file
52
vendor/honnef.co/go/tools/staticcheck/sa4001/sa4001.go
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package sa4001
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'&*x\' gets simplified to \'x\', it does not copy \'x\'`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
// cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't
|
||||
// want to flag.
|
||||
cgoIdent = regexp.MustCompile(`^_C(func|var)_.+$`)
|
||||
checkIneffectiveCopyQ1 = pattern.MustParse(`(UnaryExpr "&" (StarExpr obj))`)
|
||||
checkIneffectiveCopyQ2 = pattern.MustParse(`(StarExpr (UnaryExpr "&" _))`)
|
||||
)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
if m, ok := code.Match(pass, checkIneffectiveCopyQ1, node); ok {
|
||||
if ident, ok := m.State["obj"].(*ast.Ident); !ok || !cgoIdent.MatchString(ident.Name) {
|
||||
report.Report(pass, node, "&*x will be simplified to x. It will not copy x.")
|
||||
}
|
||||
} else if _, ok := code.Match(pass, checkIneffectiveCopyQ2, node); ok {
|
||||
report.Report(pass, node, "*&x will be simplified to x. It will not copy x.")
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.StarExpr)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
160
vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go
vendored
Normal file
160
vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
package sa4003
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"math"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4003",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Comparing unsigned values against negative values is pointless`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
isobj := func(expr ast.Expr, name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
sel, ok := expr.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
expr := node.(*ast.BinaryExpr)
|
||||
tx := pass.TypesInfo.TypeOf(expr.X)
|
||||
basic, ok := tx.Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// We only check for the math constants and integer literals, not for all constant expressions. This is to avoid
|
||||
// false positives when constant values differ under different build tags.
|
||||
var (
|
||||
maxMathConst string
|
||||
minMathConst string
|
||||
maxLiteral constant.Value
|
||||
minLiteral constant.Value
|
||||
)
|
||||
|
||||
switch basic.Kind() {
|
||||
case types.Uint8:
|
||||
maxMathConst = "math.MaxUint8"
|
||||
minLiteral = constant.MakeUint64(0)
|
||||
maxLiteral = constant.MakeUint64(math.MaxUint8)
|
||||
case types.Uint16:
|
||||
maxMathConst = "math.MaxUint16"
|
||||
minLiteral = constant.MakeUint64(0)
|
||||
maxLiteral = constant.MakeUint64(math.MaxUint16)
|
||||
case types.Uint32:
|
||||
maxMathConst = "math.MaxUint32"
|
||||
minLiteral = constant.MakeUint64(0)
|
||||
maxLiteral = constant.MakeUint64(math.MaxUint32)
|
||||
case types.Uint64:
|
||||
maxMathConst = "math.MaxUint64"
|
||||
minLiteral = constant.MakeUint64(0)
|
||||
maxLiteral = constant.MakeUint64(math.MaxUint64)
|
||||
case types.Uint:
|
||||
// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
|
||||
maxMathConst = "math.MaxUint64"
|
||||
minLiteral = constant.MakeUint64(0)
|
||||
maxLiteral = constant.MakeUint64(math.MaxUint64)
|
||||
|
||||
case types.Int8:
|
||||
minMathConst = "math.MinInt8"
|
||||
maxMathConst = "math.MaxInt8"
|
||||
minLiteral = constant.MakeInt64(math.MinInt8)
|
||||
maxLiteral = constant.MakeInt64(math.MaxInt8)
|
||||
case types.Int16:
|
||||
minMathConst = "math.MinInt16"
|
||||
maxMathConst = "math.MaxInt16"
|
||||
minLiteral = constant.MakeInt64(math.MinInt16)
|
||||
maxLiteral = constant.MakeInt64(math.MaxInt16)
|
||||
case types.Int32:
|
||||
minMathConst = "math.MinInt32"
|
||||
maxMathConst = "math.MaxInt32"
|
||||
minLiteral = constant.MakeInt64(math.MinInt32)
|
||||
maxLiteral = constant.MakeInt64(math.MaxInt32)
|
||||
case types.Int64:
|
||||
minMathConst = "math.MinInt64"
|
||||
maxMathConst = "math.MaxInt64"
|
||||
minLiteral = constant.MakeInt64(math.MinInt64)
|
||||
maxLiteral = constant.MakeInt64(math.MaxInt64)
|
||||
case types.Int:
|
||||
// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
|
||||
minMathConst = "math.MinInt64"
|
||||
maxMathConst = "math.MaxInt64"
|
||||
minLiteral = constant.MakeInt64(math.MinInt64)
|
||||
maxLiteral = constant.MakeInt64(math.MaxInt64)
|
||||
}
|
||||
|
||||
isLiteral := func(expr ast.Expr, c constant.Value) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return code.IsIntegerLiteral(pass, expr, c)
|
||||
}
|
||||
isZeroLiteral := func(expr ast.Expr) bool {
|
||||
return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
|
||||
}
|
||||
|
||||
if (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
|
||||
(expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
|
||||
report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, maxMathConst), report.FilterGenerated())
|
||||
}
|
||||
|
||||
if expr.Op == token.LEQ && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
|
||||
expr.Op == token.GEQ && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
|
||||
report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, maxMathConst), report.FilterGenerated())
|
||||
}
|
||||
|
||||
if (basic.Info() & types.IsUnsigned) != 0 {
|
||||
if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) ||
|
||||
(expr.Op == token.GTR && isZeroLiteral(expr.X)) {
|
||||
report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic), report.FilterGenerated())
|
||||
}
|
||||
if expr.Op == token.GEQ && isZeroLiteral(expr.Y) ||
|
||||
expr.Op == token.LEQ && isZeroLiteral(expr.X) {
|
||||
report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic), report.FilterGenerated())
|
||||
}
|
||||
} else {
|
||||
if (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
|
||||
(expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
|
||||
report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, minMathConst), report.FilterGenerated())
|
||||
}
|
||||
if expr.Op == token.GEQ && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
|
||||
expr.Op == token.LEQ && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
|
||||
report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, minMathConst), report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
159
vendor/honnef.co/go/tools/staticcheck/sa4004/sa4004.go
vendored
Normal file
159
vendor/honnef.co/go/tools/staticcheck/sa4004/sa4004.go
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package sa4004
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4004",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `The loop exits unconditionally after one iteration`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// This check detects some, but not all unconditional loop exits.
|
||||
// We give up in the following cases:
|
||||
//
|
||||
// - a goto anywhere in the loop. The goto might skip over our
|
||||
// return, and we don't check that it doesn't.
|
||||
//
|
||||
// - any nested, unlabelled continue, even if it is in another
|
||||
// loop or closure.
|
||||
fn := func(node ast.Node) {
|
||||
var body *ast.BlockStmt
|
||||
switch fn := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
body = fn.Body
|
||||
case *ast.FuncLit:
|
||||
body = fn.Body
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(node)
|
||||
}
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
labels := map[types.Object]ast.Stmt{}
|
||||
ast.Inspect(body, func(node ast.Node) bool {
|
||||
label, ok := node.(*ast.LabeledStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt
|
||||
return true
|
||||
})
|
||||
|
||||
ast.Inspect(body, func(node ast.Node) bool {
|
||||
var loop ast.Node
|
||||
var body *ast.BlockStmt
|
||||
switch node := node.(type) {
|
||||
case *ast.ForStmt:
|
||||
body = node.Body
|
||||
loop = node
|
||||
case *ast.RangeStmt:
|
||||
ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool {
|
||||
switch term.Type().Underlying().(type) {
|
||||
case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array:
|
||||
return true
|
||||
case *types.Map:
|
||||
// looping once over a map is a valid pattern for
|
||||
// getting an arbitrary element.
|
||||
return false
|
||||
case *types.Signature:
|
||||
// we have no idea what semantics the function implements
|
||||
return false
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(term.Type().Underlying())
|
||||
return false
|
||||
}
|
||||
})
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
body = node.Body
|
||||
loop = node
|
||||
default:
|
||||
return true
|
||||
}
|
||||
if len(body.List) < 2 {
|
||||
// TODO(dh): is this check needed? when body.List < 2,
|
||||
// then we can't find both an unconditional exit and a
|
||||
// branching statement (if, ...). and we don't flag
|
||||
// unconditional exits if there has been no branching
|
||||
// in the loop body.
|
||||
|
||||
// avoid flagging the somewhat common pattern of using
|
||||
// a range loop to get the first element in a slice,
|
||||
// or the first rune in a string.
|
||||
return true
|
||||
}
|
||||
var unconditionalExit ast.Node
|
||||
hasBranching := false
|
||||
for _, stmt := range body.List {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.BranchStmt:
|
||||
switch stmt.Tok {
|
||||
case token.BREAK:
|
||||
if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
|
||||
unconditionalExit = stmt
|
||||
}
|
||||
case token.CONTINUE:
|
||||
if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
|
||||
unconditionalExit = nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
case *ast.ReturnStmt:
|
||||
unconditionalExit = stmt
|
||||
case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
|
||||
hasBranching = true
|
||||
}
|
||||
}
|
||||
if unconditionalExit == nil || !hasBranching {
|
||||
return false
|
||||
}
|
||||
ast.Inspect(body, func(node ast.Node) bool {
|
||||
if branch, ok := node.(*ast.BranchStmt); ok {
|
||||
|
||||
switch branch.Tok {
|
||||
case token.GOTO:
|
||||
unconditionalExit = nil
|
||||
return false
|
||||
case token.CONTINUE:
|
||||
if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop {
|
||||
return true
|
||||
}
|
||||
unconditionalExit = nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if unconditionalExit != nil {
|
||||
report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated")
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
138
vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go
vendored
Normal file
138
vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package sa4005
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4005",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// The analysis only considers the receiver and its first level
|
||||
// fields. It doesn't look at other parameters, nor at nested
|
||||
// fields.
|
||||
//
|
||||
// The analysis does not detect all kinds of dead stores, only
|
||||
// those of fields that are never read after the write. That is,
|
||||
// we do not flag 'a.x = 1; a.x = 2; _ = a.x'. We might explore
|
||||
// this again if we add support for SROA to go/ir and implement
|
||||
// https://github.com/dominikh/go-tools/issues/191.
|
||||
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
|
||||
fnLoop:
|
||||
for _, fn := range irpkg.SrcFuncs {
|
||||
if recv := fn.Signature.Recv(); recv == nil {
|
||||
continue
|
||||
} else if _, ok := recv.Type().Underlying().(*types.Struct); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
recv := fn.Params[0]
|
||||
refs := irutil.FilterDebug(*recv.Referrers())
|
||||
if len(refs) != 1 {
|
||||
continue
|
||||
}
|
||||
store, ok := refs[0].(*ir.Store)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
alloc, ok := store.Addr.(*ir.Alloc)
|
||||
if !ok || alloc.Heap {
|
||||
continue
|
||||
}
|
||||
|
||||
reads := map[int][]ir.Instruction{}
|
||||
writes := map[int][]ir.Instruction{}
|
||||
for _, ref := range *alloc.Referrers() {
|
||||
switch ref := ref.(type) {
|
||||
case *ir.FieldAddr:
|
||||
for _, refref := range *ref.Referrers() {
|
||||
switch refref.(type) {
|
||||
case *ir.Store:
|
||||
writes[ref.Field] = append(writes[ref.Field], refref)
|
||||
case *ir.Load:
|
||||
reads[ref.Field] = append(reads[ref.Field], refref)
|
||||
case *ir.DebugRef:
|
||||
continue
|
||||
default:
|
||||
// this should be safe… if the field address
|
||||
// escapes, then alloc.Heap will be true.
|
||||
// there should be no instructions left that,
|
||||
// given this FieldAddr, without escaping, can
|
||||
// effect a load or store.
|
||||
continue
|
||||
}
|
||||
}
|
||||
case *ir.Store:
|
||||
// we could treat this as a store to every field, but
|
||||
// we don't want to decide the semantics of partial
|
||||
// struct initializers. should `v = t{x: 1}` also mark
|
||||
// v.y as being written to?
|
||||
if ref != store {
|
||||
continue fnLoop
|
||||
}
|
||||
case *ir.Load:
|
||||
// a load of the entire struct loads every field
|
||||
for i := 0; i < recv.Type().Underlying().(*types.Struct).NumFields(); i++ {
|
||||
reads[i] = append(reads[i], ref)
|
||||
}
|
||||
case *ir.DebugRef:
|
||||
continue
|
||||
default:
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
|
||||
offset := func(instr ir.Instruction) int {
|
||||
for i, other := range instr.Block().Instrs {
|
||||
if instr == other {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic("couldn't find instruction in its block")
|
||||
}
|
||||
|
||||
for field, ws := range writes {
|
||||
rs := reads[field]
|
||||
wLoop:
|
||||
for _, w := range ws {
|
||||
for _, r := range rs {
|
||||
if w.Block() == r.Block() {
|
||||
if offset(r) > offset(w) {
|
||||
// found a reachable read of our write
|
||||
continue wLoop
|
||||
}
|
||||
} else if irutil.Reachable(w.Block(), r.Block()) {
|
||||
// found a reachable read of our write
|
||||
continue wLoop
|
||||
}
|
||||
}
|
||||
fieldName := recv.Type().Underlying().(*types.Struct).Field(field).Name()
|
||||
report.Report(pass, w, fmt.Sprintf("ineffective assignment to field %s.%s", recv.Type().(interface{ Obj() *types.TypeName }).Obj().Name(), fieldName))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
155
vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go
vendored
Normal file
155
vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
package sa4006
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4006",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
if irutil.IsExample(fn) {
|
||||
continue
|
||||
}
|
||||
node := fn.Source()
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
if gen, ok := code.Generator(pass, node.Pos()); ok && gen == generated.Goyacc {
|
||||
// Don't flag unused values in code generated by goyacc.
|
||||
// There may be hundreds of those due to the way the state
|
||||
// machine is constructed.
|
||||
continue
|
||||
}
|
||||
|
||||
switchTags := map[ir.Value]struct{}{}
|
||||
ast.Inspect(node, func(node ast.Node) bool {
|
||||
s, ok := node.(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
v, _ := fn.ValueForExpr(s.Tag)
|
||||
switchTags[v] = struct{}{}
|
||||
return true
|
||||
})
|
||||
|
||||
// OPT(dh): don't use a map, possibly use a bitset
|
||||
var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool
|
||||
hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool {
|
||||
if _, ok := seen[v]; ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := switchTags[v]; ok {
|
||||
return true
|
||||
}
|
||||
refs := v.Referrers()
|
||||
if refs == nil {
|
||||
// TODO investigate why refs can be nil
|
||||
return true
|
||||
}
|
||||
for _, ref := range *refs {
|
||||
switch ref := ref.(type) {
|
||||
case *ir.DebugRef:
|
||||
case *ir.Sigma:
|
||||
if seen == nil {
|
||||
seen = map[ir.Value]struct{}{}
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
if hasUse(ref, seen) {
|
||||
return true
|
||||
}
|
||||
case *ir.Phi:
|
||||
if seen == nil {
|
||||
seen = map[ir.Value]struct{}{}
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
if hasUse(ref, seen) {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ast.Inspect(node, func(node ast.Node) bool {
|
||||
assign, ok := node.(*ast.AssignStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 {
|
||||
// Either a function call with multiple return values,
|
||||
// or a comma-ok assignment
|
||||
|
||||
val, _ := fn.ValueForExpr(assign.Rhs[0])
|
||||
if val == nil {
|
||||
return true
|
||||
}
|
||||
refs := val.Referrers()
|
||||
if refs == nil {
|
||||
return true
|
||||
}
|
||||
for _, ref := range *refs {
|
||||
ex, ok := ref.(*ir.Extract)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !hasUse(ex, nil) {
|
||||
lhs := assign.Lhs[ex.Index]
|
||||
if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for i, lhs := range assign.Lhs {
|
||||
rhs := assign.Rhs[i]
|
||||
if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
|
||||
continue
|
||||
}
|
||||
val, _ := fn.ValueForExpr(rhs)
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := val.(*ir.Const); ok {
|
||||
// a zero-valued constant, for example in 'foo := []string(nil)'
|
||||
continue
|
||||
}
|
||||
if !hasUse(val, nil) {
|
||||
report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
114
vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go
vendored
Normal file
114
vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package sa4008
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4008",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`,
|
||||
Text: `For example:
|
||||
|
||||
for i := 0; i < 10; j++ { ... }
|
||||
|
||||
This may also occur when a loop can only execute once because of unconditional
|
||||
control flow that terminates the loop. For example, when a loop body contains an
|
||||
unconditional break, return, or panic:
|
||||
|
||||
func f() {
|
||||
panic("oops")
|
||||
}
|
||||
func g() {
|
||||
for i := 0; i < 10; i++ {
|
||||
// f unconditionally calls panic, which means "i" is
|
||||
// never incremented.
|
||||
f()
|
||||
}
|
||||
}`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
cb := func(node ast.Node) bool {
|
||||
loop, ok := node.(*ast.ForStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if loop.Init == nil || loop.Cond == nil || loop.Post == nil {
|
||||
return true
|
||||
}
|
||||
init, ok := loop.Init.(*ast.AssignStmt)
|
||||
if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 {
|
||||
return true
|
||||
}
|
||||
cond, ok := loop.Cond.(*ast.BinaryExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := cond.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
lhs, ok := init.Lhs[0].(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if pass.TypesInfo.ObjectOf(x) != pass.TypesInfo.ObjectOf(lhs) {
|
||||
return true
|
||||
}
|
||||
if _, ok := loop.Post.(*ast.IncDecStmt); !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
v, isAddr := fn.ValueForExpr(cond.X)
|
||||
if v == nil || isAddr {
|
||||
return true
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case *ir.Phi:
|
||||
ops := v.Operands(nil)
|
||||
if len(ops) != 2 {
|
||||
return true
|
||||
}
|
||||
_, ok := (*ops[0]).(*ir.Const)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
sigma, ok := (*ops[1]).(*ir.Sigma)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if sigma.X != v {
|
||||
return true
|
||||
}
|
||||
case *ir.Load:
|
||||
return true
|
||||
}
|
||||
report.Report(pass, cond, "variable in loop condition never changes")
|
||||
|
||||
return true
|
||||
}
|
||||
if source := fn.Source(); source != nil {
|
||||
ast.Inspect(source, cb)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
106
vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go
vendored
Normal file
106
vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package sa4009
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4009",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A function argument is overwritten before its first use`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
cb := func(node ast.Node) bool {
|
||||
var typ *ast.FuncType
|
||||
var body *ast.BlockStmt
|
||||
switch fn := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
typ = fn.Type
|
||||
body = fn.Body
|
||||
case *ast.FuncLit:
|
||||
typ = fn.Type
|
||||
body = fn.Body
|
||||
}
|
||||
if body == nil {
|
||||
return true
|
||||
}
|
||||
if len(typ.Params.List) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, field := range typ.Params.List {
|
||||
for _, arg := range field.Names {
|
||||
obj := pass.TypesInfo.ObjectOf(arg)
|
||||
var irobj *ir.Parameter
|
||||
for _, param := range fn.Params {
|
||||
if param.Object() == obj {
|
||||
irobj = param
|
||||
break
|
||||
}
|
||||
}
|
||||
if irobj == nil {
|
||||
continue
|
||||
}
|
||||
refs := irobj.Referrers()
|
||||
if refs == nil {
|
||||
continue
|
||||
}
|
||||
if len(irutil.FilterDebug(*refs)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var assignment ast.Node
|
||||
ast.Inspect(body, func(node ast.Node) bool {
|
||||
if assignment != nil {
|
||||
return false
|
||||
}
|
||||
assign, ok := node.(*ast.AssignStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for _, lhs := range assign.Lhs {
|
||||
ident, ok := lhs.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if pass.TypesInfo.ObjectOf(ident) == obj {
|
||||
assignment = assign
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if assignment != nil {
|
||||
report.Report(pass, arg, fmt.Sprintf("argument %s is overwritten before first use", arg),
|
||||
report.Related(assignment, fmt.Sprintf("assignment to %s", arg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if source := fn.Source(); source != nil {
|
||||
ast.Inspect(source, cb)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
216
vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go
vendored
Normal file
216
vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package sa4010
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4010",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `The result of \'append\' will never be observed anywhere`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
isAppend := func(ins ir.Value) bool {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if call.Call.IsInvoke() {
|
||||
return false
|
||||
}
|
||||
if builtin, ok := call.Call.Value.(*ir.Builtin); !ok || builtin.Name() != "append" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// We have to be careful about aliasing.
|
||||
// Multiple slices may refer to the same backing array,
|
||||
// making appends observable even when we don't see the result of append be used anywhere.
|
||||
//
|
||||
// We will have to restrict ourselves to slices that have been allocated within the function,
|
||||
// haven't been sliced,
|
||||
// and haven't been passed anywhere that could retain them (such as function calls or memory stores).
|
||||
//
|
||||
// We check whether an append should be flagged in two steps.
|
||||
//
|
||||
// In the first step, we look at the data flow graph, starting in reverse from the argument to append, till we reach the root.
|
||||
// This graph must only consist of the following instructions:
|
||||
//
|
||||
// - phi
|
||||
// - sigma
|
||||
// - slice
|
||||
// - const nil
|
||||
// - MakeSlice
|
||||
// - Alloc
|
||||
// - calls to append
|
||||
//
|
||||
// If this step succeeds, we look at all referrers of the values found in the first step, recursively.
|
||||
// These referrers must either be in the set of values found in the first step,
|
||||
// be DebugRefs,
|
||||
// or fulfill the same type requirements as step 1, with the exception of appends, which are forbidden.
|
||||
//
|
||||
// If both steps succeed then we know that the backing array hasn't been aliased in an observable manner.
|
||||
//
|
||||
// We could relax these restrictions by making use of additional information:
|
||||
// - if we passed the slice to a function that doesn't retain the slice then we can still flag it
|
||||
// - if a slice has been sliced but is dead afterwards, we can flag appends to the new slice
|
||||
|
||||
// OPT(dh): We could cache the results of both validate functions.
|
||||
// However, we only use these functions on values that we otherwise want to flag, which are very few.
|
||||
// Not caching values hasn't increased the runtimes for the standard library nor k8s.
|
||||
var validateArgument func(v ir.Value, seen map[ir.Value]struct{}) bool
|
||||
validateArgument = func(v ir.Value, seen map[ir.Value]struct{}) bool {
|
||||
if _, ok := seen[v]; ok {
|
||||
// break cycle
|
||||
return true
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
switch v := v.(type) {
|
||||
case *ir.Phi:
|
||||
for _, edge := range v.Edges {
|
||||
if !validateArgument(edge, seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *ir.Sigma:
|
||||
return validateArgument(v.X, seen)
|
||||
case *ir.Slice:
|
||||
return validateArgument(v.X, seen)
|
||||
case *ir.Const:
|
||||
return true
|
||||
case *ir.MakeSlice:
|
||||
return true
|
||||
case *ir.Alloc:
|
||||
return true
|
||||
case *ir.Call:
|
||||
if isAppend(v) {
|
||||
return validateArgument(v.Call.Args[0], seen)
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var validateReferrers func(v ir.Value, seen map[ir.Instruction]struct{}) bool
|
||||
validateReferrers = func(v ir.Value, seen map[ir.Instruction]struct{}) bool {
|
||||
for _, ref := range *v.Referrers() {
|
||||
if _, ok := seen[ref]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[ref] = struct{}{}
|
||||
switch ref.(type) {
|
||||
case *ir.Phi:
|
||||
case *ir.Sigma:
|
||||
case *ir.Slice:
|
||||
case *ir.Const:
|
||||
case *ir.MakeSlice:
|
||||
case *ir.Alloc:
|
||||
case *ir.DebugRef:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
if ref, ok := ref.(ir.Value); ok {
|
||||
if !validateReferrers(ref, seen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
val, ok := ins.(ir.Value)
|
||||
if !ok || !isAppend(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
isUsed := false
|
||||
visited := map[ir.Instruction]bool{}
|
||||
var walkRefs func(refs []ir.Instruction)
|
||||
walkRefs = func(refs []ir.Instruction) {
|
||||
loop:
|
||||
for _, ref := range refs {
|
||||
if visited[ref] {
|
||||
continue
|
||||
}
|
||||
visited[ref] = true
|
||||
if _, ok := ref.(*ir.DebugRef); ok {
|
||||
continue
|
||||
}
|
||||
switch ref := ref.(type) {
|
||||
case *ir.Phi:
|
||||
walkRefs(*ref.Referrers())
|
||||
case *ir.Sigma:
|
||||
walkRefs(*ref.Referrers())
|
||||
case ir.Value:
|
||||
if !isAppend(ref) {
|
||||
isUsed = true
|
||||
} else {
|
||||
walkRefs(*ref.Referrers())
|
||||
}
|
||||
case ir.Instruction:
|
||||
isUsed = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refs := val.Referrers()
|
||||
if refs == nil {
|
||||
continue
|
||||
}
|
||||
walkRefs(*refs)
|
||||
|
||||
if isUsed {
|
||||
continue
|
||||
}
|
||||
|
||||
seen := map[ir.Value]struct{}{}
|
||||
if !validateArgument(ins.(*ir.Call).Call.Args[0], seen) {
|
||||
continue
|
||||
}
|
||||
|
||||
seen2 := map[ir.Instruction]struct{}{}
|
||||
for k := range seen {
|
||||
// the only values we allow are also instructions, so this type assertion cannot fail
|
||||
seen2[k.(ir.Instruction)] = struct{}{}
|
||||
}
|
||||
seen2[ins] = struct{}{}
|
||||
failed := false
|
||||
for v := range seen {
|
||||
if !validateReferrers(v, seen2) {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !failed {
|
||||
report.Report(pass, ins, "this result of append is never used, except maybe in other appends")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
88
vendor/honnef.co/go/tools/staticcheck/sa4011/sa4011.go
vendored
Normal file
88
vendor/honnef.co/go/tools/staticcheck/sa4011/sa4011.go
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package sa4011
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4011",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Break statement with no effect. Did you mean to break out of an outer loop?`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
var body *ast.BlockStmt
|
||||
switch node := node.(type) {
|
||||
case *ast.ForStmt:
|
||||
body = node.Body
|
||||
case *ast.RangeStmt:
|
||||
body = node.Body
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(node)
|
||||
}
|
||||
for _, stmt := range body.List {
|
||||
var blocks [][]ast.Stmt
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.SwitchStmt:
|
||||
for _, c := range stmt.Body.List {
|
||||
blocks = append(blocks, c.(*ast.CaseClause).Body)
|
||||
}
|
||||
case *ast.SelectStmt:
|
||||
for _, c := range stmt.Body.List {
|
||||
blocks = append(blocks, c.(*ast.CommClause).Body)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
for _, body := range blocks {
|
||||
if len(body) == 0 {
|
||||
continue
|
||||
}
|
||||
lasts := []ast.Stmt{body[len(body)-1]}
|
||||
// TODO(dh): unfold all levels of nested block
|
||||
// statements, not just a single level if statement
|
||||
if ifs, ok := lasts[0].(*ast.IfStmt); ok {
|
||||
if len(ifs.Body.List) == 0 {
|
||||
continue
|
||||
}
|
||||
lasts[0] = ifs.Body.List[len(ifs.Body.List)-1]
|
||||
|
||||
if block, ok := ifs.Else.(*ast.BlockStmt); ok {
|
||||
if len(block.List) != 0 {
|
||||
lasts = append(lasts, block.List[len(block.List)-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, last := range lasts {
|
||||
branch, ok := last.(*ast.BranchStmt)
|
||||
if !ok || branch.Tok != token.BREAK || branch.Label != nil {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, branch, "ineffective break statement. Did you mean to break out of the outer loop?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
51
vendor/honnef.co/go/tools/staticcheck/sa4012/sa4012.go
vendored
Normal file
51
vendor/honnef.co/go/tools/staticcheck/sa4012/sa4012.go
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package sa4012
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4012",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Comparing a value against NaN even though no value is equal to NaN`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
isNaN := func(v ir.Value) bool {
|
||||
call, ok := v.(*ir.Call)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return irutil.IsCallTo(call.Common(), "math.NaN")
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
ins, ok := ins.(*ir.BinOp)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isNaN(irutil.Flatten(ins.X)) || isNaN(irutil.Flatten(ins.Y)) {
|
||||
report.Report(pass, ins, "no value is equal to NaN, not even NaN itself")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
40
vendor/honnef.co/go/tools/staticcheck/sa4013/sa4013.go
vendored
Normal file
40
vendor/honnef.co/go/tools/staticcheck/sa4013/sa4013.go
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package sa4013
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4013",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Negating a boolean twice (\'!!b\') is the same as writing \'b\'. This is either redundant, or a typo.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkDoubleNegationQ = pattern.MustParse(`(UnaryExpr "!" single@(UnaryExpr "!" x))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, checkDoubleNegationQ) {
|
||||
report.Report(pass, node, "negating a boolean twice has no effect; is this a typo?", report.Fixes(
|
||||
edit.Fix("Turn into single negation", edit.ReplaceWithNode(pass.Fset, node, m.State["single"].(ast.Node))),
|
||||
edit.Fix("Remove double negation", edit.ReplaceWithNode(pass.Fset, node, m.State["x"].(ast.Node)))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
78
vendor/honnef.co/go/tools/staticcheck/sa4014/sa4014.go
vendored
Normal file
78
vendor/honnef.co/go/tools/staticcheck/sa4014/sa4014.go
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package sa4014
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4014",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
seen := map[ast.Node]bool{}
|
||||
|
||||
var collectConds func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool)
|
||||
collectConds = func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) {
|
||||
seen[ifstmt] = true
|
||||
// Bail if any if-statement has an Init statement or side effects in its condition
|
||||
if ifstmt.Init != nil {
|
||||
return nil, false
|
||||
}
|
||||
if code.MayHaveSideEffects(pass, ifstmt.Cond, nil) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
conds = append(conds, ifstmt.Cond)
|
||||
if elsestmt, ok := ifstmt.Else.(*ast.IfStmt); ok {
|
||||
return collectConds(elsestmt, conds)
|
||||
}
|
||||
return conds, true
|
||||
}
|
||||
fn := func(node ast.Node) {
|
||||
ifstmt := node.(*ast.IfStmt)
|
||||
if seen[ifstmt] {
|
||||
// this if-statement is part of an if/else-if chain that we've already processed
|
||||
return
|
||||
}
|
||||
if ifstmt.Else == nil {
|
||||
// there can be at most one condition
|
||||
return
|
||||
}
|
||||
conds, ok := collectConds(ifstmt, nil)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(conds) < 2 {
|
||||
return
|
||||
}
|
||||
counts := map[string]int{}
|
||||
for _, cond := range conds {
|
||||
s := report.Render(pass, cond)
|
||||
counts[s]++
|
||||
if counts[s] == 2 {
|
||||
report.Report(pass, cond, "this condition occurs multiple times in this if/else if chain")
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.IfStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
56
vendor/honnef.co/go/tools/staticcheck/sa4015/sa4015.go
vendored
Normal file
56
vendor/honnef.co/go/tools/staticcheck/sa4015/sa4015.go
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package sa4015
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4015",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(checkMathIntRules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Calling functions like \'math.Ceil\' on floats converted from integers doesn't do anything useful`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkMathIntRules = map[string]callcheck.Check{
|
||||
"math.Ceil": pointlessIntMath,
|
||||
"math.Floor": pointlessIntMath,
|
||||
"math.IsNaN": pointlessIntMath,
|
||||
"math.Trunc": pointlessIntMath,
|
||||
"math.IsInf": pointlessIntMath,
|
||||
}
|
||||
|
||||
func pointlessIntMath(call *callcheck.Call) {
|
||||
if ConvertedFromInt(call.Args[0].Value) {
|
||||
call.Invalid(fmt.Sprintf("calling %s on a converted integer is pointless", irutil.CallName(call.Instr.Common())))
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertedFromInt(v callcheck.Value) bool {
|
||||
conv, ok := v.Value.(*ir.Convert)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return typeutil.NewTypeSet(conv.X.Type()).All(func(t *types.Term) bool {
|
||||
b, ok := t.Type().Underlying().(*types.Basic)
|
||||
return ok && b.Info()&types.IsInteger != 0
|
||||
})
|
||||
}
|
||||
116
vendor/honnef.co/go/tools/staticcheck/sa4016/sa4016.go
vendored
Normal file
116
vendor/honnef.co/go/tools/staticcheck/sa4016/sa4016.go
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package sa4016
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4016",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Certain bitwise operations, such as \'x ^ 0\', do not do anything useful`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
binop := node.(*ast.BinaryExpr)
|
||||
if !typeutil.All(pass.TypesInfo.TypeOf(binop), func(term *types.Term) bool {
|
||||
b, ok := term.Type().Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return (b.Info() & types.IsInteger) != 0
|
||||
}) {
|
||||
return
|
||||
}
|
||||
switch binop.Op {
|
||||
case token.AND, token.OR, token.XOR:
|
||||
default:
|
||||
// we do not flag shifts because too often, x<<0 is part
|
||||
// of a pattern, x<<0, x<<8, x<<16, ...
|
||||
return
|
||||
}
|
||||
if y, ok := binop.Y.(*ast.Ident); ok {
|
||||
obj, ok := pass.TypesInfo.ObjectOf(y).(*types.Const)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if obj.Pkg() != pass.Pkg {
|
||||
// identifier was dot-imported
|
||||
return
|
||||
}
|
||||
if v, _ := constant.Int64Val(obj.Val()); v != 0 {
|
||||
return
|
||||
}
|
||||
path, _ := astutil.PathEnclosingInterval(code.File(pass, obj), obj.Pos(), obj.Pos())
|
||||
if len(path) < 2 {
|
||||
return
|
||||
}
|
||||
spec, ok := path[1].(*ast.ValueSpec)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(spec.Names) != 1 || len(spec.Values) != 1 {
|
||||
// TODO(dh): we could support this
|
||||
return
|
||||
}
|
||||
ident, ok := spec.Values[0].(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !isIota(pass.TypesInfo.ObjectOf(ident)) {
|
||||
return
|
||||
}
|
||||
switch binop.Op {
|
||||
case token.AND:
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s always equals 0; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
|
||||
case token.OR, token.XOR:
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s always equals %s; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.X), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
|
||||
}
|
||||
} else if code.IsIntegerLiteral(pass, binop.Y, constant.MakeInt64(0)) {
|
||||
switch binop.Op {
|
||||
case token.AND:
|
||||
report.Report(pass, node, fmt.Sprintf("%s always equals 0", report.Render(pass, binop)))
|
||||
case token.OR, token.XOR:
|
||||
report.Report(pass, node, fmt.Sprintf("%s always equals %s", report.Render(pass, binop), report.Render(pass, binop.X)))
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func isIota(obj types.Object) bool {
|
||||
if obj.Name() != "iota" {
|
||||
return false
|
||||
}
|
||||
c, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.Pkg() == nil
|
||||
}
|
||||
86
vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go
vendored
Normal file
86
vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package sa4017
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/purity"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4017",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, purity.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Discarding the return values of a function without side effects, making the call pointless`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
pure := pass.ResultOf[purity.Analyzer].(purity.Result)
|
||||
|
||||
fnLoop:
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
if code.IsInTest(pass, fn) {
|
||||
params := fn.Signature.Params()
|
||||
for param := range params.Variables() {
|
||||
if typeutil.IsPointerToTypeWithName(param.Type(), "testing.B") {
|
||||
// Ignore discarded pure functions in code related
|
||||
// to benchmarks. Instead of matching BenchmarkFoo
|
||||
// functions, we match any function accepting a
|
||||
// *testing.B. Benchmarks sometimes call generic
|
||||
// functions for doing the actual work, and
|
||||
// checking for the parameter is a lot easier and
|
||||
// faster than analyzing call trees.
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, b := range fn.Blocks {
|
||||
for _, ins := range b.Instrs {
|
||||
ins, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
refs := ins.Referrers()
|
||||
if refs == nil || len(irutil.FilterDebug(*refs)) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
callee := ins.Common().StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
if callee.Object() == nil {
|
||||
// TODO(dh): support anonymous functions
|
||||
continue
|
||||
}
|
||||
if _, ok := pure[callee.Object().(*types.Func)]; ok {
|
||||
if pass.Pkg.Path() == "fmt_test" && callee.Object().(*types.Func).FullName() == "fmt.Sprintf" {
|
||||
// special case for benchmarks in the fmt package
|
||||
continue
|
||||
}
|
||||
report.Report(pass, ins, fmt.Sprintf("%s doesn't have side effects and its return value is ignored", callee.Object().Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
61
vendor/honnef.co/go/tools/staticcheck/sa4018/sa4018.go
vendored
Normal file
61
vendor/honnef.co/go/tools/staticcheck/sa4018/sa4018.go
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package sa4018
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/facts/purity"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4018",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, purity.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Self-assignment of variables`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
pure := pass.ResultOf[purity.Analyzer].(purity.Result)
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
assign := node.(*ast.AssignStmt)
|
||||
if assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
|
||||
return
|
||||
}
|
||||
for i, lhs := range assign.Lhs {
|
||||
rhs := assign.Rhs[i]
|
||||
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
|
||||
continue
|
||||
}
|
||||
if code.MayHaveSideEffects(pass, lhs, pure) || code.MayHaveSideEffects(pass, rhs, pure) {
|
||||
continue
|
||||
}
|
||||
|
||||
rlh := report.Render(pass, lhs)
|
||||
rrh := report.Render(pass, rhs)
|
||||
if rlh == rrh {
|
||||
report.Report(pass, assign, fmt.Sprintf("self-assignment of %s to %s", rrh, rlh), report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
82
vendor/honnef.co/go/tools/staticcheck/sa4019/sa4019.go
vendored
Normal file
82
vendor/honnef.co/go/tools/staticcheck/sa4019/sa4019.go
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package sa4019
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4019",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Multiple, identical build constraints in the same file`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func buildTagsIdentical(s1, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
s1s := make([]string, len(s1))
|
||||
copy(s1s, s1)
|
||||
sort.Strings(s1s)
|
||||
s2s := make([]string, len(s2))
|
||||
copy(s2s, s2)
|
||||
sort.Strings(s2s)
|
||||
for i, s := range s1s {
|
||||
if s != s2s[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
constraints := buildTags(f)
|
||||
for i, constraint1 := range constraints {
|
||||
for j, constraint2 := range constraints {
|
||||
if i >= j {
|
||||
continue
|
||||
}
|
||||
if buildTagsIdentical(constraint1, constraint2) {
|
||||
msg := fmt.Sprintf("identical build constraints %q and %q",
|
||||
strings.Join(constraint1, " "),
|
||||
strings.Join(constraint2, " "))
|
||||
report.Report(pass, f, msg, report.FilterGenerated(), report.ShortRange())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func buildTags(f *ast.File) [][]string {
|
||||
var out [][]string
|
||||
for line := range strings.SplitSeq(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
|
||||
}
|
||||
172
vendor/honnef.co/go/tools/staticcheck/sa4020/sa4020.go
vendored
Normal file
172
vendor/honnef.co/go/tools/staticcheck/sa4020/sa4020.go
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
package sa4020
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4020",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Unreachable case clause in a type switch`,
|
||||
Text: `In a type switch like the following
|
||||
|
||||
type T struct{}
|
||||
func (T) Read(b []byte) (int, error) { return 0, nil }
|
||||
|
||||
var v any = T{}
|
||||
|
||||
switch v.(type) {
|
||||
case io.Reader:
|
||||
// ...
|
||||
case T:
|
||||
// unreachable
|
||||
}
|
||||
|
||||
the second case clause can never be reached because \'T\' implements
|
||||
\'io.Reader\' and case clauses are evaluated in source order.
|
||||
|
||||
Another example:
|
||||
|
||||
type T struct{}
|
||||
func (T) Read(b []byte) (int, error) { return 0, nil }
|
||||
func (T) Close() error { return nil }
|
||||
|
||||
var v any = T{}
|
||||
|
||||
switch v.(type) {
|
||||
case io.Reader:
|
||||
// ...
|
||||
case io.ReadCloser:
|
||||
// unreachable
|
||||
}
|
||||
|
||||
Even though \'T\' has a \'Close\' method and thus implements \'io.ReadCloser\',
|
||||
\'io.Reader\' will always match first. The method set of \'io.Reader\' is a
|
||||
subset of \'io.ReadCloser\'. Thus it is impossible to match the second
|
||||
case without matching the first case.
|
||||
|
||||
|
||||
Structurally equivalent interfaces
|
||||
|
||||
A special case of the previous example are structurally identical
|
||||
interfaces. Given these declarations
|
||||
|
||||
type T error
|
||||
type V error
|
||||
|
||||
func doSomething() error {
|
||||
err, ok := doAnotherThing()
|
||||
if ok {
|
||||
return T(err)
|
||||
}
|
||||
|
||||
return U(err)
|
||||
}
|
||||
|
||||
the following type switch will have an unreachable case clause:
|
||||
|
||||
switch doSomething().(type) {
|
||||
case T:
|
||||
// ...
|
||||
case V:
|
||||
// unreachable
|
||||
}
|
||||
|
||||
\'T\' will always match before V because they are structurally equivalent
|
||||
and therefore \'doSomething()\''s return value implements both.`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// Check if T subsumes V in a type switch. T subsumes V if T is an interface and T's method set is a subset of V's method set.
|
||||
subsumes := func(T, V types.Type) bool {
|
||||
if typeparams.IsTypeParam(T) {
|
||||
return false
|
||||
}
|
||||
tIface, ok := T.Underlying().(*types.Interface)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return types.Implements(V, tIface)
|
||||
}
|
||||
|
||||
subsumesAny := func(Ts, Vs []types.Type) (types.Type, types.Type, bool) {
|
||||
for _, T := range Ts {
|
||||
for _, V := range Vs {
|
||||
if subsumes(T, V) {
|
||||
return T, V, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
tsStmt := node.(*ast.TypeSwitchStmt)
|
||||
|
||||
type ccAndTypes struct {
|
||||
cc *ast.CaseClause
|
||||
types []types.Type
|
||||
}
|
||||
|
||||
// All asserted types in the order of case clauses.
|
||||
ccs := make([]ccAndTypes, 0, len(tsStmt.Body.List))
|
||||
for _, stmt := range tsStmt.Body.List {
|
||||
cc, _ := stmt.(*ast.CaseClause)
|
||||
|
||||
// Exclude the 'default' case.
|
||||
if len(cc.List) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
Ts := make([]types.Type, 0, len(cc.List))
|
||||
for _, expr := range cc.List {
|
||||
// Exclude the 'nil' value from any 'case' statement (it is always reachable).
|
||||
if typ := pass.TypesInfo.TypeOf(expr); typ != types.Typ[types.UntypedNil] {
|
||||
Ts = append(Ts, typ)
|
||||
}
|
||||
}
|
||||
|
||||
ccs = append(ccs, ccAndTypes{cc: cc, types: Ts})
|
||||
}
|
||||
|
||||
if len(ccs) <= 1 {
|
||||
// Zero or one case clauses, nothing to check.
|
||||
return
|
||||
}
|
||||
|
||||
// Check if case clauses following cc have types that are subsumed by cc.
|
||||
for i, cc := range ccs[:len(ccs)-1] {
|
||||
for _, next := range ccs[i+1:] {
|
||||
if T, V, yes := subsumesAny(cc.types, next.types); yes {
|
||||
report.Report(pass, next.cc, fmt.Sprintf("unreachable case clause: %s will always match before %s", T.String(), V.String()),
|
||||
report.ShortRange())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
36
vendor/honnef.co/go/tools/staticcheck/sa4021/sa4021.go
vendored
Normal file
36
vendor/honnef.co/go/tools/staticcheck/sa4021/sa4021.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package sa4021
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4021",
|
||||
Run: run,
|
||||
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\"x = append(y)\" is equivalent to \"x = y\"`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var checkSingleArgAppendQ = pattern.MustParse(`(CallExpr (Builtin "append") [_])`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node := range code.Matches(pass, checkSingleArgAppendQ) {
|
||||
report.Report(pass, node, "x = append(y) is equivalent to x = y", report.FilterGenerated())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
40
vendor/honnef.co/go/tools/staticcheck/sa4022/sa4022.go
vendored
Normal file
40
vendor/honnef.co/go/tools/staticcheck/sa4022/sa4022.go
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package sa4022
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4022",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Comparing the address of a variable against nil`,
|
||||
Text: `Code such as \"if &x == nil\" is meaningless, because taking the address of a variable always yields a non-nil pointer.`,
|
||||
Since: "2020.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var CheckAddressIsNilQ = pattern.MustParse(
|
||||
`(BinaryExpr
|
||||
(UnaryExpr "&" _)
|
||||
(Or "==" "!=")
|
||||
(Builtin "nil"))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node := range code.Matches(pass, CheckAddressIsNilQ) {
|
||||
report.Report(pass, node, "the address of a variable cannot be nil")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
205
vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go
vendored
Normal file
205
vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package sa4023
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/nilness"
|
||||
"honnef.co/go/tools/analysis/facts/typedness"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/exp/typeparams"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4023",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, typedness.Analysis, nilness.Analysis},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Impossible comparison of interface value with untyped nil`,
|
||||
Text: `Under the covers, interfaces are implemented as two elements, a
|
||||
type T and a value V. V is a concrete value such as an int,
|
||||
struct or pointer, never an interface itself, and has type T. For
|
||||
instance, if we store the int value 3 in an interface, the
|
||||
resulting interface value has, schematically, (T=int, V=3). The
|
||||
value V is also known as the interface's dynamic value, since a
|
||||
given interface variable might hold different values V (and
|
||||
corresponding types T) during the execution of the program.
|
||||
|
||||
An interface value is nil only if the V and T are both
|
||||
unset, (T=nil, V is not set), In particular, a nil interface will
|
||||
always hold a nil type. If we store a nil pointer of type *int
|
||||
inside an interface value, the inner type will be *int regardless
|
||||
of the value of the pointer: (T=*int, V=nil). Such an interface
|
||||
value will therefore be non-nil even when the pointer value V
|
||||
inside is nil.
|
||||
|
||||
This situation can be confusing, and arises when a nil value is
|
||||
stored inside an interface value such as an error return:
|
||||
|
||||
func returnsError() error {
|
||||
var p *MyError = nil
|
||||
if bad() {
|
||||
p = ErrBad
|
||||
}
|
||||
return p // Will always return a non-nil error.
|
||||
}
|
||||
|
||||
If all goes well, the function returns a nil p, so the return
|
||||
value is an error interface value holding (T=*MyError, V=nil).
|
||||
This means that if the caller compares the returned error to nil,
|
||||
it will always look as if there was an error even if nothing bad
|
||||
happened. To return a proper nil error to the caller, the
|
||||
function must return an explicit nil:
|
||||
|
||||
func returnsError() error {
|
||||
if bad() {
|
||||
return ErrBad
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
It's a good idea for functions that return errors always to use
|
||||
the error type in their signature (as we did above) rather than a
|
||||
concrete type such as \'*MyError\', to help guarantee the error is
|
||||
created correctly. As an example, \'os.Open\' returns an error even
|
||||
though, if not nil, it's always of concrete type *os.PathError.
|
||||
|
||||
Similar situations to those described here can arise whenever
|
||||
interfaces are used. Just keep in mind that if any concrete value
|
||||
has been stored in the interface, the interface will not be nil.
|
||||
For more information, see The Laws of
|
||||
Reflection at https://golang.org/doc/articles/laws_of_reflection.html.
|
||||
|
||||
This text has been copied from
|
||||
https://golang.org/doc/faq#nil_error, licensed under the Creative
|
||||
Commons Attribution 3.0 License.`,
|
||||
Since: "2020.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny, // TODO should this be MergeIfAll?
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// The comparison 'fn() == nil' can never be true if fn() returns
|
||||
// an interface value and only returns typed nils. This is usually
|
||||
// a mistake in the function itself, but all we can say for
|
||||
// certain is that the comparison is pointless.
|
||||
//
|
||||
// Flag results if no untyped nils are being returned, but either
|
||||
// known typed nils, or typed unknown nilness are being returned.
|
||||
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
|
||||
typedness := pass.ResultOf[typedness.Analysis].(*typedness.Result)
|
||||
nilness := pass.ResultOf[nilness.Analysis].(*nilness.Result)
|
||||
for _, fn := range irpkg.SrcFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
binop, ok := instr.(*ir.BinOp)
|
||||
if !ok || !(binop.Op == token.EQL || binop.Op == token.NEQ) {
|
||||
continue
|
||||
}
|
||||
if _, ok := binop.X.Type().Underlying().(*types.Interface); !ok || typeparams.IsTypeParam(binop.X.Type()) {
|
||||
// TODO support swapped X and Y
|
||||
continue
|
||||
}
|
||||
|
||||
k, ok := binop.Y.(*ir.Const)
|
||||
if !ok || !k.IsNil() {
|
||||
// if binop.X is an interface, then binop.Y can
|
||||
// only be a Const if its untyped. A typed nil
|
||||
// constant would first be passed to
|
||||
// MakeInterface.
|
||||
continue
|
||||
}
|
||||
|
||||
var idx int
|
||||
var obj *types.Func
|
||||
switch x := irutil.Flatten(binop.X).(type) {
|
||||
case *ir.Call:
|
||||
callee := x.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
obj, _ = callee.Object().(*types.Func)
|
||||
idx = 0
|
||||
case *ir.Extract:
|
||||
call, ok := irutil.Flatten(x.Tuple).(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
callee := call.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
obj, _ = callee.Object().(*types.Func)
|
||||
idx = x.Index
|
||||
case *ir.MakeInterface:
|
||||
var qualifier string
|
||||
switch binop.Op {
|
||||
case token.EQL:
|
||||
qualifier = "never"
|
||||
case token.NEQ:
|
||||
qualifier = "always"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
terms, err := typeparams.NormalTerms(x.X.Type())
|
||||
if len(terms) == 0 || err != nil {
|
||||
// Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type
|
||||
// _can_ be nil when put in an interface value.
|
||||
continue
|
||||
}
|
||||
|
||||
if report.HasRange(x.X) {
|
||||
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier),
|
||||
report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type"))
|
||||
} else {
|
||||
// we can't generate related information for this, so make the diagnostic itself slightly more useful
|
||||
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", qualifier))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
isNil, onlyGlobal := nilness.MayReturnNil(obj, idx)
|
||||
if typedness.MustReturnTyped(obj, idx) && isNil && !onlyGlobal && !code.IsInTest(pass, binop) {
|
||||
// Don't flag these comparisons in tests. Tests
|
||||
// may be explicitly enforcing the invariant that
|
||||
// a value isn't nil.
|
||||
|
||||
var qualifier string
|
||||
switch binop.Op {
|
||||
case token.EQL:
|
||||
qualifier = "never"
|
||||
case token.NEQ:
|
||||
qualifier = "always"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier),
|
||||
// TODO support swapped X and Y
|
||||
report.Related(binop.X, fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", report.Ordinal(idx+1))),
|
||||
report.Related(obj, fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
58
vendor/honnef.co/go/tools/staticcheck/sa4024/sa4024.go
vendored
Normal file
58
vendor/honnef.co/go/tools/staticcheck/sa4024/sa4024.go
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package sa4024
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4024",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Checking for impossible return value from a builtin function`,
|
||||
Text: `Return values of the \'len\' and \'cap\' builtins cannot be negative.
|
||||
|
||||
See https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.
|
||||
|
||||
Example:
|
||||
|
||||
if len(slice) < 0 {
|
||||
fmt.Println("unreachable code")
|
||||
}`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var builtinLessThanZeroQ = pattern.MustParse(`
|
||||
(Or
|
||||
(BinaryExpr
|
||||
(IntegerLiteral "0")
|
||||
">"
|
||||
(CallExpr builtin@(Builtin (Or "len" "cap")) _))
|
||||
(BinaryExpr
|
||||
(CallExpr builtin@(Builtin (Or "len" "cap")) _)
|
||||
"<"
|
||||
(IntegerLiteral "0")))
|
||||
`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, matcher := range code.Matches(pass, builtinLessThanZeroQ) {
|
||||
builtin := matcher.State["builtin"].(*ast.Ident)
|
||||
report.Report(pass, node, fmt.Sprintf("builtin function %s does not return negative values", builtin.Name))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
69
vendor/honnef.co/go/tools/staticcheck/sa4025/sa4025.go
vendored
Normal file
69
vendor/honnef.co/go/tools/staticcheck/sa4025/sa4025.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package sa4025
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4025",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Integer division of literals that results in zero",
|
||||
Text: `When dividing two integer constants, the result will
|
||||
also be an integer. Thus, a division such as \'2 / 3\' results in \'0\'.
|
||||
This is true for all of the following examples:
|
||||
|
||||
_ = 2 / 3
|
||||
const _ = 2 / 3
|
||||
const _ float64 = 2 / 3
|
||||
_ = float64(2 / 3)
|
||||
|
||||
Staticcheck will flag such divisions if both sides of the division are
|
||||
integer literals, as it is highly unlikely that the division was
|
||||
intended to truncate to zero. Staticcheck will not flag integer
|
||||
division involving named constants, to avoid noisy positives.
|
||||
`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var integerDivisionQ = pattern.MustParse(`(BinaryExpr (IntegerLiteral _) "/" (IntegerLiteral _))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node := range code.Matches(pass, integerDivisionQ) {
|
||||
val := constant.ToInt(pass.TypesInfo.Types[node.(ast.Expr)].Value)
|
||||
if v, ok := constant.Uint64Val(val); ok && v == 0 {
|
||||
report.Report(pass, node, fmt.Sprintf("the integer division '%s' results in zero", report.Render(pass, node)))
|
||||
}
|
||||
|
||||
// TODO: we could offer a suggested fix here, but I am not
|
||||
// sure what it should be. There are many options to choose
|
||||
// from.
|
||||
|
||||
// Note: we experimented with flagging divisions that truncate
|
||||
// (e.g. 4 / 3), but it ran into false positives in Go's
|
||||
// 'time' package, which does this, deliberately:
|
||||
//
|
||||
// unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
|
||||
//
|
||||
// The check also found a real bug in other code, but I don't
|
||||
// think we can outright ban this kind of division.
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
79
vendor/honnef.co/go/tools/staticcheck/sa4026/sa4026.go
vendored
Normal file
79
vendor/honnef.co/go/tools/staticcheck/sa4026/sa4026.go
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package sa4026
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4026",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Go constants cannot express negative zero",
|
||||
Text: `In IEEE 754 floating point math, zero has a sign and can be positive
|
||||
or negative. This can be useful in certain numerical code.
|
||||
|
||||
Go constants, however, cannot express negative zero. This means that
|
||||
the literals \'-0.0\' and \'0.0\' have the same ideal value (zero) and
|
||||
will both represent positive zero at runtime.
|
||||
|
||||
To explicitly and reliably create a negative zero, you can use the
|
||||
\'math.Copysign\' function: \'math.Copysign(0, -1)\'.`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var negativeZeroFloatQ = pattern.MustParse(`
|
||||
(Or
|
||||
(UnaryExpr
|
||||
"-"
|
||||
(BasicLit "FLOAT" "0.0"))
|
||||
|
||||
(UnaryExpr
|
||||
"-"
|
||||
(CallExpr conv@(Object (Or "float32" "float64")) lit@(Or (BasicLit "INT" "0") (BasicLit "FLOAT" "0.0"))))
|
||||
|
||||
(CallExpr
|
||||
conv@(Object (Or "float32" "float64"))
|
||||
(UnaryExpr "-" lit@(BasicLit "INT" "0"))))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, negativeZeroFloatQ) {
|
||||
if conv, ok := m.State["conv"].(*types.TypeName); ok {
|
||||
var replacement string
|
||||
// TODO(dh): how does this handle type aliases?
|
||||
if conv.Name() == "float32" {
|
||||
replacement = `float32(math.Copysign(0, -1))`
|
||||
} else {
|
||||
replacement = `math.Copysign(0, -1)`
|
||||
}
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("in Go, the floating-point expression '%s' is the same as '%s(%s)', it does not produce a negative zero",
|
||||
report.Render(pass, node),
|
||||
conv.Name(),
|
||||
report.Render(pass, m.State["lit"])),
|
||||
report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
|
||||
} else {
|
||||
const replacement = `math.Copysign(0, -1)`
|
||||
report.Report(pass, node,
|
||||
"in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero",
|
||||
report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
57
vendor/honnef.co/go/tools/staticcheck/sa4027/sa4027.go
vendored
Normal file
57
vendor/honnef.co/go/tools/staticcheck/sa4027/sa4027.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package sa4027
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4027",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'(*net/url.URL).Query\' returns a copy, modifying it doesn't change the URL`,
|
||||
Text: `\'(*net/url.URL).Query\' parses the current value of \'net/url.URL.RawQuery\'
|
||||
and returns it as a map of type \'net/url.Values\'. Subsequent changes to
|
||||
this map will not affect the URL unless the map gets encoded and
|
||||
assigned to the URL's \'RawQuery\'.
|
||||
|
||||
As a consequence, the following code pattern is an expensive no-op:
|
||||
\'u.Query().Add(key, value)\'.`,
|
||||
Since: "2021.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var ineffectiveURLQueryAddQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (SelectorExpr recv (Ident "Query")) []) (Ident meth)) _)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// TODO(dh): We could make this check more complex and detect
|
||||
// pointless modifications of net/url.Values in general, but that
|
||||
// requires us to get the state machine correct, else we'll cause
|
||||
// false positives.
|
||||
|
||||
for node, m := range code.Matches(pass, ineffectiveURLQueryAddQ) {
|
||||
if !code.IsOfPointerToTypeWithName(pass, m.State["recv"].(ast.Expr), "net/url.URL") {
|
||||
continue
|
||||
}
|
||||
switch m.State["meth"].(string) {
|
||||
case "Add", "Del", "Set":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
report.Report(pass, node, "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
35
vendor/honnef.co/go/tools/staticcheck/sa4028/sa4028.go
vendored
Normal file
35
vendor/honnef.co/go/tools/staticcheck/sa4028/sa4028.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package sa4028
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4028",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\'x % 1\' is always zero`,
|
||||
Since: "2022.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var moduloOneQ = pattern.MustParse(`(BinaryExpr _ "%" (IntegerLiteral "1"))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node := range code.Matches(pass, moduloOneQ) {
|
||||
report.Report(pass, node, "x % 1 is always zero")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
80
vendor/honnef.co/go/tools/staticcheck/sa4029/sa4029.go
vendored
Normal file
80
vendor/honnef.co/go/tools/staticcheck/sa4029/sa4029.go
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package sa4029
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4029",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Ineffective attempt at sorting slice",
|
||||
Text: `
|
||||
\'sort.Float64Slice\', \'sort.IntSlice\', and \'sort.StringSlice\' are
|
||||
types, not functions. Doing \'x = sort.StringSlice(x)\' does nothing,
|
||||
especially not sort any values. The correct usage is
|
||||
\'sort.Sort(sort.StringSlice(x))\' or \'sort.StringSlice(x).Sort()\',
|
||||
but there are more convenient helpers, namely \'sort.Float64s\',
|
||||
\'sort.Ints\', and \'sort.Strings\'.
|
||||
`,
|
||||
Since: "2022.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var ineffectiveSortQ = pattern.MustParse(`(AssignStmt target@(Ident _) "=" (CallExpr typ@(Symbol (Or "sort.Float64Slice" "sort.IntSlice" "sort.StringSlice")) [target]))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, ineffectiveSortQ) {
|
||||
_, ok := types.Unalias(pass.TypesInfo.TypeOf(m.State["target"].(ast.Expr))).(*types.Slice)
|
||||
if !ok {
|
||||
// Avoid flagging 'x = sort.StringSlice(x)' where TypeOf(x) == sort.StringSlice
|
||||
continue
|
||||
}
|
||||
|
||||
var alternative string
|
||||
typeName := types.TypeString(types.Unalias(m.State["typ"].(*types.TypeName).Type()), nil)
|
||||
switch typeName {
|
||||
case "sort.Float64Slice":
|
||||
alternative = "Float64s"
|
||||
case "sort.IntSlice":
|
||||
alternative = "Ints"
|
||||
case "sort.StringSlice":
|
||||
alternative = "Strings"
|
||||
default:
|
||||
panic(fmt.Sprintf("unreachable: %q", typeName))
|
||||
}
|
||||
|
||||
r := &ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: "sort"},
|
||||
Sel: &ast.Ident{Name: alternative},
|
||||
},
|
||||
Args: []ast.Expr{m.State["target"].(ast.Expr)},
|
||||
}
|
||||
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s is a type, not a function, and %s doesn't sort your values; consider using sort.%s instead",
|
||||
typeName,
|
||||
report.Render(pass, node.(*ast.AssignStmt).Rhs[0]),
|
||||
alternative),
|
||||
report.Fixes(edit.Fix(fmt.Sprintf("Replace with call to sort.%s", alternative), edit.ReplaceWithNode(pass.Fset, node, r))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
70
vendor/honnef.co/go/tools/staticcheck/sa4030/sa4030.go
vendored
Normal file
70
vendor/honnef.co/go/tools/staticcheck/sa4030/sa4030.go
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package sa4030
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4030",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Ineffective attempt at generating random number",
|
||||
Text: `
|
||||
Functions in the \'math/rand\' package that accept upper limits, such
|
||||
as \'Intn\', generate random numbers in the half-open interval [0,n). In
|
||||
other words, the generated numbers will be \'>= 0\' and \'< n\' – they
|
||||
don't include \'n\'. \'rand.Intn(1)\' therefore doesn't generate \'0\'
|
||||
or \'1\', it always generates \'0\'.`,
|
||||
Since: "2022.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var ineffectiveRandIntQ = pattern.MustParse(`
|
||||
(CallExpr
|
||||
(Symbol
|
||||
name@(Or
|
||||
"math/rand.Int31n"
|
||||
"math/rand.Int63n"
|
||||
"math/rand.Intn"
|
||||
"(*math/rand.Rand).Int31n"
|
||||
"(*math/rand.Rand).Int63n"
|
||||
"(*math/rand.Rand).Intn"
|
||||
|
||||
"math/rand/v2.Int32N"
|
||||
"math/rand/v2.Int64N"
|
||||
"math/rand/v2.IntN"
|
||||
"math/rand/v2.N"
|
||||
"math/rand/v2.Uint32N"
|
||||
"math/rand/v2.Uint64N"
|
||||
"math/rand/v2.UintN"
|
||||
|
||||
"(*math/rand/v2.Rand).Int32N"
|
||||
"(*math/rand/v2.Rand).Int64N"
|
||||
"(*math/rand/v2.Rand).IntN"
|
||||
"(*math/rand/v2.Rand).Uint32N"
|
||||
"(*math/rand/v2.Rand).Uint64N"
|
||||
"(*math/rand/v2.Rand).UintN"))
|
||||
[(IntegerLiteral "1")])`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, ineffectiveRandIntQ) {
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("%s(n) generates a random value 0 <= x < n; that is, the generated values don't include n; %s therefore always returns 0",
|
||||
m.State["name"], report.Render(pass, node)))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
162
vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go
vendored
Normal file
162
vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package sa4031
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/pattern"
|
||||
"honnef.co/go/tools/staticcheck/sa4022"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4031",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Checking never-nil value against nil`,
|
||||
Since: "2022.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var allocationNilCheckQ = pattern.MustParse(`(IfStmt _ cond@(BinaryExpr lhs op@(Or "==" "!=") (Builtin "nil")) _ _)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
|
||||
var path []ast.Node
|
||||
fn := func(node ast.Node, stack []ast.Node) {
|
||||
m, ok := code.Match(pass, allocationNilCheckQ, node)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
cond := m.State["cond"].(ast.Node)
|
||||
if _, ok := code.Match(pass, sa4022.CheckAddressIsNilQ, cond); ok {
|
||||
// Don't duplicate diagnostics reported by SA4022
|
||||
return
|
||||
}
|
||||
lhs := m.State["lhs"].(ast.Expr)
|
||||
path = path[:0]
|
||||
for i := len(stack) - 1; i >= 0; i-- {
|
||||
path = append(path, stack[i])
|
||||
}
|
||||
irfn := ir.EnclosingFunction(irpkg, path)
|
||||
if irfn == nil {
|
||||
// For example for functions named "_", because we don't generate IR for them.
|
||||
return
|
||||
}
|
||||
v, isAddr := irfn.ValueForExpr(lhs)
|
||||
if isAddr {
|
||||
return
|
||||
}
|
||||
|
||||
seen := map[ir.Value]struct{}{}
|
||||
var values []ir.Value
|
||||
var neverNil func(v ir.Value, track bool) bool
|
||||
neverNil = func(v ir.Value, track bool) bool {
|
||||
if _, ok := seen[v]; ok {
|
||||
return true
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
switch v := v.(type) {
|
||||
case *ir.MakeClosure, *ir.Function:
|
||||
if track {
|
||||
values = append(values, v)
|
||||
}
|
||||
return true
|
||||
case *ir.MakeChan, *ir.MakeMap, *ir.MakeSlice, *ir.Alloc:
|
||||
if track {
|
||||
values = append(values, v)
|
||||
}
|
||||
return true
|
||||
case *ir.Slice:
|
||||
if track {
|
||||
values = append(values, v)
|
||||
}
|
||||
return neverNil(v.X, false)
|
||||
case *ir.FieldAddr:
|
||||
if track {
|
||||
values = append(values, v)
|
||||
}
|
||||
return neverNil(v.X, false)
|
||||
case *ir.Sigma:
|
||||
return neverNil(v.X, true)
|
||||
case *ir.Phi:
|
||||
for _, e := range v.Edges {
|
||||
if !neverNil(e, true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !neverNil(v, true) {
|
||||
return
|
||||
}
|
||||
|
||||
var qualifier string
|
||||
if op := m.State["op"].(token.Token); op == token.EQL {
|
||||
qualifier = "never"
|
||||
} else {
|
||||
qualifier = "always"
|
||||
}
|
||||
fallback := fmt.Sprintf("this nil check is %s true", qualifier)
|
||||
|
||||
sort.Slice(values, func(i, j int) bool { return values[i].Pos() < values[j].Pos() })
|
||||
|
||||
if ident, ok := m.State["lhs"].(*ast.Ident); ok {
|
||||
if _, ok := pass.TypesInfo.ObjectOf(ident).(*types.Var); ok {
|
||||
var opts []report.Option
|
||||
if v.Parent() == irfn {
|
||||
if len(values) == 1 {
|
||||
opts = append(opts, report.Related(values[0], fmt.Sprintf("this is the value of %s", ident.Name)))
|
||||
} else {
|
||||
for _, vv := range values {
|
||||
opts = append(opts, report.Related(vv, fmt.Sprintf("this is one of the value of %s", ident.Name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case *ir.MakeClosure, *ir.Function:
|
||||
report.Report(pass, cond, "the checked variable contains a function and is never nil; did you mean to call it?", opts...)
|
||||
default:
|
||||
report.Report(pass, cond, fallback, opts...)
|
||||
}
|
||||
} else {
|
||||
if _, ok := v.(*ir.Function); ok {
|
||||
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
|
||||
} else {
|
||||
report.Report(pass, cond, fallback)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := v.(*ir.Function); ok {
|
||||
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
|
||||
} else {
|
||||
report.Report(pass, cond, fallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
code.PreorderStack(pass, fn, (*ast.IfStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
221
vendor/honnef.co/go/tools/staticcheck/sa4032/sa4032.go
vendored
Normal file
221
vendor/honnef.co/go/tools/staticcheck/sa4032/sa4032.go
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package sa4032
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build/constraint"
|
||||
"go/constant"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/pattern"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA4032",
|
||||
Run: CheckImpossibleGOOSGOARCH,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Comparing \'runtime.GOOS\' or \'runtime.GOARCH\' against impossible value`,
|
||||
Since: "2024.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
goosComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOOS") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`)
|
||||
goarchComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOARCH") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`)
|
||||
)
|
||||
|
||||
func CheckImpossibleGOOSGOARCH(pass *analysis.Pass) (any, error) {
|
||||
// TODO(dh): validate GOOS and GOARCH together. that is,
|
||||
// given '(linux && amd64) || (windows && mips)',
|
||||
// flag 'if runtime.GOOS == "linux" && runtime.GOARCH == "mips"'
|
||||
//
|
||||
// We can't use our IR for the control flow graph, because go/types constant folds constant comparisons, so
|
||||
// 'runtime.GOOS == "windows"' will just become 'false'. We can't use the AST-based CFG builder from x/tools,
|
||||
// because it doesn't model branch conditions.
|
||||
|
||||
if !code.CouldMatchAny(pass, goarchComparisonQ, goosComparisonQ) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, f := range pass.Files {
|
||||
expr, ok := code.BuildConstraints(pass, f)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
if m, ok := code.Match(pass, goosComparisonQ, node); ok {
|
||||
tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)]
|
||||
goos := constant.StringVal(tv.Value)
|
||||
|
||||
if _, ok := knowledge.KnownGOOS[goos]; !ok {
|
||||
// Don't try to reason about GOOS values we don't know about. Maybe the user is using a newer
|
||||
// version of Go that supports a new target, or maybe they run a fork of Go.
|
||||
return true
|
||||
}
|
||||
sat, ok := validateGOOSComparison(expr, goos)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if !sat {
|
||||
// Note that we do not have to worry about constraints that can never be satisfied, such as 'linux
|
||||
// && windows'. Packages with such files will not be passed to Staticcheck in the first place,
|
||||
// precisely because the constraints aren't satisfiable.
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("due to the file's build constraints, runtime.GOOS will never equal %q", goos))
|
||||
}
|
||||
} else if m, ok := code.Match(pass, goarchComparisonQ, node); ok {
|
||||
tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)]
|
||||
goarch := constant.StringVal(tv.Value)
|
||||
|
||||
if _, ok := knowledge.KnownGOARCH[goarch]; !ok {
|
||||
// Don't try to reason about GOARCH values we don't know about. Maybe the user is using a newer
|
||||
// version of Go that supports a new target, or maybe they run a fork of Go.
|
||||
return true
|
||||
}
|
||||
sat, ok := validateGOARCHComparison(expr, goarch)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if !sat {
|
||||
// Note that we do not have to worry about constraints that can never be satisfied, such as 'amd64
|
||||
// && mips'. Packages with such files will not be passed to Staticcheck in the first place,
|
||||
// precisely because the constraints aren't satisfiable.
|
||||
report.Report(pass, node,
|
||||
fmt.Sprintf("due to the file's build constraints, runtime.GOARCH will never equal %q", goarch))
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
func validateGOOSComparison(expr constraint.Expr, goos string) (sat bool, didCheck bool) {
|
||||
matchGoosTag := func(tag string, goos string) (ok bool, goosTag bool) {
|
||||
switch tag {
|
||||
case "aix",
|
||||
"android",
|
||||
"dragonfly",
|
||||
"freebsd",
|
||||
"hurd",
|
||||
"illumos",
|
||||
"ios",
|
||||
"js",
|
||||
"netbsd",
|
||||
"openbsd",
|
||||
"plan9",
|
||||
"wasip1",
|
||||
"windows":
|
||||
return goos == tag, true
|
||||
case "darwin":
|
||||
return (goos == "darwin" || goos == "ios"), true
|
||||
case "linux":
|
||||
return (goos == "linux" || goos == "android"), true
|
||||
case "solaris":
|
||||
return (goos == "solaris" || goos == "illumos"), true
|
||||
case "unix":
|
||||
return (goos == "aix" ||
|
||||
goos == "android" ||
|
||||
goos == "darwin" ||
|
||||
goos == "dragonfly" ||
|
||||
goos == "freebsd" ||
|
||||
goos == "hurd" ||
|
||||
goos == "illumos" ||
|
||||
goos == "ios" ||
|
||||
goos == "linux" ||
|
||||
goos == "netbsd" ||
|
||||
goos == "openbsd" ||
|
||||
goos == "solaris"), true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
return validateTagComparison(expr, func(tag string) (matched bool, special bool) {
|
||||
return matchGoosTag(tag, goos)
|
||||
})
|
||||
}
|
||||
|
||||
func validateGOARCHComparison(expr constraint.Expr, goarch string) (sat bool, didCheck bool) {
|
||||
matchGoarchTag := func(tag string, goarch string) (ok bool, goosTag bool) {
|
||||
switch tag {
|
||||
case "386",
|
||||
"amd64",
|
||||
"arm",
|
||||
"arm64",
|
||||
"loong64",
|
||||
"mips",
|
||||
"mipsle",
|
||||
"mips64",
|
||||
"mips64le",
|
||||
"ppc64",
|
||||
"ppc64le",
|
||||
"riscv64",
|
||||
"s390x",
|
||||
"sparc64",
|
||||
"wasm":
|
||||
return goarch == tag, true
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
return validateTagComparison(expr, func(tag string) (matched bool, special bool) {
|
||||
return matchGoarchTag(tag, goarch)
|
||||
})
|
||||
}
|
||||
|
||||
func validateTagComparison(expr constraint.Expr, matchSpecialTag func(tag string) (matched bool, special bool)) (sat bool, didCheck bool) {
|
||||
otherTags := map[string]int{}
|
||||
// Collect all tags that aren't known architecture-based tags
|
||||
b := expr.Eval(func(tag string) bool {
|
||||
ok, special := matchSpecialTag(tag)
|
||||
if !special {
|
||||
// Assign an ID to this tag, but only if we haven't seen it before. For the expression 'foo && foo', this
|
||||
// callback will be called twice for the 'foo' tag.
|
||||
if _, ok := otherTags[tag]; !ok {
|
||||
otherTags[tag] = len(otherTags)
|
||||
}
|
||||
}
|
||||
return ok
|
||||
})
|
||||
|
||||
if b || len(otherTags) == 0 {
|
||||
// We're done. Either the formula can be satisfied regardless of the values of non-special tags, if any,
|
||||
// or there aren't any non-special tags and the formula cannot be satisfied.
|
||||
return b, true
|
||||
}
|
||||
|
||||
if len(otherTags) > 10 {
|
||||
// We have to try 2**len(otherTags) combinations of tags. 2**10 is about the worst we're willing to try.
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Try all permutations of otherTags. If any evaluates to true, then the expression is satisfiable.
|
||||
for bits := 0; bits < 1<<len(otherTags); bits++ {
|
||||
b := expr.Eval(func(tag string) bool {
|
||||
ok, special := matchSpecialTag(tag)
|
||||
if special {
|
||||
return ok
|
||||
}
|
||||
return bits&(1<<otherTags[tag]) != 0
|
||||
})
|
||||
if b {
|
||||
return true, true
|
||||
}
|
||||
}
|
||||
|
||||
return false, true
|
||||
}
|
||||
49
vendor/honnef.co/go/tools/staticcheck/sa5000/sa5000.go
vendored
Normal file
49
vendor/honnef.co/go/tools/staticcheck/sa5000/sa5000.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package sa5000
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5000",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Assignment to nil map`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, block := range fn.Blocks {
|
||||
for _, ins := range block.Instrs {
|
||||
mu, ok := ins.(*ir.MapUpdate)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c, ok := irutil.Flatten(mu.Map).(*ir.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if c.Value != nil {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, mu, "assignment to nil map")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
109
vendor/honnef.co/go/tools/staticcheck/sa5001/sa5001.go
vendored
Normal file
109
vendor/honnef.co/go/tools/staticcheck/sa5001/sa5001.go
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package sa5001
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Deferring \'Close\' before checking for a possible error`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
block := node.(*ast.BlockStmt)
|
||||
if len(block.List) < 2 {
|
||||
return
|
||||
}
|
||||
for i, stmt := range block.List {
|
||||
if i == len(block.List)-1 {
|
||||
break
|
||||
}
|
||||
assign, ok := stmt.(*ast.AssignStmt)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(assign.Rhs) != 1 {
|
||||
continue
|
||||
}
|
||||
if len(assign.Lhs) < 2 {
|
||||
continue
|
||||
}
|
||||
if lhs, ok := assign.Lhs[len(assign.Lhs)-1].(*ast.Ident); ok && lhs.Name == "_" {
|
||||
continue
|
||||
}
|
||||
call, ok := assign.Rhs[0].(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if sig.Results().Len() < 2 {
|
||||
continue
|
||||
}
|
||||
last := sig.Results().At(sig.Results().Len() - 1)
|
||||
// FIXME(dh): check that it's error from universe, not
|
||||
// another type of the same name
|
||||
if last.Type().String() != "error" {
|
||||
continue
|
||||
}
|
||||
lhs, ok := assign.Lhs[0].(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
def, ok := block.List[i+1].(*ast.DeferStmt)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sel, ok := def.Call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ident, ok := selectorX(sel).(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(lhs) {
|
||||
continue
|
||||
}
|
||||
if sel.Sel.Name != "Close" {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, def, fmt.Sprintf("should check error returned from %s() before deferring %s",
|
||||
report.Render(pass, call.Fun), report.Render(pass, def.Call)))
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func selectorX(sel *ast.SelectorExpr) ast.Node {
|
||||
switch x := sel.X.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
return selectorX(x)
|
||||
default:
|
||||
return x
|
||||
}
|
||||
}
|
||||
74
vendor/honnef.co/go/tools/staticcheck/sa5002/sa5002.go
vendored
Normal file
74
vendor/honnef.co/go/tools/staticcheck/sa5002/sa5002.go
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package sa5002
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5002",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `The empty for loop (\"for {}\") spins and can block the scheduler`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
loop := node.(*ast.ForStmt)
|
||||
if len(loop.Body.List) != 0 || loop.Post != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if loop.Init != nil {
|
||||
// TODO(dh): this isn't strictly necessary, it just makes
|
||||
// the check easier.
|
||||
return
|
||||
}
|
||||
// An empty loop is bad news in two cases: 1) The loop has no
|
||||
// condition. In that case, it's just a loop that spins
|
||||
// forever and as fast as it can, keeping a core busy. 2) The
|
||||
// loop condition only consists of variable or field reads and
|
||||
// operators on those. The only way those could change their
|
||||
// value is with unsynchronised access, which constitutes a
|
||||
// data race.
|
||||
//
|
||||
// If the condition contains any function calls, its behaviour
|
||||
// is dynamic and the loop might terminate. Similarly for
|
||||
// channel receives.
|
||||
|
||||
if loop.Cond != nil {
|
||||
if code.MayHaveSideEffects(pass, loop.Cond, nil) {
|
||||
return
|
||||
}
|
||||
if ident, ok := loop.Cond.(*ast.Ident); ok {
|
||||
if k, ok := pass.TypesInfo.ObjectOf(ident).(*types.Const); ok {
|
||||
if !constant.BoolVal(k.Val()) {
|
||||
// don't flag `for false {}` loops. They're a debug aid.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
report.Report(pass, loop, "loop condition never changes or has a race condition")
|
||||
}
|
||||
report.Report(pass, loop, "this loop will spin, using 100% CPU", report.ShortRange())
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.ForStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
74
vendor/honnef.co/go/tools/staticcheck/sa5003/sa5003.go
vendored
Normal file
74
vendor/honnef.co/go/tools/staticcheck/sa5003/sa5003.go
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package sa5003
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5003",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Defers in infinite loops will never execute`,
|
||||
Text: `Defers are scoped to the surrounding function, not the surrounding
|
||||
block. In a function that never returns, i.e. one containing an
|
||||
infinite loop, defers will never execute.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
mightExit := false
|
||||
var defers []ast.Stmt
|
||||
loop := node.(*ast.ForStmt)
|
||||
if loop.Cond != nil {
|
||||
return
|
||||
}
|
||||
fn2 := func(node ast.Node) bool {
|
||||
switch stmt := node.(type) {
|
||||
case *ast.ReturnStmt:
|
||||
mightExit = true
|
||||
return false
|
||||
case *ast.BranchStmt:
|
||||
// TODO(dominikh): if this sees a break in a switch or
|
||||
// select, it doesn't check if it breaks the loop or
|
||||
// just the select/switch. This causes some false
|
||||
// negatives.
|
||||
if stmt.Tok == token.BREAK {
|
||||
mightExit = true
|
||||
return false
|
||||
}
|
||||
case *ast.DeferStmt:
|
||||
defers = append(defers, stmt)
|
||||
case *ast.FuncLit:
|
||||
// Don't look into function bodies
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
ast.Inspect(loop.Body, fn2)
|
||||
if mightExit {
|
||||
return
|
||||
}
|
||||
for _, stmt := range defers {
|
||||
report.Report(pass, stmt, "defers in this infinite loop will never run")
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.ForStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
49
vendor/honnef.co/go/tools/staticcheck/sa5004/sa5004.go
vendored
Normal file
49
vendor/honnef.co/go/tools/staticcheck/sa5004/sa5004.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package sa5004
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5004",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `\"for { select { ...\" with an empty default branch spins`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`(ForStmt nil nil nil (SelectStmt body))`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, m := range code.Matches(pass, query) {
|
||||
for _, c := range m.State["body"].([]ast.Stmt) {
|
||||
// FIXME this leaves behind an empty line, and possibly
|
||||
// comments in the default branch. We can't easily fix
|
||||
// either.
|
||||
if comm, ok := c.(*ast.CommClause); ok && comm.Comm == nil && len(comm.Body) == 0 {
|
||||
report.Report(pass, comm,
|
||||
"should not have an empty default case in a for+select loop; the loop will spin",
|
||||
report.Fixes(edit.Fix("Remove empty default branch", edit.Delete(comm))))
|
||||
// there can only be one default case
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
90
vendor/honnef.co/go/tools/staticcheck/sa5005/sa5005.go
vendored
Normal file
90
vendor/honnef.co/go/tools/staticcheck/sa5005/sa5005.go
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package sa5005
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5005",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `The finalizer references the finalized object, preventing garbage collection`,
|
||||
Text: `A finalizer is a function associated with an object that runs when the
|
||||
garbage collector is ready to collect said object, that is when the
|
||||
object is no longer referenced by anything.
|
||||
|
||||
If the finalizer references the object, however, it will always remain
|
||||
as the final reference to that object, preventing the garbage
|
||||
collector from collecting the object. The finalizer will never run,
|
||||
and the object will never be collected, leading to a memory leak. That
|
||||
is why the finalizer should instead use its first argument to operate
|
||||
on the object. That way, the number of references can temporarily go
|
||||
to zero before the object is being passed to the finalizer.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
|
||||
if callee.RelString(nil) != "runtime.SetFinalizer" {
|
||||
return
|
||||
}
|
||||
arg0 := site.Common().Args[knowledge.Arg("runtime.SetFinalizer.obj")]
|
||||
if iface, ok := arg0.(*ir.MakeInterface); ok {
|
||||
arg0 = iface.X
|
||||
}
|
||||
load, ok := arg0.(*ir.Load)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, ok := load.X.(*ir.Alloc)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
arg1 := site.Common().Args[knowledge.Arg("runtime.SetFinalizer.finalizer")]
|
||||
if iface, ok := arg1.(*ir.MakeInterface); ok {
|
||||
arg1 = iface.X
|
||||
}
|
||||
mc, ok := arg1.(*ir.MakeClosure)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, b := range mc.Bindings {
|
||||
if b == v {
|
||||
pos := report.DisplayPosition(pass.Fset, mc.Fn.Pos())
|
||||
report.Report(pass, site, fmt.Sprintf("the finalizer closes over the object, preventing the finalizer from ever running (at %s)", pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
eachCall(fn, cb)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ir.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
cb(fn, site, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go
vendored
Normal file
77
vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package sa5007
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5007",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Infinite recursive call`,
|
||||
Text: `A function that calls itself recursively needs to have an exit
|
||||
condition. Otherwise it will recurse forever, until the system runs
|
||||
out of memory.
|
||||
|
||||
This issue can be caused by simple bugs such as forgetting to add an
|
||||
exit condition. It can also happen "on purpose". Some languages have
|
||||
tail call optimization which makes certain infinite recursive calls
|
||||
safe to use. Go, however, does not implement TCO, and as such a loop
|
||||
should be used instead.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
eachCall(fn, func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
|
||||
if callee != fn {
|
||||
return
|
||||
}
|
||||
if _, ok := site.(*ir.Go); ok {
|
||||
// Recursively spawning goroutines doesn't consume
|
||||
// stack space infinitely, so don't flag it.
|
||||
return
|
||||
}
|
||||
|
||||
block := site.Block()
|
||||
for _, b := range fn.Blocks {
|
||||
if block.Dominates(b) {
|
||||
continue
|
||||
}
|
||||
if len(b.Instrs) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := b.Control().(*ir.Return); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
report.Report(pass, site, "infinite recursive call")
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if site, ok := instr.(ir.CallInstruction); ok {
|
||||
if g := site.Common().StaticCallee(); g != nil {
|
||||
cb(fn, site, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
288
vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go
vendored
Normal file
288
vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
|
||||
// This file is a modified copy of Go's encoding/json/v2/field.go
|
||||
|
||||
package sa5008
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
func validateJSONTag(pass *analysis.Pass, field *ast.Field, tag string) {
|
||||
hasTag := tag != ""
|
||||
tagOrig := tag
|
||||
|
||||
// Check whether this field is explicitly ignored.
|
||||
if tag == "-" {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether this field is unexported and not embedded,
|
||||
// which Go reflection cannot mutate for the sake of serialization.
|
||||
//
|
||||
// An embedded field of an unexported type is still capable of
|
||||
// forwarding exported fields, which may be JSON serialized.
|
||||
// This technically operates on the edge of what is permissible by
|
||||
// the Go language, but the most recent decision is to permit this.
|
||||
//
|
||||
// See https://go.dev/issue/24153 and https://go.dev/issue/32772.
|
||||
anonymous := len(field.Names) == 0
|
||||
if !anonymous && !field.Names[0].IsExported() {
|
||||
// Tag options specified on an unexported field suggests user error.
|
||||
if hasTag {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("unexported struct field cannot have non-ignored `json:%q` tag", tag))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
|
||||
// For better compatibility with v1, accept almost any unescaped name.
|
||||
n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
|
||||
return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes
|
||||
}))
|
||||
name := tag[:n]
|
||||
|
||||
// If the next character is not a comma, then the name is either
|
||||
// malformed (if n > 0) or a single-quoted name.
|
||||
// In either case, call consumeTagOption to handle it further.
|
||||
var err error
|
||||
if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
|
||||
name, n, err = consumeTagOption(tag)
|
||||
if err != nil {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err))
|
||||
}
|
||||
}
|
||||
if !utf8.ValidString(name) {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("invalid UTF-8 in JSON object name %q", name))
|
||||
name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError
|
||||
}
|
||||
if name == "-" && tag[0] == '-' {
|
||||
// TODO(dh): offer automatic fix
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("should encoding/json ignore this field or name it \"-\"? Either use `json:\"-\"` to ignore the field or use `json:\"'-'%s` to specify %q as the name",
|
||||
strings.TrimPrefix(strconv.Quote(tagOrig), `"-`), name))
|
||||
}
|
||||
tag = tag[n:]
|
||||
}
|
||||
|
||||
// Handle any additional tag options (if any).
|
||||
var wasFormat bool
|
||||
seenOpts := make(map[string]bool)
|
||||
for len(tag) > 0 {
|
||||
// Consume comma delimiter.
|
||||
if tag[0] != ',' {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("malformed `json` tag: invalid character %q before next option (expecting ',')",
|
||||
tag[0]))
|
||||
} else {
|
||||
tag = tag[len(","):]
|
||||
if len(tag) == 0 {
|
||||
report.Report(pass, field.Tag, "malformed `json` tag: invalid trailing ',' character")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Consume and process the tag option.
|
||||
opt, n, err := consumeTagOption(tag)
|
||||
if err != nil {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err))
|
||||
}
|
||||
rawOpt := tag[:n]
|
||||
tag = tag[n:]
|
||||
switch {
|
||||
case wasFormat:
|
||||
report.Report(pass, field.Tag, "`format` tag option was not specified last")
|
||||
case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
|
||||
// TODO(dh): offer automatic fix
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", rawOpt, opt))
|
||||
}
|
||||
switch opt {
|
||||
case "case":
|
||||
if !strings.HasPrefix(tag, ":") {
|
||||
// TODO(dh): offer automatic fix
|
||||
report.Report(pass, field.Tag,
|
||||
"missing value for `case` tag option; specify `case:ignore` or `case:strict` instead")
|
||||
break
|
||||
}
|
||||
tag = tag[len(":"):]
|
||||
opt, n, err := consumeTagOption(tag)
|
||||
if err != nil {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("malformed value for `case` tag option: %v", err))
|
||||
break
|
||||
}
|
||||
rawOpt := tag[:n]
|
||||
tag = tag[n:]
|
||||
if strings.HasPrefix(rawOpt, "'") {
|
||||
// TODO(dh): offer automatic fix
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead",
|
||||
rawOpt, opt))
|
||||
}
|
||||
switch opt {
|
||||
case "ignore":
|
||||
case "strict":
|
||||
default:
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("invalid appearance of unknown `case:%s` tag value", rawOpt))
|
||||
}
|
||||
case "inline":
|
||||
case "unknown":
|
||||
case "omitzero":
|
||||
case "omitempty":
|
||||
case "string":
|
||||
const msg = "invalid appearance of `string` tag option; it is only intended for fields of numeric types or pointers to those"
|
||||
tset := typeutil.NewTypeSet(pass.TypesInfo.TypeOf(field.Type))
|
||||
if len(tset.Terms) == 0 {
|
||||
// TODO(dh): improve message, call out the use of type parameters
|
||||
report.Report(pass, field.Tag, msg)
|
||||
continue
|
||||
}
|
||||
for _, term := range tset.Terms {
|
||||
T := typeutil.Dereference(term.Type().Underlying())
|
||||
for _, term2 := range typeutil.NewTypeSet(T).Terms {
|
||||
basic, ok := term2.Type().Underlying().(*types.Basic)
|
||||
// We accept bools and strings because v1 of encoding/json
|
||||
// supports those. We don't mention that in the message,
|
||||
// however, because their support is accidental, and v2
|
||||
// doesn't support it.
|
||||
if !ok || (basic.Info()&(types.IsBoolean|types.IsInteger|types.IsFloat|types.IsString)) == 0 {
|
||||
// TODO(dh): improve message, show how we arrived at the type
|
||||
report.Report(pass, field.Tag, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "format":
|
||||
if !strings.HasPrefix(tag, ":") {
|
||||
report.Report(pass, field.Tag, "missing value for `format` tag option")
|
||||
break
|
||||
}
|
||||
tag = tag[len(":"):]
|
||||
_, n, err := consumeTagOption(tag)
|
||||
if err != nil {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("malformed value for `format` tag option: %v", err))
|
||||
break
|
||||
}
|
||||
tag = tag[n:]
|
||||
wasFormat = true
|
||||
default:
|
||||
// Reject keys that resemble one of the supported options.
|
||||
// This catches invalid mutants such as "omitEmpty" or "omit_empty".
|
||||
normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
|
||||
switch normOpt {
|
||||
case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("invalid appearance of `%s` tag option; specify `%s` instead",
|
||||
opt, normOpt))
|
||||
default:
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("invalid appearance of unknown `%s` tag option", opt))
|
||||
}
|
||||
}
|
||||
|
||||
// Reject duplicates.
|
||||
if seenOpts[opt] {
|
||||
report.Report(pass, field.Tag,
|
||||
fmt.Sprintf("duplicate appearance of `%s` tag option", rawOpt))
|
||||
}
|
||||
seenOpts[opt] = true
|
||||
}
|
||||
|
||||
if seenOpts["inline"] && seenOpts["unknown"] {
|
||||
report.Report(pass, field.Tag,
|
||||
"field cannot have both `inline` and `unknown` specified")
|
||||
}
|
||||
|
||||
// TODO(dh): implement more restrictions for types of inlined and unknown
|
||||
// fields, including recursive restrictions:
|
||||
//
|
||||
// - Go struct field %s cannot have any options other than `inline` or `unknown` specified
|
||||
// - inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value
|
||||
// - inlined Go struct field %s is not exported
|
||||
// - inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods
|
||||
// - inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value
|
||||
}
|
||||
|
||||
// consumeTagOption consumes the next option,
|
||||
// which is either a Go identifier or a single-quoted string.
|
||||
// If the next option is invalid, it returns all of in until the next comma,
|
||||
// and reports an error.
|
||||
func consumeTagOption(in string) (string, int, error) {
|
||||
// For legacy compatibility with v1, assume options are comma-separated.
|
||||
i := strings.IndexByte(in, ',')
|
||||
if i < 0 {
|
||||
i = len(in)
|
||||
}
|
||||
|
||||
switch r, _ := utf8.DecodeRuneInString(in); {
|
||||
// Option as a Go identifier.
|
||||
case r == '_' || unicode.IsLetter(r):
|
||||
n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
|
||||
return in[:n], n, nil
|
||||
// Option as a single-quoted string.
|
||||
case r == '\'':
|
||||
// The grammar is nearly identical to a double-quoted Go string literal,
|
||||
// but uses single quotes as the terminators. The reason for a custom
|
||||
// grammar is because both backtick and double quotes cannot be used
|
||||
// verbatim in a struct tag.
|
||||
//
|
||||
// Convert a single-quoted string to a double-quote string and rely on
|
||||
// strconv.Unquote to handle the rest.
|
||||
var inEscape bool
|
||||
b := []byte{'"'}
|
||||
n := len(`'`)
|
||||
for len(in) > n {
|
||||
r, rn := utf8.DecodeRuneInString(in[n:])
|
||||
switch {
|
||||
case inEscape:
|
||||
if r == '\'' {
|
||||
b = b[:len(b)-1] // remove escape character: `\'` => `'`
|
||||
}
|
||||
inEscape = false
|
||||
case r == '\\':
|
||||
inEscape = true
|
||||
case r == '"':
|
||||
b = append(b, '\\') // insert escape character: `"` => `\"`
|
||||
case r == '\'':
|
||||
b = append(b, '"')
|
||||
n += len(`'`)
|
||||
out, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n])
|
||||
}
|
||||
return out, n, nil
|
||||
}
|
||||
b = append(b, in[n:][:rn]...)
|
||||
n += rn
|
||||
}
|
||||
if n > 10 {
|
||||
n = 10 // limit the amount of context printed in the error
|
||||
}
|
||||
//lint:ignore ST1005 The ellipsis denotes truncated text
|
||||
return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
|
||||
case len(in) == 0:
|
||||
return in[:i], i, io.ErrUnexpectedEOF
|
||||
default:
|
||||
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
|
||||
}
|
||||
}
|
||||
|
||||
func isLetterOrDigit(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||
}
|
||||
130
vendor/honnef.co/go/tools/staticcheck/sa5008/sa5008.go
vendored
Normal file
130
vendor/honnef.co/go/tools/staticcheck/sa5008/sa5008.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package sa5008
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/staticcheck/fakereflect"
|
||||
"honnef.co/go/tools/staticcheck/fakexml"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5008",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid struct tag`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
importsGoFlags := false
|
||||
|
||||
// we use the AST instead of (*types.Package).Imports to work
|
||||
// around vendored packages in GOPATH mode. A vendored package's
|
||||
// path will include the vendoring subtree as a prefix.
|
||||
for _, f := range pass.Files {
|
||||
for _, imp := range f.Imports {
|
||||
v := imp.Path.Value
|
||||
if v[1:len(v)-1] == "github.com/jessevdk/go-flags" {
|
||||
importsGoFlags = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
structNode := node.(*ast.StructType)
|
||||
T := pass.TypesInfo.Types[structNode].Type.(*types.Struct)
|
||||
rt := fakereflect.TypeAndCanAddr{
|
||||
Type: T,
|
||||
}
|
||||
for i, field := range structNode.Fields.List {
|
||||
if field.Tag == nil {
|
||||
continue
|
||||
}
|
||||
tags, err := parseStructTag(field.Tag.Value[1 : len(field.Tag.Value)-1])
|
||||
if err != nil {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("unparseable struct tag: %s", err))
|
||||
continue
|
||||
}
|
||||
for k, v := range tags {
|
||||
if len(v) > 1 {
|
||||
isGoFlagsTag := importsGoFlags &&
|
||||
(k == "choice" || k == "optional-value" || k == "default")
|
||||
if !isGoFlagsTag {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("duplicate struct tag %q", k))
|
||||
}
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "json":
|
||||
checkJSONTag(pass, field, v[0])
|
||||
case "xml":
|
||||
if _, err := fakexml.StructFieldInfo(rt.Field(i)); err != nil {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: %s", err))
|
||||
}
|
||||
checkXMLTag(pass, field, v[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.StructType)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func checkJSONTag(pass *analysis.Pass, field *ast.Field, tag string) {
|
||||
if pass.Pkg.Path() == "encoding/json" ||
|
||||
pass.Pkg.Path() == "encoding/json_test" ||
|
||||
pass.Pkg.Path() == "encoding/json/v2" ||
|
||||
pass.Pkg.Path() == "encoding/json/v2_test" {
|
||||
// don't flag malformed JSON tags in the encoding/json
|
||||
// package; it knows what it is doing, and it is testing
|
||||
// itself.
|
||||
return
|
||||
}
|
||||
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
|
||||
if len(tag) == 0 {
|
||||
}
|
||||
|
||||
validateJSONTag(pass, field, tag)
|
||||
}
|
||||
|
||||
func checkXMLTag(pass *analysis.Pass, field *ast.Field, tag string) {
|
||||
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
|
||||
if len(tag) == 0 {
|
||||
}
|
||||
fields := strings.Split(tag, ",")
|
||||
counts := map[string]int{}
|
||||
for _, s := range fields[1:] {
|
||||
switch s {
|
||||
case "attr", "chardata", "cdata", "innerxml", "comment":
|
||||
counts[s]++
|
||||
case "omitempty", "any":
|
||||
counts[s]++
|
||||
case "":
|
||||
default:
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: unknown option %q", s))
|
||||
}
|
||||
}
|
||||
for k, v := range counts {
|
||||
if v > 1 {
|
||||
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: duplicate option %q", k))
|
||||
}
|
||||
}
|
||||
}
|
||||
58
vendor/honnef.co/go/tools/staticcheck/sa5008/structtag.go
vendored
Normal file
58
vendor/honnef.co/go/tools/staticcheck/sa5008/structtag.go
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2019 Dominik Honnef. All rights reserved.
|
||||
|
||||
package sa5008
|
||||
|
||||
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
|
||||
}
|
||||
409
vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go
vendored
Normal file
409
vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
package sa5009
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
"honnef.co/go/tools/printf"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5009",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Invalid Printf call`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
// TODO(dh): detect printf wrappers
|
||||
var rules = map[string]callcheck.Check{
|
||||
"fmt.Errorf": func(call *callcheck.Call) { check(call, 0, 1) },
|
||||
"fmt.Printf": func(call *callcheck.Call) { check(call, 0, 1) },
|
||||
"fmt.Sprintf": func(call *callcheck.Call) { check(call, 0, 1) },
|
||||
"fmt.Fprintf": func(call *callcheck.Call) { check(call, 1, 2) },
|
||||
"golang.org/x/xerrors.Errorf": func(call *callcheck.Call) { check(call, 0, 1) },
|
||||
}
|
||||
|
||||
type verbFlag int
|
||||
|
||||
const (
|
||||
isInt verbFlag = 1 << iota
|
||||
isBool
|
||||
isFP
|
||||
isString
|
||||
isPointer
|
||||
// Verbs that accept "pseudo pointers" will sometimes dereference
|
||||
// non-nil pointers. For example, %x on a non-nil *struct will print the
|
||||
// individual fields, but on a nil pointer it will print the address.
|
||||
isPseudoPointer
|
||||
isSlice
|
||||
isAny
|
||||
noRecurse
|
||||
)
|
||||
|
||||
var verbs = [...]verbFlag{
|
||||
'b': isPseudoPointer | isInt | isFP,
|
||||
'c': isInt,
|
||||
'd': isPseudoPointer | isInt,
|
||||
'e': isFP,
|
||||
'E': isFP,
|
||||
'f': isFP,
|
||||
'F': isFP,
|
||||
'g': isFP,
|
||||
'G': isFP,
|
||||
'o': isPseudoPointer | isInt,
|
||||
'O': isPseudoPointer | isInt,
|
||||
'p': isSlice | isPointer | noRecurse,
|
||||
'q': isInt | isString,
|
||||
's': isString,
|
||||
't': isBool,
|
||||
'T': isAny,
|
||||
'U': isInt,
|
||||
'v': isAny,
|
||||
'X': isPseudoPointer | isInt | isFP | isString,
|
||||
'x': isPseudoPointer | isInt | isFP | isString,
|
||||
}
|
||||
|
||||
func check(call *callcheck.Call, fIdx, vIdx int) {
|
||||
f := call.Args[fIdx]
|
||||
var args []ir.Value
|
||||
switch v := call.Args[vIdx].Value.Value.(type) {
|
||||
case *ir.Slice:
|
||||
var ok bool
|
||||
args, ok = irutil.Vararg(v)
|
||||
if !ok {
|
||||
// We don't know what the actual arguments to the function are
|
||||
return
|
||||
}
|
||||
case *ir.Const:
|
||||
// nil, i.e. no arguments
|
||||
default:
|
||||
// We don't know what the actual arguments to the function are
|
||||
return
|
||||
}
|
||||
checkImpl(f, f.Value.Value, args)
|
||||
}
|
||||
|
||||
func checkImpl(carg *callcheck.Argument, f ir.Value, args []ir.Value) {
|
||||
var msCache *typeutil.MethodSetCache
|
||||
if f.Parent() != nil {
|
||||
msCache = &f.Parent().Prog.MethodSets
|
||||
}
|
||||
|
||||
elem := func(T types.Type, verb rune) ([]types.Type, bool) {
|
||||
if verbs[verb]&noRecurse != 0 {
|
||||
return []types.Type{T}, false
|
||||
}
|
||||
switch T := T.(type) {
|
||||
case *types.Slice:
|
||||
if verbs[verb]&isSlice != 0 {
|
||||
return []types.Type{T}, false
|
||||
}
|
||||
if verbs[verb]&isString != 0 && types.Identical(T.Elem().Underlying(), types.Typ[types.Byte]) {
|
||||
return []types.Type{T}, false
|
||||
}
|
||||
return []types.Type{T.Elem()}, true
|
||||
case *types.Map:
|
||||
key := T.Key()
|
||||
val := T.Elem()
|
||||
return []types.Type{key, val}, true
|
||||
case *types.Struct:
|
||||
out := make([]types.Type, 0, T.NumFields())
|
||||
for field := range T.Fields() {
|
||||
out = append(out, field.Type())
|
||||
}
|
||||
return out, true
|
||||
case *types.Array:
|
||||
return []types.Type{T.Elem()}, true
|
||||
default:
|
||||
return []types.Type{T}, false
|
||||
}
|
||||
}
|
||||
isInfo := func(T types.Type, info types.BasicInfo) bool {
|
||||
basic, ok := T.Underlying().(*types.Basic)
|
||||
return ok && basic.Info()&info != 0
|
||||
}
|
||||
|
||||
isFormatter := func(T types.Type, ms *types.MethodSet) bool {
|
||||
sel := ms.Lookup(nil, "Format")
|
||||
if sel == nil {
|
||||
return false
|
||||
}
|
||||
fn, ok := sel.Obj().(*types.Func)
|
||||
if !ok {
|
||||
// should be unreachable
|
||||
return false
|
||||
}
|
||||
sig := fn.Type().(*types.Signature)
|
||||
if sig.Params().Len() != 2 {
|
||||
return false
|
||||
}
|
||||
// TODO(dh): check the types of the arguments for more
|
||||
// precision
|
||||
if sig.Results().Len() != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var seen typeutil.Map[struct{}]
|
||||
var checkType func(verb rune, T types.Type, top bool) bool
|
||||
checkType = func(verb rune, T types.Type, top bool) bool {
|
||||
if top {
|
||||
seen = typeutil.Map[struct{}]{}
|
||||
}
|
||||
if _, ok := seen.At(T); ok {
|
||||
return true
|
||||
}
|
||||
seen.Set(T, struct{}{})
|
||||
if int(verb) >= len(verbs) {
|
||||
// Unknown verb
|
||||
return true
|
||||
}
|
||||
|
||||
flags := verbs[verb]
|
||||
if flags == 0 {
|
||||
// Unknown verb
|
||||
return true
|
||||
}
|
||||
|
||||
ms := msCache.MethodSet(T)
|
||||
if isFormatter(T, ms) {
|
||||
// the value is responsible for formatting itself
|
||||
return true
|
||||
}
|
||||
|
||||
if flags&isString != 0 && (types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"])) {
|
||||
// Check for stringer early because we're about to dereference
|
||||
return true
|
||||
}
|
||||
|
||||
T = T.Underlying()
|
||||
if flags&(isPointer|isPseudoPointer) == 0 && top {
|
||||
T = typeutil.Dereference(T)
|
||||
}
|
||||
if flags&isPseudoPointer != 0 && top {
|
||||
t := typeutil.Dereference(T)
|
||||
if _, ok := t.Underlying().(*types.Struct); ok {
|
||||
T = t
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := T.(*types.Interface); ok {
|
||||
// We don't know what's in the interface
|
||||
return true
|
||||
}
|
||||
|
||||
var info types.BasicInfo
|
||||
if flags&isInt != 0 {
|
||||
info |= types.IsInteger
|
||||
}
|
||||
if flags&isBool != 0 {
|
||||
info |= types.IsBoolean
|
||||
}
|
||||
if flags&isFP != 0 {
|
||||
info |= types.IsFloat | types.IsComplex
|
||||
}
|
||||
if flags&isString != 0 {
|
||||
info |= types.IsString
|
||||
}
|
||||
|
||||
if info != 0 && isInfo(T, info) {
|
||||
return true
|
||||
}
|
||||
|
||||
if flags&isString != 0 {
|
||||
isStringyElem := func(typ types.Type) bool {
|
||||
if typ, ok := typ.Underlying().(*types.Basic); ok {
|
||||
return typ.Kind() == types.Byte
|
||||
}
|
||||
return false
|
||||
}
|
||||
switch T := T.(type) {
|
||||
case *types.Slice:
|
||||
if isStringyElem(T.Elem()) {
|
||||
return true
|
||||
}
|
||||
case *types.Array:
|
||||
if isStringyElem(T.Elem()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if flags&isPointer != 0 && typeutil.IsPointerLike(T) {
|
||||
return true
|
||||
}
|
||||
if flags&isPseudoPointer != 0 {
|
||||
switch U := T.Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
if !top {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := U.Elem().Underlying().(*types.Struct); !ok {
|
||||
// TODO(dh): can this condition ever be false? For
|
||||
// *T, if T is a struct, we'll already have
|
||||
// dereferenced it, meaning the *types.Pointer
|
||||
// branch couldn't have been taken. For T that
|
||||
// aren't structs, this condition will always
|
||||
// evaluate to true.
|
||||
return true
|
||||
}
|
||||
case *types.Chan, *types.Signature:
|
||||
// Channels and functions are always treated as
|
||||
// pointers and never recursed into.
|
||||
return true
|
||||
case *types.Basic:
|
||||
if U.Kind() == types.UnsafePointer {
|
||||
return true
|
||||
}
|
||||
case *types.Interface:
|
||||
// we will already have bailed if the type is an
|
||||
// interface.
|
||||
panic("unreachable")
|
||||
default:
|
||||
// other pointer-like types, such as maps or slices,
|
||||
// will be printed element-wise.
|
||||
}
|
||||
}
|
||||
|
||||
if flags&isSlice != 0 {
|
||||
if _, ok := T.(*types.Slice); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if flags&isAny != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
elems, ok := elem(T.Underlying(), verb)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, elem := range elems {
|
||||
if !checkType(verb, elem, false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
k, ok := irutil.Flatten(f).(*ir.Const)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
actions, err := printf.Parse(constant.StringVal(k.Value))
|
||||
if err != nil {
|
||||
carg.Invalid("couldn't parse format string")
|
||||
return
|
||||
}
|
||||
|
||||
ptr := 1
|
||||
hasExplicit := false
|
||||
|
||||
checkStar := func(verb printf.Verb, star printf.Argument) bool {
|
||||
if star, ok := star.(printf.Star); ok {
|
||||
idx := 0
|
||||
if star.Index == -1 {
|
||||
idx = ptr
|
||||
ptr++
|
||||
} else {
|
||||
hasExplicit = true
|
||||
idx = star.Index
|
||||
ptr = star.Index + 1
|
||||
}
|
||||
if idx == 0 {
|
||||
carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
|
||||
return false
|
||||
}
|
||||
if idx > len(args) {
|
||||
carg.Invalid(
|
||||
fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
|
||||
verb.Raw, idx, len(args)))
|
||||
return false
|
||||
}
|
||||
if arg, ok := args[idx-1].(*ir.MakeInterface); ok {
|
||||
if !isInfo(arg.X.Type(), types.IsInteger) {
|
||||
carg.Invalid(fmt.Sprintf("Printf format %s reads non-int arg #%d as argument of *", verb.Raw, idx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// We only report one problem per format string. Making a
|
||||
// mistake with an index tends to invalidate all future
|
||||
// implicit indices.
|
||||
for _, action := range actions {
|
||||
verb, ok := action.(printf.Verb)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !checkStar(verb, verb.Width) || !checkStar(verb, verb.Precision) {
|
||||
return
|
||||
}
|
||||
|
||||
off := ptr
|
||||
if verb.Value != -1 {
|
||||
hasExplicit = true
|
||||
off = verb.Value
|
||||
}
|
||||
if off > len(args) {
|
||||
carg.Invalid(
|
||||
fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
|
||||
verb.Raw, off, len(args)))
|
||||
return
|
||||
} else if verb.Value == 0 && verb.Letter != '%' {
|
||||
carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
|
||||
return
|
||||
} else if off != 0 {
|
||||
arg, ok := args[off-1].(*ir.MakeInterface)
|
||||
if ok {
|
||||
if !checkType(verb.Letter, arg.X.Type(), true) {
|
||||
carg.Invalid(fmt.Sprintf("Printf format %s has arg #%d of wrong type %s",
|
||||
verb.Raw, ptr, args[ptr-1].(*ir.MakeInterface).X.Type()))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch verb.Value {
|
||||
case -1:
|
||||
// Consume next argument
|
||||
ptr++
|
||||
case 0:
|
||||
// Don't consume any arguments
|
||||
default:
|
||||
ptr = verb.Value + 1
|
||||
}
|
||||
}
|
||||
|
||||
if !hasExplicit && ptr <= len(args) {
|
||||
carg.Invalid(fmt.Sprintf("Printf call needs %d args but has %d args", ptr-1, len(args)))
|
||||
}
|
||||
}
|
||||
115
vendor/honnef.co/go/tools/staticcheck/sa5010/sa5010.go
vendored
Normal file
115
vendor/honnef.co/go/tools/staticcheck/sa5010/sa5010.go
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package sa5010
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5010",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Impossible type assertion`,
|
||||
|
||||
Text: `Some type assertions can be statically proven to be
|
||||
impossible. This is the case when the method sets of both
|
||||
arguments of the type assertion conflict with each other, for
|
||||
example by containing the same method with different
|
||||
signatures.
|
||||
|
||||
The Go compiler already applies this check when asserting from an
|
||||
interface value to a concrete type. If the concrete type misses
|
||||
methods from the interface, or if function signatures don't match,
|
||||
then the type assertion can never succeed.
|
||||
|
||||
This check applies the same logic when asserting from one interface to
|
||||
another. If both interface types contain the same method but with
|
||||
different signatures, then the type assertion can never succeed,
|
||||
either.`,
|
||||
|
||||
Since: "2020.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
// Technically this should be MergeIfAll, but the Go compiler
|
||||
// already flags some impossible type assertions, so
|
||||
// MergeIfAny is consistent with the compiler.
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
type entry struct {
|
||||
l, r *types.Func
|
||||
}
|
||||
|
||||
msc := &pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg.Prog.MethodSets
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
instrLoop:
|
||||
for _, instr := range b.Instrs {
|
||||
assert, ok := instr.(*ir.TypeAssert)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var wrong []entry
|
||||
left := assert.X.Type()
|
||||
right := assert.AssertedType
|
||||
righti, ok := right.Underlying().(*types.Interface)
|
||||
|
||||
if !ok {
|
||||
// We only care about interface->interface
|
||||
// assertions. The Go compiler already catches
|
||||
// impossible interface->concrete assertions.
|
||||
continue
|
||||
}
|
||||
|
||||
ms := msc.MethodSet(left)
|
||||
for mr := range righti.Methods() {
|
||||
sel := ms.Lookup(mr.Pkg(), mr.Name())
|
||||
if sel == nil {
|
||||
continue
|
||||
}
|
||||
ml := sel.Obj().(*types.Func)
|
||||
if ml.Origin() != ml || mr.Origin() != mr {
|
||||
// Give up when we see generics.
|
||||
//
|
||||
// TODO(dh): support generics once go/types gets an
|
||||
// exported API for type unification.
|
||||
continue instrLoop
|
||||
}
|
||||
if types.AssignableTo(ml.Type(), mr.Type()) {
|
||||
continue
|
||||
}
|
||||
|
||||
wrong = append(wrong, entry{ml, mr})
|
||||
}
|
||||
|
||||
if len(wrong) != 0 {
|
||||
var s strings.Builder
|
||||
s.WriteString(fmt.Sprintf("impossible type assertion; %s and %s contradict each other:",
|
||||
types.TypeString(left, types.RelativeTo(pass.Pkg)),
|
||||
types.TypeString(right, types.RelativeTo(pass.Pkg))))
|
||||
for _, e := range wrong {
|
||||
s.WriteString(fmt.Sprintf("\n\twrong type for %s method", e.l.Name()))
|
||||
s.WriteString(fmt.Sprintf("\n\t\thave %s", e.l.Type()))
|
||||
s.WriteString(fmt.Sprintf("\n\t\twant %s", e.r.Type()))
|
||||
}
|
||||
report.Report(pass, assert, s.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
221
vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go
vendored
Normal file
221
vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package sa5011
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5011",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Possible nil pointer dereference`,
|
||||
|
||||
Text: `A pointer is being dereferenced unconditionally, while
|
||||
also being checked against nil in another place. This suggests that
|
||||
the pointer may be nil and dereferencing it may panic. This is
|
||||
commonly a result of improperly ordered code or missing return
|
||||
statements. Consider the following examples:
|
||||
|
||||
func fn(x *int) {
|
||||
fmt.Println(*x)
|
||||
|
||||
// This nil check is equally important for the previous dereference
|
||||
if x != nil {
|
||||
foo(*x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFoo(t *testing.T) {
|
||||
x := compute()
|
||||
if x == nil {
|
||||
t.Errorf("nil pointer received")
|
||||
}
|
||||
|
||||
// t.Errorf does not abort the test, so if x is nil, the next line will panic.
|
||||
foo(*x)
|
||||
}
|
||||
|
||||
Staticcheck tries to deduce which functions abort control flow.
|
||||
For example, it is aware that a function will not continue
|
||||
execution after a call to \'panic\' or \'log.Fatal\'. However, sometimes
|
||||
this detection fails, in particular in the presence of
|
||||
conditionals. Consider the following example:
|
||||
|
||||
func Log(msg string, level int) {
|
||||
fmt.Println(msg)
|
||||
if level == levelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(msg string) {
|
||||
Log(msg, levelFatal)
|
||||
}
|
||||
|
||||
func fn(x *int) {
|
||||
if x == nil {
|
||||
Fatal("unexpected nil pointer")
|
||||
}
|
||||
fmt.Println(*x)
|
||||
}
|
||||
|
||||
Staticcheck will flag the dereference of \'x\', even though it is perfectly
|
||||
safe. Staticcheck is not able to deduce that a call to
|
||||
Fatal will exit the program. For the time being, the easiest
|
||||
workaround is to modify the definition of Fatal like so:
|
||||
|
||||
func Fatal(msg string) {
|
||||
Log(msg, levelFatal)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
We also hard-code functions from common logging packages such as
|
||||
logrus. Please file an issue if we're missing support for a
|
||||
popular package.`,
|
||||
Since: "2020.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// This is an extremely trivial check that doesn't try to reason
|
||||
// about control flow. That is, phis and sigmas do not propagate
|
||||
// any information. As such, we can flag this:
|
||||
//
|
||||
// _ = *x
|
||||
// if x == nil { return }
|
||||
//
|
||||
// but we cannot flag this:
|
||||
//
|
||||
// if x == nil { println(x) }
|
||||
// _ = *x
|
||||
//
|
||||
// but we can flag this, because the if's body doesn't use x:
|
||||
//
|
||||
// if x == nil { println("this is bad") }
|
||||
// _ = *x
|
||||
//
|
||||
// nor many other variations of conditional uses of or assignments to x.
|
||||
//
|
||||
// However, even this trivial implementation finds plenty of
|
||||
// real-world bugs, such as dereference before nil pointer check,
|
||||
// or using t.Error instead of t.Fatal when encountering nil
|
||||
// pointers.
|
||||
//
|
||||
// On the flip side, our naive implementation avoids false positives in branches, such as
|
||||
//
|
||||
// if x != nil { _ = *x }
|
||||
//
|
||||
// due to the same lack of propagating information through sigma
|
||||
// nodes. x inside the branch will be independent of the x in the
|
||||
// nil pointer check.
|
||||
//
|
||||
//
|
||||
// We could implement a more powerful check, but then we'd be
|
||||
// getting false positives instead of false negatives because
|
||||
// we're incapable of deducing relationships between variables.
|
||||
// For example, a function might return a pointer and an error,
|
||||
// and the error being nil guarantees that the pointer is not nil.
|
||||
// Depending on the surrounding code, the pointer may still end up
|
||||
// being checked against nil in one place, and guarded by a check
|
||||
// on the error in another, which would lead to us marking some
|
||||
// loads as unsafe.
|
||||
//
|
||||
// Unfortunately, simply hard-coding the relationship between
|
||||
// return values wouldn't eliminate all false positives, either.
|
||||
// Many other more subtle relationships exist. An abridged example
|
||||
// from real code:
|
||||
//
|
||||
// if a == nil && b == nil { return }
|
||||
// c := fn(a)
|
||||
// if c != "" { _ = *a }
|
||||
//
|
||||
// where `fn` is guaranteed to return a non-empty string if a
|
||||
// isn't nil.
|
||||
//
|
||||
// We choose to err on the side of false negatives.
|
||||
|
||||
isNilConst := func(v ir.Value) bool {
|
||||
if typeutil.IsPointerLike(v.Type()) {
|
||||
if k, ok := v.(*ir.Const); ok {
|
||||
return k.IsNil()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
maybeNil := map[ir.Value]ir.Instruction{}
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
// Originally we looked at all ir.BinOp, but that would lead to calls like 'assert(x != nil)' causing false positives.
|
||||
// Restrict ourselves to actual if statements, as these are more likely to affect control flow in a way we can observe.
|
||||
if instr, ok := instr.(*ir.If); ok {
|
||||
if cond, ok := instr.Cond.(*ir.BinOp); ok {
|
||||
if isNilConst(cond.X) {
|
||||
maybeNil[cond.Y] = cond
|
||||
}
|
||||
if isNilConst(cond.Y) {
|
||||
maybeNil[cond.X] = cond
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
var ptr ir.Value
|
||||
switch instr := instr.(type) {
|
||||
case *ir.Load:
|
||||
ptr = instr.X
|
||||
case *ir.Store:
|
||||
ptr = instr.Addr
|
||||
case *ir.IndexAddr:
|
||||
ptr = instr.X
|
||||
if typeutil.All(ptr.Type(), func(term *types.Term) bool {
|
||||
if _, ok := term.Type().Underlying().(*types.Slice); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
// indexing a nil slice does not cause a nil pointer panic
|
||||
//
|
||||
// Note: This also works around the bad lowering of range loops over slices
|
||||
// (https://github.com/dominikh/go-tools/issues/1053)
|
||||
continue
|
||||
}
|
||||
case *ir.FieldAddr:
|
||||
ptr = instr.X
|
||||
}
|
||||
if ptr != nil {
|
||||
switch ptr.(type) {
|
||||
case *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr:
|
||||
// these cannot be nil
|
||||
continue
|
||||
}
|
||||
if r, ok := maybeNil[ptr]; ok {
|
||||
report.Report(pass, instr, "possible nil pointer dereference",
|
||||
report.Related(r, "this check suggests that the pointer can be nil"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
286
vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go
vendored
Normal file
286
vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
package sa5012
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA5012",
|
||||
Run: run,
|
||||
FactTypes: []analysis.Fact{new(evenElements)},
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Passing odd-sized slice to function expecting even size",
|
||||
Text: `Some functions that take slices as parameters expect the slices to have an even number of elements.
|
||||
Often, these functions treat elements in a slice as pairs.
|
||||
For example, \'strings.NewReplacer\' takes pairs of old and new strings,
|
||||
and calling it with an odd number of elements would be an error.`,
|
||||
Since: "2020.2",
|
||||
Severity: lint.SeverityError,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
type evenElements struct{}
|
||||
|
||||
func (evenElements) AFact() {}
|
||||
|
||||
func (evenElements) String() string { return "needs even elements" }
|
||||
|
||||
func findSliceLength(v ir.Value) int {
|
||||
// TODO(dh): VRP would help here
|
||||
|
||||
v = irutil.Flatten(v)
|
||||
val := func(v ir.Value) int {
|
||||
if v, ok := v.(*ir.Const); ok {
|
||||
return int(v.Int64())
|
||||
}
|
||||
return -1
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case *ir.Slice:
|
||||
low := 0
|
||||
high := -1
|
||||
if v.Low != nil {
|
||||
low = val(v.Low)
|
||||
}
|
||||
if v.High != nil {
|
||||
high = val(v.High)
|
||||
} else {
|
||||
switch vv := v.X.(type) {
|
||||
case *ir.Alloc:
|
||||
high = int(typeutil.Dereference(vv.Type()).Underlying().(*types.Array).Len())
|
||||
case *ir.Slice:
|
||||
high = findSliceLength(vv)
|
||||
}
|
||||
}
|
||||
if low == -1 || high == -1 {
|
||||
return -1
|
||||
}
|
||||
return high - low
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func flagSliceLens(pass *analysis.Pass) {
|
||||
var tag evenElements
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
call, ok := instr.(ir.CallInstruction)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
callee := call.Common().StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
for argi, arg := range call.Common().Args {
|
||||
if callee.Signature.Recv() != nil {
|
||||
if argi == 0 {
|
||||
continue
|
||||
}
|
||||
argi--
|
||||
}
|
||||
|
||||
_, ok := arg.Type().Underlying().(*types.Slice)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
param := callee.Signature.Params().At(argi)
|
||||
if !pass.ImportObjectFact(param, &tag) {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO handle stubs
|
||||
|
||||
// we know the argument has to have even length.
|
||||
// now let's try to find its length
|
||||
if n := findSliceLength(arg); n > -1 && n%2 != 0 {
|
||||
src := call.Source().(*ast.CallExpr).Args[argi]
|
||||
sig := call.Common().Signature()
|
||||
var label string
|
||||
if argi == sig.Params().Len()-1 && sig.Variadic() {
|
||||
label = "variadic argument"
|
||||
} else {
|
||||
label = "argument"
|
||||
}
|
||||
// Note that param.Name() is guaranteed to not
|
||||
// be empty, otherwise the function couldn't
|
||||
// have enforced its length.
|
||||
report.Report(pass, src, fmt.Sprintf("%s %q is expected to have even number of elements, but has %d elements", label, param.Name(), n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findSliceLenChecks(pass *analysis.Pass) {
|
||||
// mark all function parameters that have to be of even length
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
// all paths go through this block
|
||||
if !b.Dominates(fn.Exit) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if foo % 2 != 0
|
||||
ifi, ok := b.Control().(*ir.If)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cmp, ok := ifi.Cond.(*ir.BinOp)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var needle uint64
|
||||
switch cmp.Op {
|
||||
case token.NEQ:
|
||||
// look for != 0
|
||||
needle = 0
|
||||
case token.EQL:
|
||||
// look for == 1
|
||||
needle = 1
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
rem, ok1 := cmp.X.(*ir.BinOp)
|
||||
k, ok2 := cmp.Y.(*ir.Const)
|
||||
if ok1 != ok2 {
|
||||
continue
|
||||
}
|
||||
if !ok1 {
|
||||
rem, ok1 = cmp.Y.(*ir.BinOp)
|
||||
k, ok2 = cmp.X.(*ir.Const)
|
||||
}
|
||||
if !ok1 || !ok2 || rem.Op != token.REM || k.Value.Kind() != constant.Int || k.Uint64() != needle {
|
||||
continue
|
||||
}
|
||||
k, ok = rem.Y.(*ir.Const)
|
||||
if !ok || k.Value.Kind() != constant.Int || k.Uint64() != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// if len(foo) % 2 != 0
|
||||
call, ok := rem.X.(*ir.Call)
|
||||
if !ok || !irutil.IsCallTo(call.Common(), "len") {
|
||||
continue
|
||||
}
|
||||
|
||||
// we're checking the length of a parameter that is a slice
|
||||
// TODO(dh): support parameters that have flown through sigmas and phis
|
||||
param, ok := call.Call.Args[0].(*ir.Parameter)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !typeutil.All(param.Type(), typeutil.IsSlice) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if len(foo) % 2 != 0 then panic
|
||||
if _, ok := b.Succs[0].Control().(*ir.Panic); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pass.ExportObjectFact(param.Object(), new(evenElements))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findIndirectSliceLenChecks(pass *analysis.Pass) {
|
||||
seen := map[*ir.Function]struct{}{}
|
||||
|
||||
var doFunction func(fn *ir.Function)
|
||||
doFunction = func(fn *ir.Function) {
|
||||
if _, ok := seen[fn]; ok {
|
||||
return
|
||||
}
|
||||
seen[fn] = struct{}{}
|
||||
|
||||
for _, b := range fn.Blocks {
|
||||
// all paths go through this block
|
||||
if !b.Dominates(fn.Exit) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, instr := range b.Instrs {
|
||||
call, ok := instr.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
callee := call.Call.StaticCallee()
|
||||
if callee == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if callee.Pkg == fn.Pkg || callee.Pkg == nil {
|
||||
doFunction(callee)
|
||||
}
|
||||
|
||||
for argi, arg := range call.Call.Args {
|
||||
if callee.Signature.Recv() != nil {
|
||||
if argi == 0 {
|
||||
continue
|
||||
}
|
||||
argi--
|
||||
}
|
||||
|
||||
// TODO(dh): support parameters that have flown through length-preserving instructions
|
||||
param, ok := arg.(*ir.Parameter)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !typeutil.All(param.Type(), typeutil.IsSlice) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We can't use callee.Params to look up the
|
||||
// parameter, because Params is not populated for
|
||||
// external functions. In our modular analysis.
|
||||
// any function in any package that isn't the
|
||||
// current package is considered "external", as it
|
||||
// has been loaded from export data only.
|
||||
sigParams := callee.Signature.Params()
|
||||
|
||||
if !pass.ImportObjectFact(sigParams.At(argi), new(evenElements)) {
|
||||
continue
|
||||
}
|
||||
pass.ExportObjectFact(param.Object(), new(evenElements))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
doFunction(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
findSliceLenChecks(pass)
|
||||
findIndirectSliceLenChecks(pass)
|
||||
flagSliceLens(pass)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
57
vendor/honnef.co/go/tools/staticcheck/sa6000/sa6000.go
vendored
Normal file
57
vendor/honnef.co/go/tools/staticcheck/sa6000/sa6000.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package sa6000
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6000",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using \'regexp.Match\' or related in a loop, should use \'regexp.Compile\'`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"regexp.Match": check("regexp.Match"),
|
||||
"regexp.MatchReader": check("regexp.MatchReader"),
|
||||
"regexp.MatchString": check("regexp.MatchString"),
|
||||
}
|
||||
|
||||
func check(name string) callcheck.Check {
|
||||
return func(call *callcheck.Call) {
|
||||
if callcheck.ExtractConst(call.Args[0].Value) == nil {
|
||||
return
|
||||
}
|
||||
if !isInLoop(call.Instr.Block()) {
|
||||
return
|
||||
}
|
||||
call.Invalid(fmt.Sprintf("calling %s in a loop has poor performance, consider using regexp.Compile", name))
|
||||
}
|
||||
}
|
||||
|
||||
func isInLoop(b *ir.BasicBlock) bool {
|
||||
sets := irutil.FindLoops(b.Parent())
|
||||
for _, set := range sets {
|
||||
if set.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
124
vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go
vendored
Normal file
124
vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package sa6001
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Missing an optimization opportunity when indexing maps by byte slices`,
|
||||
|
||||
Text: `Map keys must be comparable, which precludes the use of byte slices.
|
||||
This usually leads to using string keys and converting byte slices to
|
||||
strings.
|
||||
|
||||
Normally, a conversion of a byte slice to a string needs to copy the data and
|
||||
causes allocations. The compiler, however, recognizes \'m[string(b)]\' and
|
||||
uses the data of \'b\' directly, without copying it, because it knows that
|
||||
the data can't change during the map lookup. This leads to the
|
||||
counter-intuitive situation that
|
||||
|
||||
k := string(b)
|
||||
println(m[k])
|
||||
println(m[k])
|
||||
|
||||
will be less efficient than
|
||||
|
||||
println(m[string(b)])
|
||||
println(m[string(b)])
|
||||
|
||||
because the first version needs to copy and allocate, while the second
|
||||
one does not.
|
||||
|
||||
For some history on this optimization, check out commit
|
||||
f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
for _, b := range fn.Blocks {
|
||||
insLoop:
|
||||
for _, ins := range b.Instrs {
|
||||
var fromType types.Type
|
||||
var toType types.Type
|
||||
|
||||
// find []byte -> string conversions
|
||||
switch ins := ins.(type) {
|
||||
case *ir.Convert:
|
||||
fromType = ins.X.Type()
|
||||
toType = ins.Type()
|
||||
case *ir.MultiConvert:
|
||||
fromType = ins.X.Type()
|
||||
toType = ins.Type()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if toType != types.Universe.Lookup("string").Type() {
|
||||
continue
|
||||
}
|
||||
tset := typeutil.NewTypeSet(fromType)
|
||||
// If at least one of the types is []byte, then it's more efficient to inline the conversion
|
||||
if !tset.Any(func(term *types.Term) bool {
|
||||
s, ok := term.Type().Underlying().(*types.Slice)
|
||||
return ok && s.Elem().Underlying() == types.Universe.Lookup("byte").Type()
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
refs := ins.Referrers()
|
||||
// need at least two (DebugRef) references: the
|
||||
// conversion and the *ast.Ident
|
||||
if refs == nil || len(*refs) < 2 {
|
||||
continue
|
||||
}
|
||||
ident := false
|
||||
// skip first reference, that's the conversion itself
|
||||
for _, ref := range (*refs)[1:] {
|
||||
switch ref := ref.(type) {
|
||||
case *ir.DebugRef:
|
||||
if _, ok := ref.Expr.(*ast.Ident); !ok {
|
||||
// the string seems to be used somewhere
|
||||
// unexpected; the default branch should
|
||||
// catch this already, but be safe
|
||||
continue insLoop
|
||||
} else {
|
||||
ident = true
|
||||
}
|
||||
case *ir.MapLookup:
|
||||
default:
|
||||
// the string is used somewhere else than a
|
||||
// map lookup
|
||||
continue insLoop
|
||||
}
|
||||
}
|
||||
|
||||
// the result of the conversion wasn't assigned to an
|
||||
// identifier
|
||||
if !ident {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, ins, "m[string(key)] would be more efficient than k := string(key); m[k]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
52
vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go
vendored
Normal file
52
vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package sa6002
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6002",
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Storing non-pointer values in \'sync.Pool\' allocates memory`,
|
||||
Text: `A \'sync.Pool\' is used to avoid unnecessary allocations and reduce the
|
||||
amount of work the garbage collector has to do.
|
||||
|
||||
When passing a value that is not a pointer to a function that accepts
|
||||
an interface, the value needs to be placed on the heap, which means an
|
||||
additional allocation. Slices are a common thing to put in sync.Pools,
|
||||
and they're structs with 3 fields (length, capacity, and a pointer to
|
||||
an array). In order to avoid the extra allocation, one should store a
|
||||
pointer to the slice instead.
|
||||
|
||||
See the comments on https://go-review.googlesource.com/c/go/+/24371
|
||||
that discuss this problem.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
"(*sync.Pool).Put": func(call *callcheck.Call) {
|
||||
arg := call.Args[knowledge.Arg("(*sync.Pool).Put.x")]
|
||||
typ := arg.Value.Value.Type()
|
||||
_, isSlice := typ.Underlying().(*types.Slice)
|
||||
if !typeutil.IsPointerLike(typ) || isSlice {
|
||||
arg.Invalid("argument should be pointer-like to avoid allocations")
|
||||
}
|
||||
},
|
||||
}
|
||||
42
vendor/honnef.co/go/tools/staticcheck/sa6003/sa6003.go
vendored
Normal file
42
vendor/honnef.co/go/tools/staticcheck/sa6003/sa6003.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package sa6003
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/internal/sharedcheck"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6003",
|
||||
Run: sharedcheck.CheckRangeStringRunes,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Converting a string to a slice of runes before ranging over it`,
|
||||
Text: `You may want to loop over the runes in a string. Instead of converting
|
||||
the string to a slice of runes and looping over that, you can loop
|
||||
over the string itself. That is,
|
||||
|
||||
for _, r := range s {}
|
||||
|
||||
and
|
||||
|
||||
for _, r := range []rune(s) {}
|
||||
|
||||
will yield the same values. The first version, however, will be faster
|
||||
and avoid unnecessary memory allocations.
|
||||
|
||||
Do note that if you are interested in the indices, ranging over a
|
||||
string and over a slice of runes will yield different indices. The
|
||||
first one yields byte offsets, while the second one yields indices in
|
||||
the slice of runes.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
75
vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go
vendored
Normal file
75
vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package sa6005
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6005",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Inefficient string comparison with \'strings.ToLower\' or \'strings.ToUpper\'`,
|
||||
Text: `Converting two strings to the same case and comparing them like so
|
||||
|
||||
if strings.ToLower(s1) == strings.ToLower(s2) {
|
||||
...
|
||||
}
|
||||
|
||||
is significantly more expensive than comparing them with
|
||||
\'strings.EqualFold(s1, s2)\'. This is due to memory usage as well as
|
||||
computational complexity.
|
||||
|
||||
\'strings.ToLower\' will have to allocate memory for the new strings, as
|
||||
well as convert both strings fully, even if they differ on the very
|
||||
first byte. strings.EqualFold, on the other hand, compares the strings
|
||||
one character at a time. It doesn't need to create two intermediate
|
||||
strings and can return as soon as the first non-matching character has
|
||||
been found.
|
||||
|
||||
For a more in-depth explanation of this issue, see
|
||||
https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
checkToLowerToUpperComparisonQ = pattern.MustParse(`
|
||||
(BinaryExpr
|
||||
(CallExpr fun@(Symbol (Or "strings.ToLower" "strings.ToUpper")) [a])
|
||||
tok@(Or "==" "!=")
|
||||
(CallExpr fun [b]))`)
|
||||
checkToLowerToUpperComparisonR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b])`)
|
||||
)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, checkToLowerToUpperComparisonQ) {
|
||||
rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr)
|
||||
if m.State["tok"].(token.Token) == token.NEQ {
|
||||
rn = &ast.UnaryExpr{
|
||||
Op: token.NOT,
|
||||
X: rn,
|
||||
}
|
||||
}
|
||||
|
||||
report.Report(pass, node,
|
||||
"should use strings.EqualFold instead",
|
||||
report.Fixes(edit.Fix("Replace with strings.EqualFold", edit.ReplaceWithNode(pass.Fset, node, rn))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
46
vendor/honnef.co/go/tools/staticcheck/sa6006/sa6006.go
vendored
Normal file
46
vendor/honnef.co/go/tools/staticcheck/sa6006/sa6006.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package sa6006
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA6006",
|
||||
Run: run,
|
||||
Requires: code.RequiredAnalyzers,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using io.WriteString to write \'[]byte\'`,
|
||||
Text: `Using io.WriteString to write a slice of bytes, as in
|
||||
|
||||
io.WriteString(w, string(b))
|
||||
|
||||
is both unnecessary and inefficient. Converting from \'[]byte\' to \'string\'
|
||||
has to allocate and copy the data, and we could simply use \'w.Write(b)\'
|
||||
instead.`,
|
||||
|
||||
Since: "2024.1",
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var ioWriteStringConversion = pattern.MustParse(`(CallExpr (Symbol "io.WriteString") [_ (CallExpr (Builtin "string") [arg])])`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, ioWriteStringConversion) {
|
||||
if !code.IsOfStringConvertibleByteSlice(pass, m.State["arg"].(ast.Expr)) {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, node, "use io.Writer.Write instead of converting from []byte to string to use io.WriteString")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
69
vendor/honnef.co/go/tools/staticcheck/sa9001/sa9001.go
vendored
Normal file
69
vendor/honnef.co/go/tools/staticcheck/sa9001/sa9001.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package sa9001
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA9001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Defers in range loops may not run when you expect them to`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
loop := node.(*ast.RangeStmt)
|
||||
typ := pass.TypesInfo.TypeOf(loop.X)
|
||||
_, ok := typeutil.CoreType(typ).(*types.Chan)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
stmts := []*ast.DeferStmt{}
|
||||
exits := false
|
||||
fn2 := func(node ast.Node) bool {
|
||||
switch stmt := node.(type) {
|
||||
case *ast.DeferStmt:
|
||||
stmts = append(stmts, stmt)
|
||||
case *ast.FuncLit:
|
||||
// Don't look into function bodies
|
||||
return false
|
||||
case *ast.ReturnStmt:
|
||||
exits = true
|
||||
case *ast.BranchStmt:
|
||||
exits = node.(*ast.BranchStmt).Tok == token.BREAK
|
||||
}
|
||||
return true
|
||||
}
|
||||
ast.Inspect(loop.Body, fn2)
|
||||
|
||||
if exits {
|
||||
return
|
||||
}
|
||||
for _, stmt := range stmts {
|
||||
report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed")
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
63
vendor/honnef.co/go/tools/staticcheck/sa9002/sa9002.go
vendored
Normal file
63
vendor/honnef.co/go/tools/staticcheck/sa9002/sa9002.go
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package sa9002
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strconv"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA9002",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Using a non-octal \'os.FileMode\' that looks like it was meant to be in octal.`,
|
||||
Since: "2017.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
call := node.(*ast.CallExpr)
|
||||
for _, arg := range call.Args {
|
||||
lit, ok := arg.(*ast.BasicLit)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "os.FileMode") &&
|
||||
!typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "io/fs.FileMode") {
|
||||
continue
|
||||
}
|
||||
if len(lit.Value) == 3 &&
|
||||
lit.Value[0] != '0' &&
|
||||
lit.Value[0] >= '0' && lit.Value[0] <= '7' &&
|
||||
lit.Value[1] >= '0' && lit.Value[1] <= '7' &&
|
||||
lit.Value[2] >= '0' && lit.Value[2] <= '7' {
|
||||
|
||||
v, err := strconv.ParseInt(lit.Value, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, arg, fmt.Sprintf("file mode '%s' evaluates to %#o; did you mean '0%s'?", lit.Value, v, lit.Value),
|
||||
report.Fixes(edit.Fix("Fix octal literal", edit.ReplaceWithString(arg, "0"+lit.Value))))
|
||||
}
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.CallExpr)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
62
vendor/honnef.co/go/tools/staticcheck/sa9003/sa9003.go
vendored
Normal file
62
vendor/honnef.co/go/tools/staticcheck/sa9003/sa9003.go
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package sa9003
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA9003",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Empty body in an if or else branch`,
|
||||
Since: "2017.1",
|
||||
NonDefault: true,
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
if fn.Source() == nil {
|
||||
continue
|
||||
}
|
||||
if irutil.IsExample(fn) {
|
||||
continue
|
||||
}
|
||||
cb := func(node ast.Node) bool {
|
||||
ifstmt, ok := node.(*ast.IfStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if ifstmt.Else != nil {
|
||||
b, ok := ifstmt.Else.(*ast.BlockStmt)
|
||||
if !ok || len(b.List) != 0 {
|
||||
return true
|
||||
}
|
||||
report.Report(pass, ifstmt.Else, "empty branch", report.FilterGenerated(), report.ShortRange())
|
||||
}
|
||||
if len(ifstmt.Body.List) != 0 {
|
||||
return true
|
||||
}
|
||||
report.Report(pass, ifstmt, "empty branch", report.FilterGenerated(), report.ShortRange())
|
||||
return true
|
||||
}
|
||||
if source := fn.Source(); source != nil {
|
||||
ast.Inspect(source, cb)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
185
vendor/honnef.co/go/tools/staticcheck/sa9004/sa9004.go
vendored
Normal file
185
vendor/honnef.co/go/tools/staticcheck/sa9004/sa9004.go
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
package sa9004
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ast/astutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA9004",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Only the first constant has an explicit type`,
|
||||
|
||||
Text: `In a constant declaration such as the following:
|
||||
|
||||
const (
|
||||
First byte = 1
|
||||
Second = 2
|
||||
)
|
||||
|
||||
the constant Second does not have the same type as the constant First.
|
||||
This construct shouldn't be confused with
|
||||
|
||||
const (
|
||||
First byte = iota
|
||||
Second
|
||||
)
|
||||
|
||||
where \'First\' and \'Second\' do indeed have the same type. The type is only
|
||||
passed on when no explicit value is assigned to the constant.
|
||||
|
||||
When declaring enumerations with explicit values it is therefore
|
||||
important not to write
|
||||
|
||||
const (
|
||||
EnumFirst EnumType = 1
|
||||
EnumSecond = 2
|
||||
EnumThird = 3
|
||||
)
|
||||
|
||||
This discrepancy in types can cause various confusing behaviors and
|
||||
bugs.
|
||||
|
||||
|
||||
Wrong type in variable declarations
|
||||
|
||||
The most obvious issue with such incorrect enumerations expresses
|
||||
itself as a compile error:
|
||||
|
||||
package pkg
|
||||
|
||||
const (
|
||||
EnumFirst uint8 = 1
|
||||
EnumSecond = 2
|
||||
)
|
||||
|
||||
func fn(useFirst bool) {
|
||||
x := EnumSecond
|
||||
if useFirst {
|
||||
x = EnumFirst
|
||||
}
|
||||
}
|
||||
|
||||
fails to compile with
|
||||
|
||||
./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
|
||||
|
||||
|
||||
Losing method sets
|
||||
|
||||
A more subtle issue occurs with types that have methods and optional
|
||||
interfaces. Consider the following:
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Enum int
|
||||
|
||||
func (e Enum) String() string {
|
||||
return "an enum"
|
||||
}
|
||||
|
||||
const (
|
||||
EnumFirst Enum = 1
|
||||
EnumSecond = 2
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(EnumFirst)
|
||||
fmt.Println(EnumSecond)
|
||||
}
|
||||
|
||||
This code will output
|
||||
|
||||
an enum
|
||||
2
|
||||
|
||||
as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`,
|
||||
Since: "2019.1",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
decl := node.(*ast.GenDecl)
|
||||
if !decl.Lparen.IsValid() {
|
||||
return
|
||||
}
|
||||
if decl.Tok != token.CONST {
|
||||
return
|
||||
}
|
||||
|
||||
groups := astutil.GroupSpecs(pass.Fset, decl.Specs)
|
||||
groupLoop:
|
||||
for _, group := range groups {
|
||||
if len(group) < 2 {
|
||||
continue
|
||||
}
|
||||
if group[0].(*ast.ValueSpec).Type == nil {
|
||||
// first constant doesn't have a type
|
||||
continue groupLoop
|
||||
}
|
||||
|
||||
firstType := pass.TypesInfo.TypeOf(group[0].(*ast.ValueSpec).Values[0])
|
||||
for i, spec := range group {
|
||||
spec := spec.(*ast.ValueSpec)
|
||||
if i > 0 && spec.Type != nil {
|
||||
continue groupLoop
|
||||
}
|
||||
if len(spec.Names) != 1 || len(spec.Values) != 1 {
|
||||
continue groupLoop
|
||||
}
|
||||
|
||||
if !types.ConvertibleTo(pass.TypesInfo.TypeOf(spec.Values[0]), firstType) {
|
||||
continue groupLoop
|
||||
}
|
||||
|
||||
switch v := spec.Values[0].(type) {
|
||||
case *ast.BasicLit:
|
||||
case *ast.UnaryExpr:
|
||||
if _, ok := v.X.(*ast.BasicLit); !ok {
|
||||
continue groupLoop
|
||||
}
|
||||
default:
|
||||
// if it's not a literal it might be typed, such as
|
||||
// time.Microsecond = 1000 * Nanosecond
|
||||
continue groupLoop
|
||||
}
|
||||
}
|
||||
var edits []analysis.TextEdit
|
||||
typ := group[0].(*ast.ValueSpec).Type
|
||||
for _, spec := range group[1:] {
|
||||
nspec := *spec.(*ast.ValueSpec)
|
||||
nspec.Type = typ
|
||||
// The position of `spec` node excludes comments (if any).
|
||||
// However, on generating the source back from the node, the comments are included. Setting `Comment` to nil ensures deduplication of comments.
|
||||
nspec.Comment = nil
|
||||
edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec))
|
||||
}
|
||||
report.Report(pass, group[0],
|
||||
"only the first constant in this group has an explicit type",
|
||||
report.Fixes(edit.Fix("Add type to all constants in group", edits...)))
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.GenDecl)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
94
vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go
vendored
Normal file
94
vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package sa9005
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/callcheck"
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
"honnef.co/go/tools/knowledge"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "SA9005",
|
||||
Requires: []*analysis.Analyzer{
|
||||
buildir.Analyzer,
|
||||
// Filtering generated code because it may include empty structs generated from data models.
|
||||
generated.Analyzer,
|
||||
},
|
||||
Run: callcheck.Analyzer(rules),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Trying to marshal a struct with no public fields nor custom marshaling`,
|
||||
Text: `
|
||||
The \'encoding/json\' and \'encoding/xml\' packages only operate on exported
|
||||
fields in structs, not unexported ones. It is usually an error to try
|
||||
to (un)marshal structs that only consist of unexported fields.
|
||||
|
||||
This check will not flag calls involving types that define custom
|
||||
marshaling behavior, e.g. via \'MarshalJSON\' methods. It will also not
|
||||
flag empty structs.`,
|
||||
Since: "2019.2",
|
||||
Severity: lint.SeverityWarning,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var rules = map[string]callcheck.Check{
|
||||
// TODO(dh): should we really flag XML? Even an empty struct
|
||||
// produces a non-zero amount of data, namely its type name.
|
||||
// Let's see if we encounter any false positives.
|
||||
//
|
||||
// Also, should we flag gob?
|
||||
"encoding/json.Marshal": check(knowledge.Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"),
|
||||
"encoding/xml.Marshal": check(knowledge.Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"),
|
||||
"(*encoding/json.Encoder).Encode": check(knowledge.Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"),
|
||||
"(*encoding/xml.Encoder).Encode": check(knowledge.Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"),
|
||||
|
||||
"encoding/json.Unmarshal": check(knowledge.Arg("json.Unmarshal.v"), "UnmarshalJSON", "UnmarshalText"),
|
||||
"encoding/xml.Unmarshal": check(knowledge.Arg("xml.Unmarshal.v"), "UnmarshalXML", "UnmarshalText"),
|
||||
"(*encoding/json.Decoder).Decode": check(knowledge.Arg("(*encoding/json.Decoder).Decode.v"), "UnmarshalJSON", "UnmarshalText"),
|
||||
"(*encoding/xml.Decoder).Decode": check(knowledge.Arg("(*encoding/xml.Decoder).Decode.v"), "UnmarshalXML", "UnmarshalText"),
|
||||
}
|
||||
|
||||
func check(argN int, meths ...string) callcheck.Check {
|
||||
return func(call *callcheck.Call) {
|
||||
if code.IsGenerated(call.Pass, call.Instr.Pos()) {
|
||||
return
|
||||
}
|
||||
arg := call.Args[argN]
|
||||
T := arg.Value.Value.Type()
|
||||
Ts, ok := typeutil.Dereference(T).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if Ts.NumFields() == 0 {
|
||||
return
|
||||
}
|
||||
fields := typeutil.FlattenFields(Ts)
|
||||
for _, field := range fields {
|
||||
if field.Var.Exported() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// OPT(dh): we could use a method set cache here
|
||||
ms := call.Instr.Parent().Prog.MethodSets.MethodSet(T)
|
||||
// TODO(dh): we're not checking the signature, which can cause false negatives.
|
||||
// This isn't a huge problem, however, since vet complains about incorrect signatures.
|
||||
for _, meth := range meths {
|
||||
if ms.Lookup(nil, meth) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
arg.Invalid(fmt.Sprintf("struct type '%s' doesn't have any exported fields, nor custom marshaling", typeutil.Dereference(T)))
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user