2019-11-07 19:05:39 +00:00
// Package staticcheck contains a linter for Go source code.
package staticcheck // import "honnef.co/go/tools/staticcheck"
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
htmltemplate "html/template"
"net/http"
"reflect"
"regexp"
"regexp/syntax"
"sort"
"strconv"
"strings"
texttemplate "text/template"
"unicode"
. "honnef.co/go/tools/arg"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/code"
2019-11-07 19:05:39 +00:00
"honnef.co/go/tools/deprecated"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/edit"
2019-11-07 19:05:39 +00:00
"honnef.co/go/tools/facts"
"honnef.co/go/tools/functions"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/internal/passes/buildir"
2019-11-07 19:05:39 +00:00
"honnef.co/go/tools/internal/sharedcheck"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/ir"
"honnef.co/go/tools/ir/irutil"
2019-11-07 19:05:39 +00:00
"honnef.co/go/tools/lint"
. "honnef.co/go/tools/lint/lintdsl"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/pattern"
2019-11-07 19:05:39 +00:00
"honnef.co/go/tools/printf"
2020-02-26 18:45:19 +00:00
"honnef.co/go/tools/report"
2019-11-07 19:05:39 +00:00
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
2020-02-26 18:45:19 +00:00
func checkSortSlice ( call * 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 ) )
}
}
2019-11-07 19:05:39 +00:00
func validRegexp ( call * Call ) {
arg := call . Args [ 0 ]
err := ValidateRegexp ( arg . Value )
if err != nil {
arg . Invalid ( err . Error ( ) )
}
}
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 ] }
func utf8Cutset ( call * Call ) {
arg := call . Args [ 1 ]
if InvalidUTF8 ( arg . Value ) {
arg . Invalid ( MsgInvalidUTF8 )
}
}
func uniqueCutset ( call * Call ) {
arg := call . Args [ 1 ]
if ! UniqueStringCutset ( arg . Value ) {
arg . Invalid ( MsgNonUniqueCutset )
}
}
func unmarshalPointer ( name string , arg int ) CallCheck {
return func ( call * 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 pointlessIntMath ( call * Call ) {
if ConvertedFromInt ( call . Args [ 0 ] . Value ) {
2020-02-26 18:45:19 +00:00
call . Invalid ( fmt . Sprintf ( "calling %s on a converted integer is pointless" , code . CallName ( call . Instr . Common ( ) ) ) )
2019-11-07 19:05:39 +00:00
}
}
func checkValidHostPort ( arg int ) CallCheck {
return func ( call * Call ) {
if ! ValidHostPort ( call . Args [ arg ] . Value ) {
call . Args [ arg ] . Invalid ( MsgInvalidHostPort )
}
}
}
var (
checkRegexpRules = map [ string ] CallCheck {
"regexp.MustCompile" : validRegexp ,
"regexp.Compile" : validRegexp ,
"regexp.Match" : validRegexp ,
"regexp.MatchReader" : validRegexp ,
"regexp.MatchString" : validRegexp ,
}
checkTimeParseRules = map [ string ] CallCheck {
"time.Parse" : func ( call * Call ) {
arg := call . Args [ Arg ( "time.Parse.layout" ) ]
err := ValidateTimeLayout ( arg . Value )
if err != nil {
arg . Invalid ( err . Error ( ) )
}
} ,
}
checkEncodingBinaryRules = map [ string ] CallCheck {
"encoding/binary.Write" : func ( call * Call ) {
arg := call . Args [ Arg ( "encoding/binary.Write.data" ) ]
if ! CanBinaryMarshal ( call . Pass , arg . Value ) {
arg . Invalid ( fmt . Sprintf ( "value of type %s cannot be used with binary.Write" , arg . Value . Value . Type ( ) ) )
}
} ,
}
checkURLsRules = map [ string ] CallCheck {
"net/url.Parse" : func ( call * Call ) {
arg := call . Args [ Arg ( "net/url.Parse.rawurl" ) ]
err := ValidateURL ( arg . Value )
if err != nil {
arg . Invalid ( err . Error ( ) )
}
} ,
}
checkSyncPoolValueRules = map [ string ] CallCheck {
"(*sync.Pool).Put" : func ( call * Call ) {
arg := call . Args [ Arg ( "(*sync.Pool).Put.x" ) ]
typ := arg . Value . Value . Type ( )
2020-02-26 18:45:19 +00:00
if ! code . IsPointerLike ( typ ) {
2019-11-07 19:05:39 +00:00
arg . Invalid ( "argument should be pointer-like to avoid allocations" )
}
} ,
}
checkRegexpFindAllRules = map [ string ] CallCheck {
"(*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 ) ,
}
checkUTF8CutsetRules = map [ string ] CallCheck {
"strings.IndexAny" : utf8Cutset ,
"strings.LastIndexAny" : utf8Cutset ,
"strings.ContainsAny" : utf8Cutset ,
"strings.Trim" : utf8Cutset ,
"strings.TrimLeft" : utf8Cutset ,
"strings.TrimRight" : utf8Cutset ,
}
checkUniqueCutsetRules = map [ string ] CallCheck {
"strings.Trim" : uniqueCutset ,
"strings.TrimLeft" : uniqueCutset ,
"strings.TrimRight" : uniqueCutset ,
}
checkUnmarshalPointerRules = map [ string ] CallCheck {
"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 ) ,
}
checkUnbufferedSignalChanRules = map [ string ] CallCheck {
"os/signal.Notify" : func ( call * Call ) {
arg := call . Args [ Arg ( "os/signal.Notify.c" ) ]
if UnbufferedChannel ( arg . Value ) {
arg . Invalid ( "the channel used with signal.Notify should be buffered" )
}
} ,
}
checkMathIntRules = map [ string ] CallCheck {
"math.Ceil" : pointlessIntMath ,
"math.Floor" : pointlessIntMath ,
"math.IsNaN" : pointlessIntMath ,
"math.Trunc" : pointlessIntMath ,
"math.IsInf" : pointlessIntMath ,
}
checkStringsReplaceZeroRules = map [ string ] CallCheck {
"strings.Replace" : RepeatZeroTimes ( "strings.Replace" , 3 ) ,
"bytes.Replace" : RepeatZeroTimes ( "bytes.Replace" , 3 ) ,
}
checkListenAddressRules = map [ string ] CallCheck {
"net/http.ListenAndServe" : checkValidHostPort ( 0 ) ,
"net/http.ListenAndServeTLS" : checkValidHostPort ( 0 ) ,
}
checkBytesEqualIPRules = map [ string ] CallCheck {
"bytes.Equal" : func ( call * Call ) {
if ConvertedFrom ( call . Args [ Arg ( "bytes.Equal.a" ) ] . Value , "net.IP" ) &&
ConvertedFrom ( call . Args [ Arg ( "bytes.Equal.b" ) ] . Value , "net.IP" ) {
call . Invalid ( "use net.IP.Equal to compare net.IPs, not bytes.Equal" )
}
} ,
}
checkRegexpMatchLoopRules = map [ string ] CallCheck {
"regexp.Match" : loopedRegexp ( "regexp.Match" ) ,
"regexp.MatchReader" : loopedRegexp ( "regexp.MatchReader" ) ,
"regexp.MatchString" : loopedRegexp ( "regexp.MatchString" ) ,
}
checkNoopMarshal = map [ string ] CallCheck {
// 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" : checkNoopMarshalImpl ( Arg ( "json.Marshal.v" ) , "MarshalJSON" , "MarshalText" ) ,
"encoding/xml.Marshal" : checkNoopMarshalImpl ( Arg ( "xml.Marshal.v" ) , "MarshalXML" , "MarshalText" ) ,
"(*encoding/json.Encoder).Encode" : checkNoopMarshalImpl ( Arg ( "(*encoding/json.Encoder).Encode.v" ) , "MarshalJSON" , "MarshalText" ) ,
"(*encoding/xml.Encoder).Encode" : checkNoopMarshalImpl ( Arg ( "(*encoding/xml.Encoder).Encode.v" ) , "MarshalXML" , "MarshalText" ) ,
"encoding/json.Unmarshal" : checkNoopMarshalImpl ( Arg ( "json.Unmarshal.v" ) , "UnmarshalJSON" , "UnmarshalText" ) ,
"encoding/xml.Unmarshal" : checkNoopMarshalImpl ( Arg ( "xml.Unmarshal.v" ) , "UnmarshalXML" , "UnmarshalText" ) ,
"(*encoding/json.Decoder).Decode" : checkNoopMarshalImpl ( Arg ( "(*encoding/json.Decoder).Decode.v" ) , "UnmarshalJSON" , "UnmarshalText" ) ,
"(*encoding/xml.Decoder).Decode" : checkNoopMarshalImpl ( Arg ( "(*encoding/xml.Decoder).Decode.v" ) , "UnmarshalXML" , "UnmarshalText" ) ,
}
checkUnsupportedMarshal = map [ string ] CallCheck {
"encoding/json.Marshal" : checkUnsupportedMarshalImpl ( Arg ( "json.Marshal.v" ) , "json" , "MarshalJSON" , "MarshalText" ) ,
"encoding/xml.Marshal" : checkUnsupportedMarshalImpl ( Arg ( "xml.Marshal.v" ) , "xml" , "MarshalXML" , "MarshalText" ) ,
"(*encoding/json.Encoder).Encode" : checkUnsupportedMarshalImpl ( Arg ( "(*encoding/json.Encoder).Encode.v" ) , "json" , "MarshalJSON" , "MarshalText" ) ,
"(*encoding/xml.Encoder).Encode" : checkUnsupportedMarshalImpl ( Arg ( "(*encoding/xml.Encoder).Encode.v" ) , "xml" , "MarshalXML" , "MarshalText" ) ,
}
checkAtomicAlignment = map [ string ] CallCheck {
"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 ,
}
// TODO(dh): detect printf wrappers
checkPrintfRules = map [ string ] CallCheck {
2020-02-26 18:45:19 +00:00
"fmt.Errorf" : func ( call * Call ) { checkPrintfCall ( call , 0 , 1 ) } ,
"fmt.Printf" : func ( call * Call ) { checkPrintfCall ( call , 0 , 1 ) } ,
"fmt.Sprintf" : func ( call * Call ) { checkPrintfCall ( call , 0 , 1 ) } ,
"fmt.Fprintf" : func ( call * Call ) { checkPrintfCall ( call , 1 , 2 ) } ,
"golang.org/x/xerrors.Errorf" : func ( call * Call ) { checkPrintfCall ( call , 0 , 1 ) } ,
}
checkSortSliceRules = map [ string ] CallCheck {
"sort.Slice" : checkSortSlice ,
"sort.SliceIsSorted" : checkSortSlice ,
"sort.SliceStable" : checkSortSlice ,
}
checkWithValueKeyRules = map [ string ] CallCheck {
"context.WithValue" : checkWithValueKey ,
2019-11-07 19:05:39 +00:00
}
)
func checkPrintfCall ( call * Call , fIdx , vIdx int ) {
f := call . Args [ fIdx ]
2020-02-26 18:45:19 +00:00
var args [ ] ir . Value
2019-11-07 19:05:39 +00:00
switch v := call . Args [ vIdx ] . Value . Value . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Slice :
2019-11-07 19:05:39 +00:00
var ok bool
2020-02-26 18:45:19 +00:00
args , ok = irutil . Vararg ( v )
2019-11-07 19:05:39 +00:00
if ! ok {
// We don't know what the actual arguments to the function are
return
}
2020-02-26 18:45:19 +00:00
case * ir . Const :
2019-11-07 19:05:39 +00:00
// nil, i.e. no arguments
default :
// We don't know what the actual arguments to the function are
return
}
2020-02-26 18:45:19 +00:00
checkPrintfCallImpl ( f , f . Value . Value , args )
2019-11-07 19:05:39 +00:00
}
type verbFlag int
const (
isInt verbFlag = 1 << iota
isBool
isFP
isString
isPointer
2020-02-26 18:45:19 +00:00
// 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.
2019-11-07 19:05:39 +00:00
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 ,
2020-02-26 18:45:19 +00:00
'O' : isPseudoPointer | isInt ,
2019-11-07 19:05:39 +00:00
'p' : isSlice | isPointer | noRecurse ,
'q' : isInt | isString ,
's' : isString ,
't' : isBool ,
'T' : isAny ,
'U' : isInt ,
'v' : isAny ,
2020-02-26 18:45:19 +00:00
'X' : isPseudoPointer | isInt | isFP | isString ,
'x' : isPseudoPointer | isInt | isFP | isString ,
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
func checkPrintfCallImpl ( carg * Argument , f ir . Value , args [ ] ir . Value ) {
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
if verbs [ verb ] & isString != 0 && code . IsType ( T . Elem ( ) . Underlying ( ) , "byte" ) {
2019-11-07 19:05:39 +00:00
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 i := 0 ; i < T . NumFields ( ) ; i ++ {
out = append ( out , T . Field ( i ) . 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
}
isStringer := func ( T types . Type , ms * types . MethodSet ) bool {
sel := ms . Lookup ( nil , "String" )
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 ( ) != 0 {
return false
}
if sig . Results ( ) . Len ( ) != 1 {
return false
}
2020-02-26 18:45:19 +00:00
if ! code . IsType ( sig . Results ( ) . At ( 0 ) . Type ( ) , "string" ) {
2019-11-07 19:05:39 +00:00
return false
}
return true
}
isError := func ( T types . Type , ms * types . MethodSet ) bool {
sel := ms . Lookup ( nil , "Error" )
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 ( ) != 0 {
return false
}
if sig . Results ( ) . Len ( ) != 1 {
return false
}
2020-02-26 18:45:19 +00:00
if ! code . IsType ( sig . Results ( ) . At ( 0 ) . Type ( ) , "string" ) {
2019-11-07 19:05:39 +00:00
return false
}
return true
}
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
}
seen := map [ types . Type ] bool { }
var checkType func ( verb rune , T types . Type , top bool ) bool
checkType = func ( verb rune , T types . Type , top bool ) bool {
if top {
for k := range seen {
delete ( seen , k )
}
}
if seen [ T ] {
return true
}
seen [ T ] = true
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 && ( isStringer ( T , ms ) || isError ( T , ms ) ) {
// Check for stringer early because we're about to dereference
return true
}
T = T . Underlying ( )
if flags & ( isPointer | isPseudoPointer ) == 0 && top {
2020-02-26 18:45:19 +00:00
T = code . Dereference ( T )
2019-11-07 19:05:39 +00:00
}
if flags & isPseudoPointer != 0 && top {
2020-02-26 18:45:19 +00:00
t := code . Dereference ( T )
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
if flags & isString != 0 && ( code . IsType ( T , "[]byte" ) || isStringer ( T , ms ) || isError ( T , ms ) ) {
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
if flags & isPointer != 0 && code . IsPointerLike ( T ) {
2019-11-07 19:05:39 +00:00
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 {
2020-02-26 18:45:19 +00:00
// 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.
2019-11-07 19:05:39 +00:00
return true
}
case * types . Chan , * types . Signature :
2020-02-26 18:45:19 +00:00
// Channels and functions are always treated as
// pointers and never recursed into.
2019-11-07 19:05:39 +00:00
return true
2020-02-26 18:45:19 +00:00
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.
2019-11-07 19:05:39 +00:00
}
}
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
}
2020-02-26 18:45:19 +00:00
k , ok := f . ( * ir . Const )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
actions , err := printf . Parse ( constant . StringVal ( k . Value ) )
if err != nil {
2020-02-26 18:45:19 +00:00
carg . Invalid ( "couldn't parse format string" )
2019-11-07 19:05:39 +00:00
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 {
2020-02-26 18:45:19 +00:00
carg . Invalid ( fmt . Sprintf ( "Printf format %s reads invalid arg 0; indices are 1-based" , verb . Raw ) )
2019-11-07 19:05:39 +00:00
return false
}
if idx > len ( args ) {
2020-02-26 18:45:19 +00:00
carg . Invalid (
2019-11-07 19:05:39 +00:00
fmt . Sprintf ( "Printf format %s reads arg #%d, but call has only %d args" ,
verb . Raw , idx , len ( args ) ) )
return false
}
2020-02-26 18:45:19 +00:00
if arg , ok := args [ idx - 1 ] . ( * ir . MakeInterface ) ; ok {
2019-11-07 19:05:39 +00:00
if ! isInfo ( arg . X . Type ( ) , types . IsInteger ) {
2020-02-26 18:45:19 +00:00
carg . Invalid ( fmt . Sprintf ( "Printf format %s reads non-int arg #%d as argument of *" , verb . Raw , idx ) )
2019-11-07 19:05:39 +00:00
}
}
}
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 ) {
2020-02-26 18:45:19 +00:00
carg . Invalid (
2019-11-07 19:05:39 +00:00
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 != '%' {
2020-02-26 18:45:19 +00:00
carg . Invalid ( fmt . Sprintf ( "Printf format %s reads invalid arg 0; indices are 1-based" , verb . Raw ) )
2019-11-07 19:05:39 +00:00
return
} else if off != 0 {
2020-02-26 18:45:19 +00:00
arg , ok := args [ off - 1 ] . ( * ir . MakeInterface )
2019-11-07 19:05:39 +00:00
if ok {
if ! checkType ( verb . Letter , arg . X . Type ( ) , true ) {
2020-02-26 18:45:19 +00:00
carg . Invalid ( fmt . Sprintf ( "Printf format %s has arg #%d of wrong type %s" ,
verb . Raw , ptr , args [ ptr - 1 ] . ( * ir . MakeInterface ) . X . Type ( ) ) )
2019-11-07 19:05:39 +00:00
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 ) {
2020-02-26 18:45:19 +00:00
carg . Invalid ( fmt . Sprintf ( "Printf call needs %d args but has %d args" , ptr - 1 , len ( args ) ) )
2019-11-07 19:05:39 +00:00
}
}
func checkAtomicAlignmentImpl ( call * Call ) {
sizes := call . Pass . TypesSizes
if sizes . Sizeof ( types . Typ [ types . Uintptr ] ) != 4 {
// Not running on a 32-bit platform
return
}
2020-02-26 18:45:19 +00:00
v , ok := call . Args [ 0 ] . Value . Value . ( * ir . FieldAddr )
2019-11-07 19:05:39 +00:00
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 ( ) ,
2020-02-26 18:45:19 +00:00
code . CallName ( call . Instr . Common ( ) ) )
2019-11-07 19:05:39 +00:00
call . Invalid ( msg )
}
}
func checkNoopMarshalImpl ( argN int , meths ... string ) CallCheck {
return func ( call * Call ) {
2020-02-26 18:45:19 +00:00
if code . IsGenerated ( call . Pass , call . Instr . Pos ( ) ) {
2019-11-07 19:05:39 +00:00
return
}
arg := call . Args [ argN ]
T := arg . Value . Value . Type ( )
2020-02-26 18:45:19 +00:00
Ts , ok := code . Dereference ( T ) . Underlying ( ) . ( * types . Struct )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
if Ts . NumFields ( ) == 0 {
return
}
2020-02-26 18:45:19 +00:00
fields := code . FlattenFields ( Ts )
2019-11-07 19:05:39 +00:00
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 ( "struct doesn't have any exported fields, nor custom marshaling" )
}
}
func checkUnsupportedMarshalImpl ( argN int , tag string , meths ... string ) CallCheck {
// TODO(dh): flag slices and maps of unsupported types
return func ( call * Call ) {
msCache := & call . Instr . Parent ( ) . Prog . MethodSets
arg := call . Args [ argN ]
T := arg . Value . Value . Type ( )
2020-02-26 18:45:19 +00:00
Ts , ok := code . Dereference ( T ) . Underlying ( ) . ( * types . Struct )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
ms := msCache . 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
}
}
2020-02-26 18:45:19 +00:00
fields := code . FlattenFields ( Ts )
2019-11-07 19:05:39 +00:00
for _ , field := range fields {
if ! ( field . Var . Exported ( ) ) {
continue
}
if reflect . StructTag ( field . Tag ) . Get ( tag ) == "-" {
continue
}
ms := msCache . MethodSet ( field . Var . Type ( ) )
// 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
}
}
switch field . Var . Type ( ) . Underlying ( ) . ( type ) {
case * types . Chan , * types . Signature :
arg . Invalid ( fmt . Sprintf ( "trying to marshal chan or func value, field %s" , fieldPath ( T , field . Path ) ) )
}
}
}
}
func fieldPath ( start types . Type , indices [ ] int ) string {
p := start . String ( )
for _ , idx := range indices {
2020-02-26 18:45:19 +00:00
field := code . Dereference ( start ) . Underlying ( ) . ( * types . Struct ) . Field ( idx )
2019-11-07 19:05:39 +00:00
start = field . Type ( )
p += "." + field . Name ( )
}
return p
}
2020-02-26 18:45:19 +00:00
func isInLoop ( b * ir . BasicBlock ) bool {
2019-11-07 19:05:39 +00:00
sets := functions . FindLoops ( b . Parent ( ) )
for _ , set := range sets {
if set . Has ( b ) {
return true
}
}
return false
}
func CheckUntrappableSignal ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAnyAST ( pass , call ,
2019-11-07 19:05:39 +00:00
"os/signal.Ignore" , "os/signal.Notify" , "os/signal.Reset" ) {
return
}
2020-02-26 18:45:19 +00:00
hasSigterm := false
2019-11-07 19:05:39 +00:00
for _ , arg := range call . Args {
if conv , ok := arg . ( * ast . CallExpr ) ; ok && isName ( pass , conv . Fun , "os.Signal" ) {
arg = conv . Args [ 0 ]
}
2020-02-26 18:45:19 +00:00
if isName ( pass , arg , "syscall.SIGTERM" ) {
hasSigterm = true
break
}
}
for i , arg := range call . Args {
if conv , ok := arg . ( * ast . CallExpr ) ; ok && isName ( pass , conv . Fun , "os.Signal" ) {
arg = conv . Args [ 0 ]
}
2019-11-07 19:05:39 +00:00
if isName ( pass , arg , "os.Kill" ) || isName ( pass , arg , "syscall.SIGKILL" ) {
2020-02-26 18:45:19 +00:00
var fixes [ ] analysis . SuggestedFix
if ! hasSigterm {
nargs := make ( [ ] ast . Expr , len ( call . Args ) )
for j , a := range call . Args {
if i == j {
nargs [ j ] = 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 ... ) )
2019-11-07 19:05:39 +00:00
}
if isName ( pass , arg , "syscall.SIGSTOP" ) {
2020-02-26 18:45:19 +00:00
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 ) ) ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckTemplate ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
var kind string
2020-02-26 18:45:19 +00:00
switch code . CallNameAST ( pass , call ) {
case "(*text/template.Template).Parse" :
2019-11-07 19:05:39 +00:00
kind = "text"
2020-02-26 18:45:19 +00:00
case "(*html/template.Template).Parse" :
2019-11-07 19:05:39 +00:00
kind = "html"
2020-02-26 18:45:19 +00:00
default :
2019-11-07 19:05:39 +00:00
return
}
sel := call . Fun . ( * ast . SelectorExpr )
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAnyAST ( pass , sel . X , "text/template.New" , "html/template.New" ) {
2019-11-07 19:05:39 +00:00
// 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
return
}
2020-02-26 18:45:19 +00:00
s , ok := code . ExprToString ( pass , call . Args [ Arg ( "(*text/template.Template).Parse.text" ) ] )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
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" ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , call . Args [ Arg ( "(*text/template.Template).Parse.text" ) ] , err . Error ( ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
var (
checkTimeSleepConstantPatternRns = pattern . MustParse ( ` (BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond"))) ` )
checkTimeSleepConstantPatternRs = pattern . MustParse ( ` (BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second"))) ` )
)
2019-11-07 19:05:39 +00:00
func CheckTimeSleepConstant ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAST ( pass , call , "time.Sleep" ) {
2019-11-07 19:05:39 +00:00
return
}
lit , ok := call . Args [ Arg ( "time.Sleep.d" ) ] . ( * ast . BasicLit )
if ! ok {
return
}
n , err := strconv . Atoi ( lit . Value )
if err != nil {
return
}
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.
return
}
2020-02-26 18:45:19 +00:00
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 , checkTimeSleepConstantPatternRns , pattern . State { "duration" : lit } , lit ) ) ,
edit . Fix ( "use seconds" , edit . ReplaceWithPattern ( pass , checkTimeSleepConstantPatternRs , pattern . State { "duration" : lit } , lit ) ) ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
var checkWaitgroupAddQ = pattern . MustParse ( `
( GoStmt
( CallExpr
( FuncLit
_
call @ ( CallExpr ( Function "(*sync.WaitGroup).Add" ) _ ) : _ ) _ ) ) ` )
2019-11-07 19:05:39 +00:00
func CheckWaitgroupAdd ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
if m , ok := Match ( pass , checkWaitgroupAddQ , node ) ; ok {
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 ) ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . GoStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckInfiniteEmptyLoop ( pass * analysis . Pass ) ( interface { } , 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 {
2020-02-26 18:45:19 +00:00
if code . MayHaveSideEffects ( pass , loop . Cond , nil ) {
2019-11-07 19:05:39 +00:00
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
}
}
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , loop , "loop condition never changes or has a race condition" )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , loop , "this loop will spin, using 100%% CPU" , report . ShortRange ( ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . ForStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckDeferInInfiniteLoop ( pass * analysis . Pass ) ( interface { } , 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 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , stmt , "defers in this infinite loop will never run" )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . ForStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckDubiousDeferInChannelRangeLoop ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
loop := node . ( * ast . RangeStmt )
typ := pass . TypesInfo . TypeOf ( loop . X )
_ , ok := typ . Underlying ( ) . ( * types . Chan )
if ! ok {
return
}
fn2 := func ( node ast . Node ) bool {
switch stmt := node . ( type ) {
case * ast . DeferStmt :
2020-02-26 18:45:19 +00:00
report . Report ( pass , stmt , "defers in this range loop won't run unless the channel gets closed" )
2019-11-07 19:05:39 +00:00
case * ast . FuncLit :
// Don't look into function bodies
return false
}
return true
}
ast . Inspect ( loop . Body , fn2 )
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . RangeStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckTestMainExit ( pass * analysis . Pass ) ( interface { } , 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 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , fnmain , "TestMain should call os.Exit to set exit code" )
2019-11-07 19:05:39 +00:00
}
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
}
fnmain = node
arg = pass . TypesInfo . ObjectOf ( node . Type . Params . List [ 0 ] . Names [ 0 ] )
return true
case * ast . CallExpr :
2020-02-26 18:45:19 +00:00
if code . IsCallToAST ( pass , node , "os.Exit" ) {
2019-11-07 19:05:39 +00:00
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 :
2020-02-26 18:45:19 +00:00
ExhaustiveTypeSwitch ( node )
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
return code . IsOfType ( pass , arg . Type , "*testing.M" )
2019-11-07 19:05:39 +00:00
}
func CheckExec ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAST ( pass , call , "os/exec.Command" ) {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
val , ok := code . ExprToString ( pass , call . Args [ Arg ( "os/exec.Command.name" ) ] )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
if ! strings . Contains ( val , " " ) || strings . Contains ( val , ` \ ` ) || strings . Contains ( val , "/" ) {
return
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , call . Args [ Arg ( "os/exec.Command.name" ) ] ,
2019-11-07 19:05:39 +00:00
"first argument to exec.Command looks like a shell command, but a program name or path are expected" )
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckLoopEmptyDefault ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
loop := node . ( * ast . ForStmt )
if len ( loop . Body . List ) != 1 || loop . Cond != nil || loop . Init != nil {
return
}
sel , ok := loop . Body . List [ 0 ] . ( * ast . SelectStmt )
if ! ok {
return
}
for _ , c := range sel . Body . List {
2020-02-26 18:45:19 +00:00
// FIXME this leaves behind an empty line, and possibly
// comments in the default branch. We can't easily fix
// either.
2019-11-07 19:05:39 +00:00
if comm , ok := c . ( * ast . CommClause ) ; ok && comm . Comm == nil && len ( comm . Body ) == 0 {
2020-02-26 18:45:19 +00:00
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
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . ForStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckLhsRhsIdentical ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
var isFloat func ( T types . Type ) bool
isFloat = func ( T types . Type ) bool {
switch T := T . Underlying ( ) . ( type ) {
case * types . Basic :
kind := T . Kind ( )
return kind == types . Float32 || kind == types . Float64
case * types . Array :
return isFloat ( T . Elem ( ) )
case * types . Struct :
for i := 0 ; i < T . NumFields ( ) ; i ++ {
if ! isFloat ( T . Field ( i ) . Type ( ) ) {
return false
}
}
return true
default :
return false
}
}
// TODO(dh): this check ignores the existence of side-effects and
// happily flags fn() == fn() – so far, we've had nobody complain
// about a false positive, and it's caught several bugs in real
// code.
2019-11-07 19:05:39 +00:00
fn := func ( node ast . Node ) {
op := node . ( * ast . BinaryExpr )
switch op . Op {
case token . EQL , token . NEQ :
2020-02-26 18:45:19 +00:00
if isFloat ( pass . TypesInfo . TypeOf ( op . X ) ) {
// f == f and f != f might be used to check for NaN
return
2019-11-07 19:05:39 +00:00
}
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
return
}
2020-02-26 18:45:19 +00:00
if reflect . TypeOf ( op . X ) != reflect . TypeOf ( op . Y ) {
return
}
if report . Render ( pass , op . X ) != report . Render ( pass , op . Y ) {
2019-11-07 19:05:39 +00:00
return
}
l1 , ok1 := op . X . ( * ast . BasicLit )
l2 , ok2 := op . Y . ( * ast . BasicLit )
2020-02-26 18:45:19 +00:00
if ok1 && ok2 && l1 . Kind == token . INT && l2 . Kind == l1 . Kind && l1 . Value == "0" && l2 . Value == l1 . Value && code . IsGenerated ( pass , l1 . Pos ( ) ) {
2019-11-07 19:05:39 +00:00
// 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.
return
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , op , fmt . Sprintf ( "identical expressions on the left and right side of the '%s' operator" , op . Op ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . BinaryExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckScopedBreak ( pass * analysis . Pass ) ( interface { } , 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 :
2020-02-26 18:45:19 +00:00
ExhaustiveTypeSwitch ( node )
2019-11-07 19:05:39 +00:00
}
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
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , branch , "ineffective break statement. Did you mean to break out of the outer loop?" )
2019-11-07 19:05:39 +00:00
}
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . ForStmt ) ( nil ) , ( * ast . RangeStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckUnsafePrintf ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-02-26 18:45:19 +00:00
name := code . CallNameAST ( pass , call )
2019-11-07 19:05:39 +00:00
var arg int
2020-02-26 18:45:19 +00:00
switch name {
case "fmt.Printf" , "fmt.Sprintf" , "log.Printf" :
2019-11-07 19:05:39 +00:00
arg = Arg ( "fmt.Printf.format" )
2020-02-26 18:45:19 +00:00
case "fmt.Fprintf" :
2019-11-07 19:05:39 +00:00
arg = Arg ( "fmt.Fprintf.format" )
2020-02-26 18:45:19 +00:00
default :
2019-11-07 19:05:39 +00:00
return
}
if len ( call . Args ) != arg + 1 {
return
}
switch call . Args [ arg ] . ( type ) {
case * ast . CallExpr , * ast . Ident :
default :
return
}
2020-02-26 18:45:19 +00:00
alt := name [ : len ( name ) - 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 ( pass . Fset , call . Fun , alt ) ) ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckEarlyDefer ( pass * analysis . Pass ) ( interface { } , 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 ident . Obj != lhs . Obj {
continue
}
if sel . Sel . Name != "Close" {
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , def , fmt . Sprintf ( "should check returned error before deferring %s" , report . Render ( pass , def . Call ) ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . BlockStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func selectorX ( sel * ast . SelectorExpr ) ast . Node {
switch x := sel . X . ( type ) {
case * ast . SelectorExpr :
return selectorX ( x )
default :
return x
}
}
func CheckEmptyCriticalSection ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
if pass . Pkg . Path ( ) == "sync_test" {
// exception for the sync package's tests
return nil , nil
}
2019-11-07 19:05:39 +00:00
// Initially it might seem like this check would be easier to
2020-02-26 18:45:19 +00:00
// implement using IR. After all, we're only checking for two
2019-11-07 19:05:39 +00:00
// 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
2020-02-26 18:45:19 +00:00
// is protected by the mutex). In an IR-based approach, however,
2019-11-07 19:05:39 +00:00
// 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 := 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 ] )
2020-02-26 18:45:19 +00:00
if ! ok1 || ! ok2 || report . Render ( pass , sel1 ) != report . Render ( pass , sel2 ) {
2019-11-07 19:05:39 +00:00
continue
}
if ( method1 == "Lock" && method2 == "Unlock" ) ||
( method1 == "RLock" && method2 == "RUnlock" ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , block . List [ i + 1 ] , "empty critical section" )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . BlockStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
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 "&" _)) ` )
)
2019-11-07 19:05:39 +00:00
func CheckIneffectiveCopy ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
if m , ok := 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." )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
} else if _ , ok := Match ( pass , checkIneffectiveCopyQ2 , node ) ; ok {
report . Report ( pass , node , "*&x will be simplified to x. It will not copy x." )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . UnaryExpr ) ( nil ) , ( * ast . StarExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckCanonicalHeaderKey ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node , push bool ) bool {
if ! push {
return false
}
assign , ok := node . ( * ast . AssignStmt )
if 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
}
2020-02-26 18:45:19 +00:00
if code . IsOfType ( pass , op . X , "net/http.Header" ) {
2019-11-07 19:05:39 +00:00
return false
}
}
return true
}
op , ok := node . ( * ast . IndexExpr )
if ! ok {
return true
}
2020-02-26 18:45:19 +00:00
if ! code . IsOfType ( pass , op . X , "net/http.Header" ) {
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
s , ok := code . ExprToString ( pass , op . Index )
2019-11-07 19:05:39 +00:00
if ! ok {
return true
}
2020-02-26 18:45:19 +00:00
canonical := http . CanonicalHeaderKey ( s )
if s == canonical {
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
var fix analysis . SuggestedFix
switch op . Index . ( type ) {
case * ast . BasicLit :
fix = edit . Fix ( "canonicalize header key" , edit . ReplaceWithString ( pass . Fset , op . Index , strconv . Quote ( canonical ) ) )
case * ast . Ident :
call := & ast . CallExpr {
Fun : 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 )
}
2019-11-07 19:05:39 +00:00
return true
}
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Nodes ( [ ] ast . Node { ( * ast . AssignStmt ) ( nil ) , ( * ast . IndexExpr ) ( nil ) } , fn )
return nil , nil
}
func CheckBenchmarkN ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
assign := node . ( * ast . AssignStmt )
if len ( assign . Lhs ) != 1 || len ( assign . Rhs ) != 1 {
return
}
sel , ok := assign . Lhs [ 0 ] . ( * ast . SelectorExpr )
if ! ok {
return
}
if sel . Sel . Name != "N" {
return
}
2020-02-26 18:45:19 +00:00
if ! code . IsOfType ( pass , sel . X , "*testing.B" ) {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , assign , fmt . Sprintf ( "should not assign to %s" , report . Render ( pass , sel ) ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . AssignStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckUnreadVariableValues ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
if code . IsExample ( fn ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
node := fn . Source ( )
2019-11-07 19:05:39 +00:00
if node == nil {
continue
}
2020-02-26 18:45:19 +00:00
if gen , ok := code . Generator ( pass , node . Pos ( ) ) ; ok && gen == facts . Goyacc {
2019-11-07 19:05:39 +00:00
// 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
}
2020-02-26 18:45:19 +00:00
switchTags := map [ ir . Value ] struct { } { }
2019-11-07 19:05:39 +00:00
ast . Inspect ( node , func ( node ast . Node ) bool {
s , ok := node . ( * ast . SwitchStmt )
if ! ok {
return true
}
2020-02-26 18:45:19 +00:00
v , _ := fn . ValueForExpr ( s . Tag )
2019-11-07 19:05:39 +00:00
switchTags [ v ] = struct { } { }
return true
} )
2020-02-26 18:45:19 +00:00
// 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
}
2019-11-07 19:05:39 +00:00
if _ , ok := switchTags [ v ] ; ok {
return true
}
refs := v . Referrers ( )
if refs == nil {
// TODO investigate why refs can be nil
return true
}
2020-02-26 18:45:19 +00:00
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
2019-11-07 19:05:39 +00:00
}
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
2020-02-26 18:45:19 +00:00
val , _ := fn . ValueForExpr ( assign . Rhs [ 0 ] )
2019-11-07 19:05:39 +00:00
if val == nil {
return true
}
refs := val . Referrers ( )
if refs == nil {
return true
}
for _ , ref := range * refs {
2020-02-26 18:45:19 +00:00
ex , ok := ref . ( * ir . Extract )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if ! hasUse ( ex , nil ) {
2019-11-07 19:05:39 +00:00
lhs := assign . Lhs [ ex . Index ]
if ident , ok := lhs . ( * ast . Ident ) ; ! ok || ok && ident . Name == "_" {
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , assign , fmt . Sprintf ( "this value of %s is never used" , lhs ) )
2019-11-07 19:05:39 +00:00
}
}
return true
}
for i , lhs := range assign . Lhs {
rhs := assign . Rhs [ i ]
if ident , ok := lhs . ( * ast . Ident ) ; ! ok || ok && ident . Name == "_" {
continue
}
2020-02-26 18:45:19 +00:00
val , _ := fn . ValueForExpr ( rhs )
2019-11-07 19:05:39 +00:00
if val == nil {
continue
}
2020-02-26 18:45:19 +00:00
if ! hasUse ( val , nil ) {
report . Report ( pass , assign , fmt . Sprintf ( "this value of %s is never used" , lhs ) )
2019-11-07 19:05:39 +00:00
}
}
return true
} )
}
return nil , nil
}
func CheckPredeterminedBooleanExprs ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
binop , ok := ins . ( * ir . BinOp )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
switch binop . Op {
2019-11-07 19:05:39 +00:00
case token . GTR , token . LSS , token . EQL , token . NEQ , token . LEQ , token . GEQ :
default :
continue
}
2020-02-26 18:45:19 +00:00
xs , ok1 := consts ( binop . X , nil , nil )
ys , ok2 := consts ( binop . Y , nil , nil )
2019-11-07 19:05:39 +00:00
if ! ok1 || ! ok2 || len ( xs ) == 0 || len ( ys ) == 0 {
continue
}
trues := 0
for _ , x := range xs {
for _ , y := range ys {
if x . Value == nil {
if y . Value == nil {
trues ++
}
continue
}
2020-02-26 18:45:19 +00:00
if constant . Compare ( x . Value , binop . Op , y . Value ) {
2019-11-07 19:05:39 +00:00
trues ++
}
}
}
b := trues != 0
if trues == 0 || trues == len ( xs ) * len ( ys ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , binop , fmt . Sprintf ( "binary expression is always %t for all possible values (%s %s %s)" , b , xs , binop . Op , ys ) )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func CheckNilMaps ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
mu , ok := ins . ( * ir . MapUpdate )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
c , ok := mu . Map . ( * ir . Const )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
if c . Value != nil {
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , mu , "assignment to nil map" )
2019-11-07 19:05:39 +00:00
}
}
}
return nil , nil
}
func CheckExtremeComparison ( pass * analysis . Pass ) ( interface { } , error ) {
isobj := func ( expr ast . Expr , name string ) bool {
sel , ok := expr . ( * ast . SelectorExpr )
if ! ok {
return false
}
2020-02-26 18:45:19 +00:00
return code . IsObject ( pass . TypesInfo . ObjectOf ( sel . Sel ) , name )
2019-11-07 19:05:39 +00:00
}
fn := func ( node ast . Node ) {
expr := node . ( * ast . BinaryExpr )
tx := pass . TypesInfo . TypeOf ( expr . X )
basic , ok := tx . Underlying ( ) . ( * types . Basic )
if ! ok {
return
}
var max string
var min string
switch basic . Kind ( ) {
case types . Uint8 :
max = "math.MaxUint8"
case types . Uint16 :
max = "math.MaxUint16"
case types . Uint32 :
max = "math.MaxUint32"
case types . Uint64 :
max = "math.MaxUint64"
case types . Uint :
max = "math.MaxUint64"
case types . Int8 :
min = "math.MinInt8"
max = "math.MaxInt8"
case types . Int16 :
min = "math.MinInt16"
max = "math.MaxInt16"
case types . Int32 :
min = "math.MinInt32"
max = "math.MaxInt32"
case types . Int64 :
min = "math.MinInt64"
max = "math.MaxInt64"
case types . Int :
min = "math.MinInt64"
max = "math.MaxInt64"
}
if ( expr . Op == token . GTR || expr . Op == token . GEQ ) && isobj ( expr . Y , max ) ||
( expr . Op == token . LSS || expr . Op == token . LEQ ) && isobj ( expr . X , max ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , expr , fmt . Sprintf ( "no value of type %s is greater than %s" , basic , max ) )
2019-11-07 19:05:39 +00:00
}
if expr . Op == token . LEQ && isobj ( expr . Y , max ) ||
expr . Op == token . GEQ && isobj ( expr . X , max ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , expr , fmt . Sprintf ( "every value of type %s is <= %s" , basic , max ) )
2019-11-07 19:05:39 +00:00
}
if ( basic . Info ( ) & types . IsUnsigned ) != 0 {
2020-02-26 18:45:19 +00:00
if ( expr . Op == token . LSS && code . IsIntLiteral ( expr . Y , "0" ) ) ||
( expr . Op == token . GTR && code . IsIntLiteral ( expr . X , "0" ) ) {
report . Report ( pass , expr , fmt . Sprintf ( "no value of type %s is less than 0" , basic ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
if expr . Op == token . GEQ && code . IsIntLiteral ( expr . Y , "0" ) ||
expr . Op == token . LEQ && code . IsIntLiteral ( expr . X , "0" ) {
report . Report ( pass , expr , fmt . Sprintf ( "every value of type %s is >= 0" , basic ) )
2019-11-07 19:05:39 +00:00
}
} else {
if ( expr . Op == token . LSS || expr . Op == token . LEQ ) && isobj ( expr . Y , min ) ||
( expr . Op == token . GTR || expr . Op == token . GEQ ) && isobj ( expr . X , min ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , expr , fmt . Sprintf ( "no value of type %s is less than %s" , basic , min ) )
2019-11-07 19:05:39 +00:00
}
if expr . Op == token . GEQ && isobj ( expr . Y , min ) ||
expr . Op == token . LEQ && isobj ( expr . X , min ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , expr , fmt . Sprintf ( "every value of type %s is >= %s" , basic , min ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . BinaryExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
func consts ( val ir . Value , out [ ] * ir . Const , visitedPhis map [ string ] bool ) ( [ ] * ir . Const , bool ) {
2019-11-07 19:05:39 +00:00
if visitedPhis == nil {
visitedPhis = map [ string ] bool { }
}
var ok bool
switch val := val . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Phi :
2019-11-07 19:05:39 +00:00
if visitedPhis [ val . Name ( ) ] {
break
}
visitedPhis [ val . Name ( ) ] = true
vals := val . Operands ( nil )
for _ , phival := range vals {
out , ok = consts ( * phival , out , visitedPhis )
if ! ok {
return nil , false
}
}
2020-02-26 18:45:19 +00:00
case * ir . Const :
2019-11-07 19:05:39 +00:00
out = append ( out , val )
2020-02-26 18:45:19 +00:00
case * ir . Convert :
2019-11-07 19:05:39 +00:00
out , ok = consts ( val . X , out , visitedPhis )
if ! ok {
return nil , false
}
default :
return nil , false
}
if len ( out ) < 2 {
return out , true
}
2020-02-26 18:45:19 +00:00
uniq := [ ] * ir . Const { out [ 0 ] }
2019-11-07 19:05:39 +00:00
for _ , val := range out [ 1 : ] {
if val . Value == uniq [ len ( uniq ) - 1 ] . Value {
continue
}
uniq = append ( uniq , val )
}
return uniq , true
}
func CheckLoopCondition ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
cb := func ( node ast . Node ) bool {
2019-11-07 19:05:39 +00:00
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 x . Obj != lhs . Obj {
return true
}
if _ , ok := loop . Post . ( * ast . IncDecStmt ) ; ! ok {
return true
}
2020-02-26 18:45:19 +00:00
v , isAddr := fn . ValueForExpr ( cond . X )
2019-11-07 19:05:39 +00:00
if v == nil || isAddr {
return true
}
switch v := v . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Phi :
2019-11-07 19:05:39 +00:00
ops := v . Operands ( nil )
if len ( ops ) != 2 {
return true
}
2020-02-26 18:45:19 +00:00
_ , ok := ( * ops [ 0 ] ) . ( * ir . Const )
2019-11-07 19:05:39 +00:00
if ! ok {
return true
}
2020-02-26 18:45:19 +00:00
sigma , ok := ( * ops [ 1 ] ) . ( * ir . Sigma )
2019-11-07 19:05:39 +00:00
if ! ok {
return true
}
if sigma . X != v {
return true
}
2020-02-26 18:45:19 +00:00
case * ir . Load :
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , cond , "variable in loop condition never changes" )
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
Inspect ( fn . Source ( ) , cb )
2019-11-07 19:05:39 +00:00
}
return nil , nil
}
func CheckArgOverwritten ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
cb := func ( node ast . Node ) bool {
2019-11-07 19:05:39 +00:00
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 )
2020-02-26 18:45:19 +00:00
var irobj * ir . Parameter
for _ , param := range fn . Params {
2019-11-07 19:05:39 +00:00
if param . Object ( ) == obj {
2020-02-26 18:45:19 +00:00
irobj = param
2019-11-07 19:05:39 +00:00
break
}
}
2020-02-26 18:45:19 +00:00
if irobj == nil {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
refs := irobj . Referrers ( )
2019-11-07 19:05:39 +00:00
if refs == nil {
continue
}
2020-02-26 18:45:19 +00:00
if len ( code . FilterDebug ( * refs ) ) != 0 {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
var assignment ast . Node
2019-11-07 19:05:39 +00:00
ast . Inspect ( body , func ( node ast . Node ) bool {
2020-02-26 18:45:19 +00:00
if assignment != nil {
return false
}
2019-11-07 19:05:39 +00:00
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 {
2020-02-26 18:45:19 +00:00
assignment = assign
2019-11-07 19:05:39 +00:00
return false
}
}
return true
} )
2020-02-26 18:45:19 +00:00
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 ) ) )
2019-11-07 19:05:39 +00:00
}
}
}
return true
}
2020-02-26 18:45:19 +00:00
Inspect ( fn . Source ( ) , cb )
2019-11-07 19:05:39 +00:00
}
return nil , nil
}
func CheckIneffectiveLoop ( pass * analysis . Pass ) ( interface { } , 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 :
2020-02-26 18:45:19 +00:00
ExhaustiveTypeSwitch ( node )
2019-11-07 19:05:39 +00:00
}
if body == nil {
return
}
labels := map [ * ast . Object ] ast . Stmt { }
ast . Inspect ( body , func ( node ast . Node ) bool {
label , ok := node . ( * ast . LabeledStmt )
if ! ok {
return true
}
labels [ label . Label . Obj ] = 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 :
typ := pass . TypesInfo . TypeOf ( node . X )
if _ , ok := typ . Underlying ( ) . ( * types . Map ) ; ok {
// looping once over a map is a valid pattern for
// getting an arbitrary element.
return true
}
body = node . Body
loop = node
default :
return true
}
if len ( body . List ) < 2 {
// 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 [ stmt . Label . Obj ] == loop {
unconditionalExit = stmt
}
case token . CONTINUE :
if stmt . Label == nil || labels [ stmt . Label . Obj ] == 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 [ branch . Label . Obj ] != loop {
return true
}
unconditionalExit = nil
return false
}
}
return true
} )
if unconditionalExit != nil {
2020-02-26 18:45:19 +00:00
report . Report ( pass , unconditionalExit , "the surrounding loop is unconditionally terminated" )
2019-11-07 19:05:39 +00:00
}
return true
} )
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . FuncDecl ) ( nil ) , ( * ast . FuncLit ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
var checkNilContextQ = pattern . MustParse ( ` (CallExpr fun@(Function _) (Builtin "nil"):_) ` )
2019-11-07 19:05:39 +00:00
func CheckNilContext ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
todo := & ast . CallExpr {
Fun : Selector ( "context" , "TODO" ) ,
}
bg := & ast . CallExpr {
Fun : Selector ( "context" , "Background" ) ,
}
2019-11-07 19:05:39 +00:00
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
m , ok := Match ( pass , checkNilContextQ , node )
if ! ok {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
call := node . ( * ast . CallExpr )
fun , ok := m . State [ "fun" ] . ( * types . Func )
2019-11-07 19:05:39 +00:00
if ! ok {
2020-02-26 18:45:19 +00:00
// it might also be a builtin
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
sig := fun . Type ( ) . ( * types . Signature )
2019-11-07 19:05:39 +00:00
if sig . Params ( ) . Len ( ) == 0 {
2020-02-26 18:45:19 +00:00
// 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.
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
if ! code . IsType ( sig . Params ( ) . At ( 0 ) . Type ( ) , "context.Context" ) {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
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 ) ) ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
var (
checkSeekerQ = pattern . MustParse ( ` (CallExpr fun@(SelectorExpr _ (Ident "Seek")) [arg1@(SelectorExpr (Ident "io") (Ident (Or "SeekStart" "SeekCurrent" "SeekEnd"))) arg2]) ` )
checkSeekerR = pattern . MustParse ( ` (CallExpr fun [arg2 arg1]) ` )
)
2019-11-07 19:05:39 +00:00
func CheckSeeker ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
if _ , edits , ok := MatchAndEdit ( pass , checkSeekerQ , checkSeekerR , node ) ; ok {
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 ... ) ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckIneffectiveAppend ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
isAppend := func ( ins ir . Value ) bool {
call , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
return false
}
if call . Call . IsInvoke ( ) {
return false
}
2020-02-26 18:45:19 +00:00
if builtin , ok := call . Call . Value . ( * ir . Builtin ) ; ! ok || builtin . Name ( ) != "append" {
2019-11-07 19:05:39 +00:00
return false
}
return true
}
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
val , ok := ins . ( ir . Value )
2019-11-07 19:05:39 +00:00
if ! ok || ! isAppend ( val ) {
continue
}
isUsed := false
2020-02-26 18:45:19 +00:00
visited := map [ ir . Instruction ] bool { }
var walkRefs func ( refs [ ] ir . Instruction )
walkRefs = func ( refs [ ] ir . Instruction ) {
2019-11-07 19:05:39 +00:00
loop :
for _ , ref := range refs {
if visited [ ref ] {
continue
}
visited [ ref ] = true
2020-02-26 18:45:19 +00:00
if _ , ok := ref . ( * ir . DebugRef ) ; ok {
2019-11-07 19:05:39 +00:00
continue
}
switch ref := ref . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Phi :
2019-11-07 19:05:39 +00:00
walkRefs ( * ref . Referrers ( ) )
2020-02-26 18:45:19 +00:00
case * ir . Sigma :
2019-11-07 19:05:39 +00:00
walkRefs ( * ref . Referrers ( ) )
2020-02-26 18:45:19 +00:00
case ir . Value :
2019-11-07 19:05:39 +00:00
if ! isAppend ( ref ) {
isUsed = true
} else {
walkRefs ( * ref . Referrers ( ) )
}
2020-02-26 18:45:19 +00:00
case ir . Instruction :
2019-11-07 19:05:39 +00:00
isUsed = true
break loop
}
}
}
2020-02-26 18:45:19 +00:00
2019-11-07 19:05:39 +00:00
refs := val . Referrers ( )
if refs == nil {
continue
}
walkRefs ( * refs )
2020-02-26 18:45:19 +00:00
2019-11-07 19:05:39 +00:00
if ! isUsed {
2020-02-26 18:45:19 +00:00
report . Report ( pass , ins , "this result of append is never used, except maybe in other appends" )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func CheckConcurrentTesting ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
gostmt , ok := ins . ( * ir . Go )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
var fn * ir . Function
2019-11-07 19:05:39 +00:00
switch val := gostmt . Call . Value . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Function :
2019-11-07 19:05:39 +00:00
fn = val
2020-02-26 18:45:19 +00:00
case * ir . MakeClosure :
fn = val . Fn . ( * ir . Function )
2019-11-07 19:05:39 +00:00
default :
continue
}
if fn . Blocks == nil {
continue
}
for _ , block := range fn . Blocks {
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
call , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
if call . Call . IsInvoke ( ) {
continue
}
callee := call . Call . StaticCallee ( )
if callee == nil {
continue
}
recv := callee . Signature . Recv ( )
if recv == nil {
continue
}
2020-02-26 18:45:19 +00:00
if ! code . IsType ( recv . Type ( ) , "*testing.common" ) {
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
// 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 ) ) )
2019-11-07 19:05:39 +00:00
}
}
}
}
}
return nil , nil
}
2020-02-26 18:45:19 +00:00
func eachCall ( fn * ir . Function , cb func ( caller * ir . Function , site ir . CallInstruction , callee * ir . Function ) ) {
for _ , b := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , instr := range b . Instrs {
2020-02-26 18:45:19 +00:00
if site , ok := instr . ( ir . CallInstruction ) ; ok {
2019-11-07 19:05:39 +00:00
if g := site . Common ( ) . StaticCallee ( ) ; g != nil {
2020-02-26 18:45:19 +00:00
cb ( fn , site , g )
2019-11-07 19:05:39 +00:00
}
}
}
}
}
func CheckCyclicFinalizer ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
cb := func ( caller * ir . Function , site ir . CallInstruction , callee * ir . Function ) {
2019-11-07 19:05:39 +00:00
if callee . RelString ( nil ) != "runtime.SetFinalizer" {
return
}
arg0 := site . Common ( ) . Args [ Arg ( "runtime.SetFinalizer.obj" ) ]
2020-02-26 18:45:19 +00:00
if iface , ok := arg0 . ( * ir . MakeInterface ) ; ok {
2019-11-07 19:05:39 +00:00
arg0 = iface . X
}
2020-02-26 18:45:19 +00:00
load , ok := arg0 . ( * ir . Load )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
2020-02-26 18:45:19 +00:00
v , ok := load . X . ( * ir . Alloc )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
arg1 := site . Common ( ) . Args [ Arg ( "runtime.SetFinalizer.finalizer" ) ]
2020-02-26 18:45:19 +00:00
if iface , ok := arg1 . ( * ir . MakeInterface ) ; ok {
2019-11-07 19:05:39 +00:00
arg1 = iface . X
}
2020-02-26 18:45:19 +00:00
mc , ok := arg1 . ( * ir . MakeClosure )
2019-11-07 19:05:39 +00:00
if ! ok {
return
}
for _ , b := range mc . Bindings {
if b == v {
pos := lint . DisplayPosition ( pass . Fset , mc . Fn . Pos ( ) )
2020-02-26 18:45:19 +00:00
report . Report ( pass , site , fmt . Sprintf ( "the finalizer closes over the object, preventing the finalizer from ever running (at %s)" , pos ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
eachCall ( fn , cb )
2019-11-07 19:05:39 +00:00
}
return nil , nil
}
/ *
func CheckSliceOutOfBounds ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
ia , ok := ins . ( * ir . IndexAddr )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
if _ , ok := ia . X . Type ( ) . Underlying ( ) . ( * types . Slice ) ; ! ok {
continue
}
2020-02-26 18:45:19 +00:00
sr , ok1 := c . funcDescs . Get ( fn ) . Ranges [ ia . X ] . ( vrp . SliceInterval )
idxr , ok2 := c . funcDescs . Get ( fn ) . Ranges [ ia . Index ] . ( vrp . IntInterval )
2019-11-07 19:05:39 +00:00
if ! ok1 || ! ok2 || ! sr . IsKnown ( ) || ! idxr . IsKnown ( ) || sr . Length . Empty ( ) || idxr . Empty ( ) {
continue
}
if idxr . Lower . Cmp ( sr . Length . Upper ) >= 0 {
2020-02-26 18:45:19 +00:00
report . Nodef ( pass , ia , "index out of bounds" )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
* /
func CheckDeferLock ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
instrs := code . FilterDebug ( block . Instrs )
2019-11-07 19:05:39 +00:00
if len ( instrs ) < 2 {
continue
}
for i , ins := range instrs [ : len ( instrs ) - 1 ] {
2020-02-26 18:45:19 +00:00
call , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAny ( call . Common ( ) , "(*sync.Mutex).Lock" , "(*sync.RWMutex).RLock" ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
nins , ok := instrs [ i + 1 ] . ( * ir . Defer )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAny ( & nins . Call , "(*sync.Mutex).Lock" , "(*sync.RWMutex).RLock" ) {
2019-11-07 19:05:39 +00:00
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"
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , nins , fmt . Sprintf ( "deferring %s right after having locked already; did you mean to defer %s?" , name , alt ) )
2019-11-07 19:05:39 +00:00
}
}
}
return nil , nil
}
func CheckNaNComparison ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
isNaN := func ( v ir . Value ) bool {
call , ok := v . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
return false
}
2020-02-26 18:45:19 +00:00
return code . IsCallTo ( call . Common ( ) , "math.NaN" )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
ins , ok := ins . ( * ir . BinOp )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
if isNaN ( ins . X ) || isNaN ( ins . Y ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , ins , "no value is equal to NaN, not even NaN itself" )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func CheckInfiniteRecursion ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
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 {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
if _ , ok := site . ( * ir . Go ) ; ok {
2019-11-07 19:05:39 +00:00
// Recursively spawning goroutines doesn't consume
// stack space infinitely, so don't flag it.
return
}
block := site . Block ( )
canReturn := false
2020-02-26 18:45:19 +00:00
for _ , b := range fn . Blocks {
2019-11-07 19:05:39 +00:00
if block . Dominates ( b ) {
continue
}
if len ( b . Instrs ) == 0 {
continue
}
2020-02-26 18:45:19 +00:00
if _ , ok := b . Control ( ) . ( * ir . Return ) ; ok {
2019-11-07 19:05:39 +00:00
canReturn = true
break
}
}
if canReturn {
return
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , site , "infinite recursive call" )
2019-11-07 19:05:39 +00:00
} )
}
return nil , nil
}
func objectName ( obj types . Object ) string {
if obj == nil {
return "<nil>"
}
var name string
if obj . Pkg ( ) != nil && obj . Pkg ( ) . Scope ( ) . Lookup ( obj . Name ( ) ) == obj {
s := obj . Pkg ( ) . Path ( )
if s != "" {
name += s + "."
}
}
name += obj . Name ( )
return name
}
func isName ( pass * analysis . Pass , expr ast . Expr , name string ) bool {
var obj types . Object
switch expr := expr . ( type ) {
case * ast . Ident :
obj = pass . TypesInfo . ObjectOf ( expr )
case * ast . SelectorExpr :
obj = pass . TypesInfo . ObjectOf ( expr . Sel )
}
return objectName ( obj ) == name
}
func CheckLeakyTimeTick ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
if code . IsMainLike ( pass ) || code . IsInTest ( pass , fn ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
call , ok := ins . ( * ir . Call )
if ! ok || ! code . IsCallTo ( call . Common ( ) , "time.Tick" ) {
2019-11-07 19:05:39 +00:00
continue
}
if ! functions . Terminates ( call . Parent ( ) ) {
continue
}
2020-02-26 18:45:19 +00:00
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" )
2019-11-07 19:05:39 +00:00
}
}
}
return nil , nil
}
2020-02-26 18:45:19 +00:00
var checkDoubleNegationQ = pattern . MustParse ( ` (UnaryExpr "!" single@(UnaryExpr "!" x)) ` )
2019-11-07 19:05:39 +00:00
func CheckDoubleNegation ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
if m , ok := Match ( pass , checkDoubleNegationQ , node ) ; ok {
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 ) ) ) ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . UnaryExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckRepeatedIfElse ( pass * analysis . Pass ) ( interface { } , error ) {
seen := map [ ast . Node ] bool { }
2020-02-26 18:45:19 +00:00
var collectConds func ( ifstmt * ast . IfStmt , conds [ ] ast . Expr ) ( [ ] ast . Expr , bool )
collectConds = func ( ifstmt * ast . IfStmt , conds [ ] ast . Expr ) ( [ ] ast . Expr , bool ) {
2019-11-07 19:05:39 +00:00
seen [ ifstmt ] = true
2020-02-26 18:45:19 +00:00
// Bail if any if-statement has an Init statement or side effects in its condition
2019-11-07 19:05:39 +00:00
if ifstmt . Init != nil {
2020-02-26 18:45:19 +00:00
return nil , false
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
if code . MayHaveSideEffects ( pass , ifstmt . Cond , nil ) {
return nil , false
}
2019-11-07 19:05:39 +00:00
conds = append ( conds , ifstmt . Cond )
if elsestmt , ok := ifstmt . Else . ( * ast . IfStmt ) ; ok {
2020-02-26 18:45:19 +00:00
return collectConds ( elsestmt , conds )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
return conds , true
2019-11-07 19:05:39 +00:00
}
fn := func ( node ast . Node ) {
ifstmt := node . ( * ast . IfStmt )
if seen [ ifstmt ] {
2020-02-26 18:45:19 +00:00
// this if-statement is part of an if/else-if chain that we've already processed
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
if ifstmt . Else == nil {
// there can be at most one condition
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
conds , ok := collectConds ( ifstmt , nil )
if ! ok {
return
}
if len ( conds ) < 2 {
return
2019-11-07 19:05:39 +00:00
}
counts := map [ string ] int { }
for _ , cond := range conds {
2020-02-26 18:45:19 +00:00
s := report . Render ( pass , cond )
2019-11-07 19:05:39 +00:00
counts [ s ] ++
if counts [ s ] == 2 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , cond , "this condition occurs multiple times in this if/else if chain" )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . IfStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckSillyBitwiseOps ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
// FIXME(dh): what happened here?
if false {
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
}
2019-11-07 19:05:39 +00:00
2020-02-26 18:45:19 +00:00
if c , ok := ins . Y . ( * ir . Const ) ; ! ok || c . Value == nil || c . Value . Kind ( ) != constant . Int || c . Uint64 ( ) != 0 {
continue
}
switch ins . 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, ...
continue
}
path , _ := astutil . PathEnclosingInterval ( code . File ( pass , ins ) , ins . Pos ( ) , ins . Pos ( ) )
if len ( path ) == 0 {
continue
}
if node , ok := path [ 0 ] . ( * ast . BinaryExpr ) ; ! ok || ! code . IsIntLiteral ( node . Y , "0" ) {
continue
}
2019-11-07 19:05:39 +00:00
2020-02-26 18:45:19 +00:00
switch ins . Op {
case token . AND :
report . Report ( pass , ins , "x & 0 always equals 0" )
case token . OR , token . XOR :
report . Report ( pass , ins , fmt . Sprintf ( "x %s 0 always equals x" , ins . Op ) )
}
2019-11-07 19:05:39 +00:00
}
}
}
}
2020-02-26 18:45:19 +00:00
fn := func ( node ast . Node ) {
binop := node . ( * ast . BinaryExpr )
b , ok := pass . TypesInfo . TypeOf ( binop ) . Underlying ( ) . ( * types . Basic )
if ! ok {
return
}
if ( 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
}
switch y := binop . Y . ( type ) {
case * ast . Ident :
obj , ok := pass . TypesInfo . ObjectOf ( y ) . ( * types . Const )
if ! ok {
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 ) ) )
}
case * ast . BasicLit :
if ! code . IsIntLiteral ( binop . Y , "0" ) {
return
}
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 ) ) )
}
default :
return
}
}
code . Preorder ( pass , fn , ( * ast . BinaryExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
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
}
2019-11-07 19:05:39 +00:00
func CheckNonOctalFileMode ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
sig , ok := pass . TypesInfo . TypeOf ( call . Fun ) . ( * types . Signature )
if ! ok {
return
}
n := sig . Params ( ) . Len ( )
for i := 0 ; i < n ; i ++ {
typ := sig . Params ( ) . At ( i ) . Type ( )
2020-02-26 18:45:19 +00:00
if ! code . IsType ( typ , "os.FileMode" ) {
continue
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
2019-11-07 19:05:39 +00:00
lit , ok := call . Args [ i ] . ( * ast . BasicLit )
if ! ok {
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
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , call . Args [ i ] , 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 ( pass . Fset , call . Args [ i ] , "0" + lit . Value ) ) ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckPureFunctions ( pass * analysis . Pass ) ( interface { } , error ) {
pure := pass . ResultOf [ facts . Purity ] . ( facts . PurityResult )
fnLoop :
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
if code . IsInTest ( pass , fn ) {
params := fn . Signature . Params ( )
2019-11-07 19:05:39 +00:00
for i := 0 ; i < params . Len ( ) ; i ++ {
param := params . At ( i )
2020-02-26 18:45:19 +00:00
if code . IsType ( param . Type ( ) , "*testing.B" ) {
2019-11-07 19:05:39 +00:00
// 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
}
}
}
2020-02-26 18:45:19 +00:00
for _ , b := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range b . Instrs {
2020-02-26 18:45:19 +00:00
ins , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
refs := ins . Referrers ( )
2020-02-26 18:45:19 +00:00
if refs == nil || len ( code . FilterDebug ( * refs ) ) > 0 {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
2019-11-07 19:05:39 +00:00
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 {
2020-02-26 18:45:19 +00:00
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 is a pure function but its return value is ignored" , callee . Name ( ) ) )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func CheckDeprecated ( pass * analysis . Pass ) ( interface { } , error ) {
deprs := pass . ResultOf [ facts . Deprecated ] . ( facts . DeprecatedResult )
// Selectors can appear outside of function literals, e.g. when
// declaring package level variables.
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 )
}
sel , ok := node . ( * ast . SelectorExpr )
if ! ok {
return true
}
obj := pass . TypesInfo . ObjectOf ( sel . Sel )
if obj . Pkg ( ) == nil {
return true
}
if pass . Pkg == obj . Pkg ( ) || obj . Pkg ( ) . Path ( ) + "_test" == pass . Pkg . Path ( ) {
// Don't flag stuff in our own package
return true
}
if depr , ok := deprs . Objects [ obj ] ; ok {
// Look for the first available alternative, not the first
// version something was deprecated in. If a function was
// deprecated in Go 1.6, an alternative has been available
// already in 1.0, and we're targeting 1.2, it still
// makes sense to use the alternative from 1.0, to be
// future-proof.
2020-02-26 18:45:19 +00:00
minVersion := deprecated . Stdlib [ code . SelectorName ( pass , sel ) ] . AlternativeAvailableSince
if ! code . IsGoVersion ( pass , minVersion ) {
2019-11-07 19:05:39 +00:00
return true
}
if tfn != nil {
if _ , ok := deprs . Objects [ tfn ] ; ok {
// functions that are deprecated may use deprecated
// symbols
return true
}
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , sel , fmt . Sprintf ( "%s is deprecated: %s" , report . Render ( pass , sel ) , depr . Msg ) )
2019-11-07 19:05:39 +00:00
return true
}
return true
}
fn2 := func ( node ast . Node ) {
spec := node . ( * ast . ImportSpec )
2020-02-26 18:45:19 +00:00
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 ( )
}
2019-11-07 19:05:39 +00:00
p := spec . Path . Value
path := p [ 1 : len ( p ) - 1 ]
if depr , ok := deprs . Packages [ imp ] ; ok {
2020-05-15 12:02:03 +00:00
if path == "github.com/golang/protobuf/proto" {
gen , ok := code . Generator ( pass , spec . Path . Pos ( ) )
if ok && gen == facts . ProtocGenGo {
return
}
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , spec , fmt . Sprintf ( "package %s is deprecated: %s" , path , depr . Msg ) )
2019-11-07 19:05:39 +00:00
}
}
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Nodes ( nil , fn )
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn2 , ( * ast . ImportSpec ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func callChecker ( rules map [ string ] CallCheck ) func ( pass * analysis . Pass ) ( interface { } , error ) {
return func ( pass * analysis . Pass ) ( interface { } , error ) {
return checkCalls ( pass , rules )
}
}
func checkCalls ( pass * analysis . Pass , rules map [ string ] CallCheck ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
cb := func ( caller * ir . Function , site ir . CallInstruction , callee * ir . Function ) {
2019-11-07 19:05:39 +00:00
obj , ok := callee . Object ( ) . ( * types . Func )
if ! ok {
return
}
r , ok := rules [ lint . FuncName ( obj ) ]
if ! ok {
return
}
var args [ ] * Argument
2020-02-26 18:45:19 +00:00
irargs := site . Common ( ) . Args
2019-11-07 19:05:39 +00:00
if callee . Signature . Recv ( ) != nil {
2020-02-26 18:45:19 +00:00
irargs = irargs [ 1 : ]
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
for _ , arg := range irargs {
if iarg , ok := arg . ( * ir . MakeInterface ) ; ok {
2019-11-07 19:05:39 +00:00
arg = iarg . X
}
2020-02-26 18:45:19 +00:00
args = append ( args , & Argument { Value : Value { arg } } )
2019-11-07 19:05:39 +00:00
}
call := & Call {
Pass : pass ,
Instr : site ,
Args : args ,
Parent : site . Parent ( ) ,
}
r ( call )
2020-02-26 18:45:19 +00:00
path , _ := astutil . PathEnclosingInterval ( code . File ( pass , site ) , site . Pos ( ) , site . Pos ( ) )
var astcall * ast . CallExpr
for _ , el := range path {
if expr , ok := el . ( * ast . CallExpr ) ; ok {
astcall = expr
break
}
}
2019-11-07 19:05:39 +00:00
for idx , arg := range call . Args {
for _ , e := range arg . invalids {
2020-02-26 18:45:19 +00:00
if astcall != nil {
report . Report ( pass , astcall . Args [ idx ] , e )
} else {
report . Report ( pass , site , e )
}
2019-11-07 19:05:39 +00:00
}
}
for _ , e := range call . invalids {
2020-02-26 18:45:19 +00:00
report . Report ( pass , call . Instr , e )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
eachCall ( fn , cb )
2019-11-07 19:05:39 +00:00
}
return nil , nil
}
2020-02-26 18:45:19 +00:00
func shortCallName ( call * ir . CallCommon ) string {
2019-11-07 19:05:39 +00:00
if call . IsInvoke ( ) {
return ""
}
switch v := call . Value . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Function :
2019-11-07 19:05:39 +00:00
fn , ok := v . Object ( ) . ( * types . Func )
if ! ok {
return ""
}
return fn . Name ( )
2020-02-26 18:45:19 +00:00
case * ir . Builtin :
2019-11-07 19:05:39 +00:00
return v . Name ( )
}
return ""
}
func CheckWriterBufferModified ( pass * analysis . Pass ) ( interface { } , 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
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
sig := fn . Signature
if fn . Name ( ) != "Write" || sig . Recv ( ) == nil || sig . Params ( ) . Len ( ) != 1 || sig . Results ( ) . Len ( ) != 2 {
2019-11-07 19:05:39 +00:00
continue
}
tArg , ok := sig . Params ( ) . At ( 0 ) . Type ( ) . ( * types . Slice )
if ! ok {
continue
}
if basic , ok := tArg . Elem ( ) . ( * types . Basic ) ; ! ok || basic . Kind ( ) != types . Byte {
continue
}
if basic , ok := sig . Results ( ) . At ( 0 ) . Type ( ) . ( * types . Basic ) ; ! ok || basic . Kind ( ) != types . Int {
continue
}
2020-02-26 18:45:19 +00:00
if named , ok := sig . Results ( ) . At ( 1 ) . Type ( ) . ( * types . Named ) ; ! ok || ! code . IsType ( named , "error" ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
for _ , block := range fn . Blocks {
2019-11-07 19:05:39 +00:00
for _ , ins := range block . Instrs {
switch ins := ins . ( type ) {
2020-02-26 18:45:19 +00:00
case * ir . Store :
addr , ok := ins . Addr . ( * ir . IndexAddr )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if addr . X != fn . Params [ 1 ] {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , ins , "io.Writer.Write must not modify the provided buffer, not even temporarily" )
case * ir . Call :
if ! code . IsCallTo ( ins . Common ( ) , "append" ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
if ins . Common ( ) . Args [ 0 ] != fn . Params [ 1 ] {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , ins , "io.Writer.Write must not modify the provided buffer, not even temporarily" )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func loopedRegexp ( name string ) CallCheck {
return func ( call * Call ) {
if len ( extractConsts ( call . Args [ 0 ] . Value . Value ) ) == 0 {
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 CheckEmptyBranch ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
if fn . Source ( ) == nil {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
if code . IsExample ( fn ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
cb := func ( node ast . Node ) bool {
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , ifstmt . Else , "empty branch" , report . FilterGenerated ( ) , report . ShortRange ( ) )
2019-11-07 19:05:39 +00:00
}
if len ( ifstmt . Body . List ) != 0 {
return true
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , ifstmt , "empty branch" , report . FilterGenerated ( ) , report . ShortRange ( ) )
2019-11-07 19:05:39 +00:00
return true
}
2020-02-26 18:45:19 +00:00
Inspect ( fn . Source ( ) , cb )
2019-11-07 19:05:39 +00:00
}
return nil , nil
}
func CheckMapBytesKey ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
2019-11-07 19:05:39 +00:00
for _ , b := range fn . Blocks {
insLoop :
for _ , ins := range b . Instrs {
// find []byte -> string conversions
2020-02-26 18:45:19 +00:00
conv , ok := ins . ( * ir . Convert )
2019-11-07 19:05:39 +00:00
if ! ok || conv . Type ( ) != types . Universe . Lookup ( "string" ) . Type ( ) {
continue
}
if s , ok := conv . X . Type ( ) . ( * types . Slice ) ; ! ok || s . Elem ( ) != types . Universe . Lookup ( "byte" ) . Type ( ) {
continue
}
refs := conv . 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 ) {
2020-02-26 18:45:19 +00:00
case * ir . DebugRef :
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
case * ir . MapLookup :
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , conv , "m[string(key)] would be more efficient than k := string(key); m[k]" )
2019-11-07 19:05:39 +00:00
}
}
}
return nil , nil
}
func CheckRangeStringRunes ( pass * analysis . Pass ) ( interface { } , error ) {
return sharedcheck . CheckRangeStringRunes ( pass )
}
func CheckSelfAssignment ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
pure := pass . ResultOf [ facts . Purity ] . ( facts . PurityResult )
2019-11-07 19:05:39 +00:00
fn := func ( node ast . Node ) {
assign := node . ( * ast . AssignStmt )
if assign . Tok != token . ASSIGN || len ( assign . Lhs ) != len ( assign . Rhs ) {
return
}
2020-02-26 18:45:19 +00:00
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 )
2019-11-07 19:05:39 +00:00
if rlh == rrh {
2020-02-26 18:45:19 +00:00
report . Report ( pass , assign , fmt . Sprintf ( "self-assignment of %s to %s" , rrh , rlh ) , report . FilterGenerated ( ) )
2019-11-07 19:05:39 +00:00
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . AssignStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
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 CheckDuplicateBuildConstraints ( pass * analysis . Pass ) ( interface { } , 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 ) {
2020-02-26 18:45:19 +00:00
msg := fmt . Sprintf ( "identical build constraints %q and %q" ,
2019-11-07 19:05:39 +00:00
strings . Join ( constraint1 , " " ) ,
strings . Join ( constraint2 , " " ) )
2020-02-26 18:45:19 +00:00
report . Report ( pass , f , msg , report . FilterGenerated ( ) , report . ShortRange ( ) )
2019-11-07 19:05:39 +00:00
}
}
}
}
return nil , nil
}
func CheckSillyRegexp ( pass * analysis . Pass ) ( interface { } , error ) {
// We could use the rule checking engine for this, but the
// arguments aren't really invalid.
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
2019-11-07 19:05:39 +00:00
for _ , b := range fn . Blocks {
for _ , ins := range b . Instrs {
2020-02-26 18:45:19 +00:00
call , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if ! code . IsCallToAny ( call . Common ( ) , "regexp.MustCompile" , "regexp.Compile" , "regexp.Match" , "regexp.MatchReader" , "regexp.MatchString" ) {
2019-11-07 19:05:39 +00:00
continue
}
2020-02-26 18:45:19 +00:00
c , ok := call . Common ( ) . Args [ 0 ] . ( * ir . Const )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
s := constant . StringVal ( c . Value )
re , err := syntax . Parse ( s , 0 )
if err != nil {
continue
}
if re . Op != syntax . OpLiteral && re . Op != syntax . OpEmptyMatch {
continue
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , call , "regular expression does not contain any meta characters" )
2019-11-07 19:05:39 +00:00
}
}
}
return nil , nil
}
func CheckMissingEnumTypesInDeclaration ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
decl := node . ( * ast . GenDecl )
if ! decl . Lparen . IsValid ( ) {
return
}
if decl . Tok != token . CONST {
return
}
2020-02-26 18:45:19 +00:00
groups := code . GroupSpecs ( pass . Fset , decl . Specs )
2019-11-07 19:05:39 +00:00
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
}
for i , spec := range group {
spec := spec . ( * ast . ValueSpec )
if len ( spec . Names ) != 1 || len ( spec . Values ) != 1 {
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
}
if i == 0 {
continue
}
if spec . Type != nil {
continue groupLoop
}
}
2020-02-26 18:45:19 +00:00
var edits [ ] analysis . TextEdit
typ := group [ 0 ] . ( * ast . ValueSpec ) . Type
for _ , spec := range group [ 1 : ] {
nspec := * spec . ( * ast . ValueSpec )
nspec . Type = typ
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 ... ) ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . GenDecl ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckTimerResetReturnValue ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
for _ , fn := range pass . ResultOf [ buildir . Analyzer ] . ( * buildir . IR ) . SrcFuncs {
2019-11-07 19:05:39 +00:00
for _ , block := range fn . Blocks {
for _ , ins := range block . Instrs {
2020-02-26 18:45:19 +00:00
call , ok := ins . ( * ir . Call )
2019-11-07 19:05:39 +00:00
if ! ok {
continue
}
2020-02-26 18:45:19 +00:00
if ! code . IsCallTo ( call . Common ( ) , "(*time.Timer).Reset" ) {
2019-11-07 19:05:39 +00:00
continue
}
refs := call . Referrers ( )
if refs == nil {
continue
}
2020-02-26 18:45:19 +00:00
for _ , ref := range code . FilterDebug ( * refs ) {
ifstmt , ok := ref . ( * ir . If )
2019-11-07 19:05:39 +00:00
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
}
2020-02-26 18:45:19 +00:00
irutil . Walk ( succ , func ( b * ir . BasicBlock ) bool {
2019-11-07 19:05:39 +00:00
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
2020-02-26 18:45:19 +00:00
if ins , ok := ins . ( * ir . Recv ) ; ok && code . IsType ( ins . Chan . Type ( ) , "<-chan time.Time" ) {
2019-11-07 19:05:39 +00:00
found = true
return false
}
}
return true
} )
}
if found {
2020-02-26 18:45:19 +00:00
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" )
2019-11-07 19:05:39 +00:00
}
}
}
}
}
return nil , nil
}
2020-02-26 18:45:19 +00:00
var (
checkToLowerToUpperComparisonQ = pattern . MustParse ( `
( BinaryExpr
( CallExpr fun @ ( Function ( Or "strings.ToLower" "strings.ToUpper" ) ) [ a ] )
tok @ ( Or "==" "!=" )
( CallExpr fun [ b ] ) ) ` )
checkToLowerToUpperComparisonR = pattern . MustParse ( ` (CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b]) ` )
)
2019-11-07 19:05:39 +00:00
func CheckToLowerToUpperComparison ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
m , ok := Match ( pass , checkToLowerToUpperComparisonQ , node )
if ! ok {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
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 ,
}
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , node , "should use strings.EqualFold instead" , report . Fixes ( edit . Fix ( "replace with strings.EqualFold" , edit . ReplaceWithNode ( pass . Fset , node , rn ) ) ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . BinaryExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckUnreachableTypeCases ( pass * analysis . Pass ) ( interface { } , 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 {
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 , len ( cc . List ) )
for i , expr := range cc . List {
Ts [ i ] = pass . TypesInfo . TypeOf ( expr )
}
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 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , next . cc , fmt . Sprintf ( "unreachable case clause: %s will always match before %s" , T . String ( ) , V . String ( ) ) ,
report . ShortRange ( ) )
2019-11-07 19:05:39 +00:00
}
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . TypeSwitchStmt ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
2020-02-26 18:45:19 +00:00
var checkSingleArgAppendQ = pattern . MustParse ( ` (CallExpr (Builtin "append") [_]) ` )
2019-11-07 19:05:39 +00:00
func CheckSingleArgAppend ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
2020-02-26 18:45:19 +00:00
_ , ok := Match ( pass , checkSingleArgAppendQ , node )
if ! ok {
2019-11-07 19:05:39 +00:00
return
}
2020-02-26 18:45:19 +00:00
report . Report ( pass , node , "x = append(y) is equivalent to x = y" , report . FilterGenerated ( ) )
2019-11-07 19:05:39 +00:00
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . CallExpr ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func CheckStructTags ( pass * analysis . Pass ) ( interface { } , error ) {
2020-02-26 18:45:19 +00:00
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
}
}
}
2019-11-07 19:05:39 +00:00
fn := func ( node ast . Node ) {
for _ , field := range node . ( * ast . StructType ) . Fields . List {
if field . Tag == nil {
continue
}
tags , err := parseStructTag ( field . Tag . Value [ 1 : len ( field . Tag . Value ) - 1 ] )
if err != nil {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "unparseable struct tag: %s" , err ) )
2019-11-07 19:05:39 +00:00
continue
}
for k , v := range tags {
if len ( v ) > 1 {
2020-02-26 18:45:19 +00:00
isGoFlagsTag := importsGoFlags &&
( k == "choice" || k == "optional-value" || k == "default" )
if ! isGoFlagsTag {
report . Report ( pass , field . Tag , fmt . Sprintf ( "duplicate struct tag %q" , k ) )
}
2019-11-07 19:05:39 +00:00
}
switch k {
case "json" :
checkJSONTag ( pass , field , v [ 0 ] )
case "xml" :
checkXMLTag ( pass , field , v [ 0 ] )
}
}
}
}
2020-02-26 18:45:19 +00:00
code . Preorder ( pass , fn , ( * ast . StructType ) ( nil ) )
2019-11-07 19:05:39 +00:00
return nil , nil
}
func checkJSONTag ( pass * analysis . Pass , field * ast . Field , tag string ) {
2020-02-26 18:45:19 +00:00
if pass . Pkg . Path ( ) == "encoding/json" || pass . Pkg . Path ( ) == "encoding/json_test" {
// don't flag malformed JSON tags in the encoding/json
// package; it knows what it is doing, and it is testing
// itself.
return
}
2019-11-07 19:05:39 +00:00
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
if len ( tag ) == 0 {
}
fields := strings . Split ( tag , "," )
for _ , r := range fields [ 0 ] {
if ! unicode . IsLetter ( r ) && ! unicode . IsDigit ( r ) && ! strings . ContainsRune ( "!#$%&()*+-./:<=>?@[]^_{|}~ " , r ) {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "invalid JSON field name %q" , fields [ 0 ] ) )
2019-11-07 19:05:39 +00:00
}
}
var co , cs , ci int
for _ , s := range fields [ 1 : ] {
switch s {
case "omitempty" :
co ++
case "" :
// allow stuff like "-,"
case "string" :
cs ++
// only for string, floating point, integer and bool
2020-02-26 18:45:19 +00:00
T := code . Dereference ( pass . TypesInfo . TypeOf ( field . Type ) . Underlying ( ) ) . Underlying ( )
2019-11-07 19:05:39 +00:00
basic , ok := T . ( * types . Basic )
if ! ok || ( basic . Info ( ) & ( types . IsBoolean | types . IsInteger | types . IsFloat | types . IsString ) ) == 0 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , "the JSON string option only applies to fields of type string, floating point, integer or bool, or pointers to those" )
2019-11-07 19:05:39 +00:00
}
case "inline" :
ci ++
default :
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "unknown JSON option %q" , s ) )
2019-11-07 19:05:39 +00:00
}
}
if co > 1 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , ` duplicate JSON option "omitempty" ` )
2019-11-07 19:05:39 +00:00
}
if cs > 1 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , ` duplicate JSON option "string" ` )
2019-11-07 19:05:39 +00:00
}
if ci > 1 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , ` duplicate JSON option "inline" ` )
2019-11-07 19:05:39 +00:00
}
}
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 { }
var exclusives [ ] string
for _ , s := range fields [ 1 : ] {
switch s {
case "attr" , "chardata" , "cdata" , "innerxml" , "comment" :
counts [ s ] ++
if counts [ s ] == 1 {
exclusives = append ( exclusives , s )
}
case "omitempty" , "any" :
counts [ s ] ++
case "" :
default :
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "unknown XML option %q" , s ) )
2019-11-07 19:05:39 +00:00
}
}
for k , v := range counts {
if v > 1 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "duplicate XML option %q" , k ) )
2019-11-07 19:05:39 +00:00
}
}
if len ( exclusives ) > 1 {
2020-02-26 18:45:19 +00:00
report . Report ( pass , field . Tag , fmt . Sprintf ( "XML options %s are mutually exclusive" , strings . Join ( exclusives , " and " ) ) )
}
}
func CheckImpossibleTypeAssertion ( pass * analysis . Pass ) ( interface { } , 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 {
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 i := 0 ; i < righti . NumMethods ( ) ; i ++ {
mr := righti . Method ( i )
sel := ms . Lookup ( mr . Pkg ( ) , mr . Name ( ) )
if sel == nil {
continue
}
ml := sel . Obj ( ) . ( * types . Func )
if types . AssignableTo ( ml . Type ( ) , mr . Type ( ) ) {
continue
}
wrong = append ( wrong , entry { ml , mr } )
}
if len ( wrong ) != 0 {
s := 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 += fmt . Sprintf ( "\n\twrong type for %s method" , e . l . Name ( ) )
s += fmt . Sprintf ( "\n\t\thave %s" , e . l . Type ( ) )
s += fmt . Sprintf ( "\n\t\twant %s" , e . r . Type ( ) )
}
report . Report ( pass , assert , s )
}
}
}
}
return nil , nil
}
func checkWithValueKey ( call * Call ) {
arg := call . Args [ 1 ]
T := arg . Value . Value . Type ( )
if T , ok := T . ( * types . Basic ) ; ok {
arg . Invalid (
fmt . Sprintf ( "should not use built-in type %s as key for value; define your own type to avoid collisions" , T ) )
}
if ! types . Comparable ( T ) {
arg . Invalid ( fmt . Sprintf ( "keys used with context.WithValue must be comparable, but type %s is not comparable" , T ) )
2019-11-07 19:05:39 +00:00
}
}
2020-02-26 18:45:19 +00:00
func CheckMaybeNil ( pass * analysis . Pass ) ( interface { } , 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
//
// 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 code . 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 {
if instr , ok := instr . ( * ir . BinOp ) ; ok {
var ptr ir . Value
if isNilConst ( instr . X ) {
ptr = instr . Y
} else if isNilConst ( instr . Y ) {
ptr = instr . X
}
maybeNil [ ptr ] = instr
}
}
}
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
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
}
var checkAddressIsNilQ = pattern . MustParse (
` ( BinaryExpr
( UnaryExpr "&" _ )
( Or "==" "!=" )
( Builtin "nil" ) ) ` )
func CheckAddressIsNil ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node ) {
_ , ok := Match ( pass , checkAddressIsNilQ , node )
if ! ok {
return
}
report . Report ( pass , node , "the address of a variable cannot be nil" )
}
code . Preorder ( pass , fn , ( * ast . BinaryExpr ) ( nil ) )
return nil , nil
}