2018-12-31 02:18:41 +01: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"
2019-02-18 20:32:41 +01:00
"reflect"
2018-12-31 02:18:41 +01:00
"regexp"
"regexp/syntax"
"sort"
"strconv"
"strings"
texttemplate "text/template"
2019-04-22 12:59:42 +02:00
"unicode"
2018-12-31 02:18:41 +01:00
2019-02-18 20:32:41 +01:00
. "honnef.co/go/tools/arg"
2018-12-31 02:18:41 +01:00
"honnef.co/go/tools/deprecated"
2020-05-09 15:44:17 +02:00
"honnef.co/go/tools/facts"
2018-12-31 02:18:41 +01:00
"honnef.co/go/tools/functions"
2020-05-09 15:44:17 +02:00
"honnef.co/go/tools/internal/passes/buildssa"
2018-12-31 02:18:41 +01:00
"honnef.co/go/tools/internal/sharedcheck"
"honnef.co/go/tools/lint"
. "honnef.co/go/tools/lint/lintdsl"
2019-04-22 12:59:42 +02:00
"honnef.co/go/tools/printf"
2018-12-31 02:18:41 +01:00
"honnef.co/go/tools/ssa"
2019-02-18 20:32:41 +01:00
"honnef.co/go/tools/ssautil"
2018-12-31 02:18:41 +01:00
"honnef.co/go/tools/staticcheck/vrp"
2020-05-09 15:44:17 +02:00
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
2018-12-31 02:18:41 +01:00
"golang.org/x/tools/go/ast/astutil"
2020-05-09 15:44:17 +02:00
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
2018-12-31 02:18:41 +01: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 ) {
call . Invalid ( fmt . Sprintf ( "calling %s on a converted integer is pointless" , CallName ( call . Instr . Common ( ) ) ) )
}
}
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 ) {
2019-02-18 20:32:41 +01:00
arg := call . Args [ Arg ( "time.Parse.layout" ) ]
2018-12-31 02:18:41 +01:00
err := ValidateTimeLayout ( arg . Value )
if err != nil {
arg . Invalid ( err . Error ( ) )
}
} ,
}
checkEncodingBinaryRules = map [ string ] CallCheck {
"encoding/binary.Write" : func ( call * Call ) {
2019-02-18 20:32:41 +01:00
arg := call . Args [ Arg ( "encoding/binary.Write.data" ) ]
2020-05-09 15:44:17 +02:00
if ! CanBinaryMarshal ( call . Pass , arg . Value ) {
2018-12-31 02:18:41 +01:00
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 ) {
2019-02-18 20:32:41 +01:00
arg := call . Args [ Arg ( "net/url.Parse.rawurl" ) ]
2018-12-31 02:18:41 +01:00
err := ValidateURL ( arg . Value )
if err != nil {
arg . Invalid ( err . Error ( ) )
}
} ,
}
checkSyncPoolValueRules = map [ string ] CallCheck {
"(*sync.Pool).Put" : func ( call * Call ) {
2019-02-18 20:32:41 +01:00
arg := call . Args [ Arg ( "(*sync.Pool).Put.x" ) ]
2018-12-31 02:18:41 +01:00
typ := arg . Value . Value . Type ( )
if ! IsPointerLike ( typ ) {
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 ) {
2019-02-18 20:32:41 +01:00
arg := call . Args [ Arg ( "os/signal.Notify.c" ) ]
2018-12-31 02:18:41 +01:00
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 ) {
2019-02-18 20:32:41 +01:00
if ConvertedFrom ( call . Args [ Arg ( "bytes.Equal.a" ) ] . Value , "net.IP" ) &&
ConvertedFrom ( call . Args [ Arg ( "bytes.Equal.b" ) ] . Value , "net.IP" ) {
2018-12-31 02:18:41 +01:00
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" ) ,
}
2019-02-18 20:32:41 +01:00
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" ) ,
}
2019-04-22 12:59:42 +02:00
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 {
"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 ) } ,
}
)
func checkPrintfCall ( call * Call , fIdx , vIdx int ) {
f := call . Args [ fIdx ]
var args [ ] ssa . Value
switch v := call . Args [ vIdx ] . Value . Value . ( type ) {
case * ssa . Slice :
var ok bool
args , ok = ssautil . Vararg ( v )
if ! ok {
// We don't know what the actual arguments to the function are
return
}
case * ssa . Const :
// nil, i.e. no arguments
default :
// We don't know what the actual arguments to the function are
return
}
checkPrintfCallImpl ( call , f . Value . Value , args )
}
type verbFlag int
const (
isInt verbFlag = 1 << iota
isBool
isFP
isString
isPointer
isPseudoPointer
isSlice
isAny
noRecurse
2018-12-31 02:18:41 +01:00
)
2019-04-22 12:59:42 +02:00
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 ,
'p' : isSlice | isPointer | noRecurse ,
'q' : isInt | isString ,
's' : isString ,
't' : isBool ,
'T' : isAny ,
'U' : isInt ,
'v' : isAny ,
'X' : isPseudoPointer | isInt | isString ,
'x' : isPseudoPointer | isInt | isString ,
}
func checkPrintfCallImpl ( call * Call , f ssa . Value , args [ ] ssa . Value ) {
2020-05-09 15:44:17 +02:00
var msCache * typeutil . MethodSetCache
if f . Parent ( ) != nil {
msCache = & f . Parent ( ) . Prog . MethodSets
}
elem := func ( T types . Type , verb rune ) ( [ ] types . Type , bool ) {
2019-04-22 12:59:42 +02:00
if verbs [ verb ] & noRecurse != 0 {
return [ ] types . Type { T } , false
}
switch T := T . ( type ) {
case * types . Slice :
if verbs [ verb ] & isSlice != 0 {
return [ ] types . Type { T } , false
}
if verbs [ verb ] & isString != 0 && IsType ( T . Elem ( ) . Underlying ( ) , "byte" ) {
return [ ] types . Type { T } , false
}
return [ ] types . Type { T . Elem ( ) } , true
case * types . Map :
key := T . Key ( )
val := T . Elem ( )
return [ ] types . Type { key , val } , true
case * types . Struct :
out := make ( [ ] types . Type , 0 , T . NumFields ( ) )
for 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
}
if ! IsType ( sig . Results ( ) . At ( 0 ) . Type ( ) , "string" ) {
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
}
if ! IsType ( sig . Results ( ) . At ( 0 ) . Type ( ) , "string" ) {
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
}
2020-05-09 15:44:17 +02:00
ms := msCache . MethodSet ( T )
2019-04-22 12:59:42 +02:00
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 {
T = Dereference ( T )
}
if flags & isPseudoPointer != 0 && top {
t := Dereference ( T )
if _ , ok := t . Underlying ( ) . ( * types . Struct ) ; ok {
T = t
}
}
if _ , ok := T . ( * types . Interface ) ; ok {
// We don't know what's in the interface
return true
}
var info types . BasicInfo
if flags & isInt != 0 {
info |= types . IsInteger
}
if flags & isBool != 0 {
info |= types . IsBoolean
}
if flags & isFP != 0 {
info |= types . IsFloat | types . IsComplex
}
if flags & isString != 0 {
info |= types . IsString
}
if info != 0 && isInfo ( T , info ) {
return true
}
if flags & isString != 0 && ( IsType ( T , "[]byte" ) || isStringer ( T , ms ) || isError ( T , ms ) ) {
return true
}
if flags & isPointer != 0 && IsPointerLike ( T ) {
return true
}
if flags & isPseudoPointer != 0 {
switch U := T . Underlying ( ) . ( type ) {
case * types . Pointer :
if ! top {
return true
}
if _ , ok := U . Elem ( ) . Underlying ( ) . ( * types . Struct ) ; ! ok {
return true
}
case * types . Chan , * types . Signature :
return true
}
}
if flags & isSlice != 0 {
if _ , ok := T . ( * types . Slice ) ; ok {
return true
}
}
if flags & isAny != 0 {
return true
}
elems , ok := elem ( T . Underlying ( ) , verb )
if ! ok {
return false
}
for _ , elem := range elems {
if ! checkType ( verb , elem , false ) {
return false
}
}
return true
}
k , ok := f . ( * ssa . Const )
if ! ok {
return
}
actions , err := printf . Parse ( constant . StringVal ( k . Value ) )
if err != nil {
call . Invalid ( "couldn't parse format string" )
return
}
ptr := 1
hasExplicit := false
checkStar := func ( verb printf . Verb , star printf . Argument ) bool {
if star , ok := star . ( printf . Star ) ; ok {
idx := 0
if star . Index == - 1 {
idx = ptr
ptr ++
} else {
hasExplicit = true
idx = star . Index
ptr = star . Index + 1
}
if idx == 0 {
call . Invalid ( fmt . Sprintf ( "Printf format %s reads invalid arg 0; indices are 1-based" , verb . Raw ) )
return false
}
if idx > len ( args ) {
call . Invalid (
fmt . Sprintf ( "Printf format %s reads arg #%d, but call has only %d args" ,
verb . Raw , idx , len ( args ) ) )
return false
}
if arg , ok := args [ idx - 1 ] . ( * ssa . MakeInterface ) ; ok {
if ! isInfo ( arg . X . Type ( ) , types . IsInteger ) {
call . Invalid ( fmt . Sprintf ( "Printf format %s reads non-int arg #%d as argument of *" , verb . Raw , idx ) )
}
}
}
return true
}
// We only report one problem per format string. Making a
// mistake with an index tends to invalidate all future
// implicit indices.
for _ , action := range actions {
verb , ok := action . ( printf . Verb )
if ! ok {
continue
}
if ! checkStar ( verb , verb . Width ) || ! checkStar ( verb , verb . Precision ) {
return
}
off := ptr
if verb . Value != - 1 {
hasExplicit = true
off = verb . Value
}
if off > len ( args ) {
call . Invalid (
fmt . Sprintf ( "Printf format %s reads arg #%d, but call has only %d args" ,
verb . Raw , off , len ( args ) ) )
return
} else if verb . Value == 0 && verb . Letter != '%' {
call . Invalid ( fmt . Sprintf ( "Printf format %s reads invalid arg 0; indices are 1-based" , verb . Raw ) )
return
} else if off != 0 {
arg , ok := args [ off - 1 ] . ( * ssa . MakeInterface )
if ok {
if ! checkType ( verb . Letter , arg . X . Type ( ) , true ) {
call . Invalid ( fmt . Sprintf ( "Printf format %s has arg #%d of wrong type %s" ,
verb . Raw , ptr , args [ ptr - 1 ] . ( * ssa . MakeInterface ) . X . Type ( ) ) )
return
}
}
}
switch verb . Value {
case - 1 :
// Consume next argument
ptr ++
case 0 :
// Don't consume any arguments
default :
ptr = verb . Value + 1
}
}
if ! hasExplicit && ptr <= len ( args ) {
call . Invalid ( fmt . Sprintf ( "Printf call needs %d args but has %d args" , ptr - 1 , len ( args ) ) )
}
}
func checkAtomicAlignmentImpl ( call * Call ) {
2020-05-09 15:44:17 +02:00
sizes := call . Pass . TypesSizes
2019-04-22 12:59:42 +02:00
if sizes . Sizeof ( types . Typ [ types . Uintptr ] ) != 4 {
// Not running on a 32-bit platform
return
}
v , ok := call . Args [ 0 ] . Value . Value . ( * ssa . FieldAddr )
if ! ok {
// TODO(dh): also check indexing into arrays and slices
return
}
T := v . X . Type ( ) . Underlying ( ) . ( * types . Pointer ) . Elem ( ) . Underlying ( ) . ( * types . Struct )
fields := make ( [ ] * types . Var , 0 , T . NumFields ( ) )
for i := 0 ; i < T . NumFields ( ) && i <= v . Field ; i ++ {
fields = append ( fields , T . Field ( i ) )
}
off := sizes . Offsetsof ( fields ) [ v . Field ]
if off % 8 != 0 {
msg := fmt . Sprintf ( "address of non 64-bit aligned field %s passed to %s" ,
T . Field ( v . Field ) . Name ( ) ,
CallName ( call . Instr . Common ( ) ) )
call . Invalid ( msg )
}
}
2019-02-18 20:32:41 +01:00
func checkNoopMarshalImpl ( argN int , meths ... string ) CallCheck {
return func ( call * Call ) {
2020-05-09 15:44:17 +02:00
if IsGenerated ( call . Pass , call . Instr . Pos ( ) ) {
return
}
2019-02-18 20:32:41 +01:00
arg := call . Args [ argN ]
T := arg . Value . Value . Type ( )
Ts , ok := Dereference ( T ) . Underlying ( ) . ( * types . Struct )
if ! ok {
return
}
if Ts . NumFields ( ) == 0 {
return
}
fields := FlattenFields ( Ts )
for _ , field := range fields {
if field . Var . Exported ( ) {
return
}
}
// OPT(dh): we could use a method set cache here
2020-05-09 15:44:17 +02:00
ms := call . Instr . Parent ( ) . Prog . MethodSets . MethodSet ( T )
2019-02-18 20:32:41 +01:00
// 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 ) {
2020-05-09 15:44:17 +02:00
msCache := & call . Instr . Parent ( ) . Prog . MethodSets
2019-02-18 20:32:41 +01:00
arg := call . Args [ argN ]
T := arg . Value . Value . Type ( )
Ts , ok := Dereference ( T ) . Underlying ( ) . ( * types . Struct )
if ! ok {
return
}
2020-05-09 15:44:17 +02:00
ms := msCache . MethodSet ( T )
2019-02-18 20:32:41 +01:00
// 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
}
}
fields := FlattenFields ( Ts )
for _ , field := range fields {
if ! ( field . Var . Exported ( ) ) {
continue
}
if reflect . StructTag ( field . Tag ) . Get ( tag ) == "-" {
continue
}
2020-05-09 15:44:17 +02:00
ms := msCache . MethodSet ( field . Var . Type ( ) )
2019-02-18 20:32:41 +01:00
// 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 {
field := Dereference ( start ) . Underlying ( ) . ( * types . Struct ) . Field ( idx )
start = field . Type ( )
p += "." + field . Name ( )
}
return p
}
2020-05-09 15:44:17 +02:00
func isInLoop ( b * ssa . BasicBlock ) bool {
sets := functions . FindLoops ( b . Parent ( ) )
2018-12-31 02:18:41 +01:00
for _ , set := range sets {
2020-05-09 15:44:17 +02:00
if set . Has ( b ) {
2018-12-31 02:18:41 +01:00
return true
}
}
return false
}
2020-05-09 15:44:17 +02:00
func CheckUntrappableSignal ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-05-09 15:44:17 +02:00
if ! IsCallToAnyAST ( pass , call ,
2018-12-31 02:18:41 +01:00
"os/signal.Ignore" , "os/signal.Notify" , "os/signal.Reset" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
for _ , arg := range call . Args {
2020-05-09 15:44:17 +02:00
if conv , ok := arg . ( * ast . CallExpr ) ; ok && isName ( pass , conv . Fun , "os.Signal" ) {
2018-12-31 02:18:41 +01:00
arg = conv . Args [ 0 ]
}
2020-05-09 15:44:17 +02:00
if isName ( pass , arg , "os.Kill" ) || isName ( pass , arg , "syscall.SIGKILL" ) {
ReportNodef ( pass , arg , "%s cannot be trapped (did you mean syscall.SIGTERM?)" , Render ( pass , arg ) )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if isName ( pass , arg , "syscall.SIGSTOP" ) {
ReportNodef ( pass , arg , "%s signal cannot be trapped" , Render ( pass , arg ) )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckTemplate ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2018-12-31 02:18:41 +01:00
var kind string
2020-05-09 15:44:17 +02:00
if IsCallToAST ( pass , call , "(*text/template.Template).Parse" ) {
2018-12-31 02:18:41 +01:00
kind = "text"
2020-05-09 15:44:17 +02:00
} else if IsCallToAST ( pass , call , "(*html/template.Template).Parse" ) {
2018-12-31 02:18:41 +01:00
kind = "html"
} else {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
sel := call . Fun . ( * ast . SelectorExpr )
2020-05-09 15:44:17 +02:00
if ! IsCallToAST ( pass , sel . X , "text/template.New" ) &&
! IsCallToAST ( pass , sel . X , "html/template.New" ) {
2018-12-31 02:18:41 +01: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
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
s , ok := ExprToString ( pass , call . Args [ Arg ( "(*text/template.Template).Parse.text" ) ] )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
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-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ Arg ( "(*text/template.Template).Parse.text" ) ] , "%s" , err )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckTimeSleepConstant ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-05-09 15:44:17 +02:00
if ! IsCallToAST ( pass , call , "time.Sleep" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
lit , ok := call . Args [ Arg ( "time.Sleep.d" ) ] . ( * ast . BasicLit )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
n , err := strconv . Atoi ( lit . Value )
if err != nil {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
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.
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
recommendation := "time.Sleep(time.Nanosecond)"
if n != 1 {
recommendation = fmt . Sprintf ( "time.Sleep(%d * time.Nanosecond)" , n )
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ Arg ( "time.Sleep.d" ) ] ,
2019-02-18 20:32:41 +01:00
"sleeping for %d nanoseconds is probably a bug. Be explicit if it isn't: %s" , n , recommendation )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckWaitgroupAdd ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
g := node . ( * ast . GoStmt )
2018-12-31 02:18:41 +01:00
fun , ok := g . Call . Fun . ( * ast . FuncLit )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if len ( fun . Body . List ) == 0 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
stmt , ok := fun . Body . List [ 0 ] . ( * ast . ExprStmt )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if IsCallToAST ( pass , stmt . X , "(*sync.WaitGroup).Add" ) {
ReportNodef ( pass , stmt , "should call %s before starting the goroutine to avoid a race" ,
Render ( pass , stmt ) )
2018-12-31 02:18:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . GoStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckInfiniteEmptyLoop ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
loop := node . ( * ast . ForStmt )
if len ( loop . Body . List ) != 0 || loop . Post != nil {
return
2018-12-31 02:18:41 +01:00
}
if loop . Init != nil {
// TODO(dh): this isn't strictly necessary, it just makes
// the check easier.
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
// 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 {
2019-02-18 20:32:41 +01:00
if hasSideEffects ( loop . Cond ) {
2019-04-22 12:59:42 +02:00
return
2019-02-18 20:32:41 +01:00
}
if ident , ok := loop . Cond . ( * ast . Ident ) ; ok {
2020-05-09 15:44:17 +02:00
if k , ok := pass . TypesInfo . ObjectOf ( ident ) . ( * types . Const ) ; ok {
2019-02-18 20:32:41 +01:00
if ! constant . BoolVal ( k . Val ( ) ) {
// don't flag `for false {}` loops. They're a debug aid.
2019-04-22 12:59:42 +02:00
return
2019-02-18 20:32:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , loop , "loop condition never changes or has a race condition" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , loop , "this loop will spin, using 100%% CPU" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . ForStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckDeferInInfiniteLoop ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
2018-12-31 02:18:41 +01:00
mightExit := false
var defers [ ] ast . Stmt
2019-04-22 12:59:42 +02:00
loop := node . ( * ast . ForStmt )
if loop . Cond != nil {
return
2018-12-31 02:18:41 +01:00
}
fn2 := func ( node ast . Node ) bool {
switch stmt := node . ( type ) {
case * ast . ReturnStmt :
mightExit = true
2020-05-09 15:44:17 +02:00
return false
2018-12-31 02:18:41 +01:00
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
2020-05-09 15:44:17 +02:00
return false
2018-12-31 02:18:41 +01:00
}
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 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
for _ , stmt := range defers {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , stmt , "defers in this infinite loop will never run" )
2018-12-31 02:18:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . ForStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckDubiousDeferInChannelRangeLoop ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
loop := node . ( * ast . RangeStmt )
2020-05-09 15:44:17 +02:00
typ := pass . TypesInfo . TypeOf ( loop . X )
2019-04-22 12:59:42 +02:00
_ , ok := typ . Underlying ( ) . ( * types . Chan )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
fn2 := func ( node ast . Node ) bool {
switch stmt := node . ( type ) {
case * ast . DeferStmt :
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , stmt , "defers in this range loop won't run unless the channel gets closed" )
2018-12-31 02:18:41 +01:00
case * ast . FuncLit :
// Don't look into function bodies
return false
}
return true
}
ast . Inspect ( loop . Body , fn2 )
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . RangeStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
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 {
ReportNodef ( pass , fnmain , "TestMain should call os.Exit to set exit code" )
}
fnmain = nil
callsExit = false
callsRun = false
arg = nil
}
return true
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
switch node := node . ( type ) {
case * ast . FuncDecl :
if fnmain != nil {
2018-12-31 02:18:41 +01:00
return true
}
2020-05-09 15:44:17 +02:00
if ! isTestMain ( pass , node ) {
return false
}
fnmain = node
arg = pass . TypesInfo . ObjectOf ( node . Type . Params . List [ 0 ] . Names [ 0 ] )
return true
case * ast . CallExpr :
if IsCallToAST ( pass , node , "os.Exit" ) {
callsExit = true
return false
}
sel , ok := node . Fun . ( * ast . SelectorExpr )
2018-12-31 02:18:41 +01:00
if ! ok {
return true
}
ident , ok := sel . X . ( * ast . Ident )
if ! ok {
return true
}
2020-05-09 15:44:17 +02:00
if arg != pass . TypesInfo . ObjectOf ( ident ) {
2018-12-31 02:18:41 +01:00
return true
}
if sel . Sel . Name == "Run" {
callsRun = true
return false
}
return true
2020-05-09 15:44:17 +02:00
default :
// unreachable
2018-12-31 02:18:41 +01:00
return true
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Nodes ( [ ] ast . Node { ( * ast . FuncDecl ) ( nil ) , ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func isTestMain ( pass * analysis . Pass , decl * ast . FuncDecl ) bool {
2018-12-31 02:18:41 +01:00
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-05-09 15:44:17 +02:00
return IsOfType ( pass , arg . Type , "*testing.M" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckExec ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-05-09 15:44:17 +02:00
if ! IsCallToAST ( pass , call , "os/exec.Command" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
val , ok := ExprToString ( pass , call . Args [ Arg ( "os/exec.Command.name" ) ] )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if ! strings . Contains ( val , " " ) || strings . Contains ( val , ` \ ` ) || strings . Contains ( val , "/" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ Arg ( "os/exec.Command.name" ) ] ,
2019-02-18 20:32:41 +01:00
"first argument to exec.Command looks like a shell command, but a program name or path are expected" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckLoopEmptyDefault ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
loop := node . ( * ast . ForStmt )
if len ( loop . Body . List ) != 1 || loop . Cond != nil || loop . Init != nil {
return
2018-12-31 02:18:41 +01:00
}
sel , ok := loop . Body . List [ 0 ] . ( * ast . SelectStmt )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
for _ , c := range sel . Body . List {
if comm , ok := c . ( * ast . CommClause ) ; ok && comm . Comm == nil && len ( comm . Body ) == 0 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , comm , "should not have an empty default case in a for+select loop. The loop will spin." )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . ForStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckLhsRhsIdentical ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
op := node . ( * ast . BinaryExpr )
2018-12-31 02:18:41 +01:00
switch op . Op {
case token . EQL , token . NEQ :
2020-05-09 15:44:17 +02:00
if basic , ok := pass . TypesInfo . TypeOf ( op . X ) . Underlying ( ) . ( * types . Basic ) ; ok {
2018-12-31 02:18:41 +01:00
if kind := basic . Kind ( ) ; kind == types . Float32 || kind == types . Float64 {
// f == f and f != f might be used to check for NaN
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01: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
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if Render ( pass , op . X ) != Render ( pass , op . Y ) {
2019-04-22 12:59:42 +02:00
return
}
l1 , ok1 := op . X . ( * ast . BasicLit )
l2 , ok2 := op . Y . ( * ast . BasicLit )
2020-05-09 15:44:17 +02:00
if ok1 && ok2 && l1 . Kind == token . INT && l2 . Kind == l1 . Kind && l1 . Value == "0" && l2 . Value == l1 . Value && IsGenerated ( pass , l1 . Pos ( ) ) {
2019-04-22 12:59:42 +02: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
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , op , "identical expressions on the left and right side of the '%s' operator" , op . Op )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . BinaryExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckScopedBreak ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
2018-12-31 02:18:41 +01:00
var body * ast . BlockStmt
switch node := node . ( type ) {
case * ast . ForStmt :
body = node . Body
case * ast . RangeStmt :
body = node . Body
default :
2019-04-22 12:59:42 +02:00
panic ( fmt . Sprintf ( "unreachable: %T" , node ) )
2018-12-31 02:18:41 +01: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-05-09 15:44:17 +02:00
ReportNodef ( pass , branch , "ineffective break statement. Did you mean to break out of the outer loop?" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . ForStmt ) ( nil ) , ( * ast . RangeStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckUnsafePrintf ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2019-02-18 20:32:41 +01:00
var arg int
2020-05-09 15:44:17 +02:00
if IsCallToAnyAST ( pass , call , "fmt.Printf" , "fmt.Sprintf" , "log.Printf" ) {
2019-02-18 20:32:41 +01:00
arg = Arg ( "fmt.Printf.format" )
2020-05-09 15:44:17 +02:00
} else if IsCallToAnyAST ( pass , call , "fmt.Fprintf" ) {
2019-02-18 20:32:41 +01:00
arg = Arg ( "fmt.Fprintf.format" )
} else {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
if len ( call . Args ) != arg + 1 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
switch call . Args [ arg ] . ( type ) {
2018-12-31 02:18:41 +01:00
case * ast . CallExpr , * ast . Ident :
default :
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ arg ] ,
2019-02-18 20:32:41 +01:00
"printf-style function with dynamic format string and no further arguments should use print-style function instead" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckEarlyDefer ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
block := node . ( * ast . BlockStmt )
2018-12-31 02:18:41 +01:00
if len ( block . List ) < 2 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
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
}
2020-05-09 15:44:17 +02:00
sig , ok := pass . TypesInfo . TypeOf ( call . Fun ) . ( * types . Signature )
2018-12-31 02:18:41 +01:00
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-05-09 15:44:17 +02:00
ReportNodef ( pass , def , "should check returned error before deferring %s" , Render ( pass , def . Call ) )
2018-12-31 02:18:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . BlockStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
func selectorX ( sel * ast . SelectorExpr ) ast . Node {
switch x := sel . X . ( type ) {
case * ast . SelectorExpr :
return selectorX ( x )
default :
return x
}
}
2020-05-09 15:44:17 +02:00
func CheckEmptyCriticalSection ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
// Initially it might seem like this check would be easier to
// implement in SSA. After all, we're only checking for two
// consecutive method calls. In reality, however, there may be any
// number of other instructions between the lock and unlock, while
// still constituting an empty critical section. For example,
// given `m.x().Lock(); m.x().Unlock()`, there will be a call to
// x(). In the AST-based approach, this has a tiny potential for a
// false positive (the second call to x might be doing work that
// is protected by the mutex). In an SSA-based approach, however,
// it would miss a lot of real bugs.
mutexParams := func ( s ast . Stmt ) ( x ast . Expr , funcName string , ok bool ) {
expr , ok := s . ( * ast . ExprStmt )
if ! ok {
return nil , "" , false
}
call , ok := expr . X . ( * ast . CallExpr )
if ! ok {
return nil , "" , false
}
sel , ok := call . Fun . ( * ast . SelectorExpr )
if ! ok {
return nil , "" , false
}
2020-05-09 15:44:17 +02:00
fn , ok := pass . TypesInfo . ObjectOf ( sel . Sel ) . ( * types . Func )
2018-12-31 02:18:41 +01:00
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
}
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
block := node . ( * ast . BlockStmt )
2018-12-31 02:18:41 +01:00
if len ( block . List ) < 2 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
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-05-09 15:44:17 +02:00
if ! ok1 || ! ok2 || Render ( pass , sel1 ) != Render ( pass , sel2 ) {
2018-12-31 02:18:41 +01:00
continue
}
if ( method1 == "Lock" && method2 == "Unlock" ) ||
( method1 == "RLock" && method2 == "RUnlock" ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , block . List [ i + 1 ] , "empty critical section" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . BlockStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
// cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't
// want to flag.
var cgoIdent = regexp . MustCompile ( ` ^_C(func|var)_.+$ ` )
2020-05-09 15:44:17 +02:00
func CheckIneffectiveCopy ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
2018-12-31 02:18:41 +01:00
if unary , ok := node . ( * ast . UnaryExpr ) ; ok {
if star , ok := unary . X . ( * ast . StarExpr ) ; ok && unary . Op == token . AND {
ident , ok := star . X . ( * ast . Ident )
if ! ok || ! cgoIdent . MatchString ( ident . Name ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , unary , "&*x will be simplified to x. It will not copy x." )
2018-12-31 02:18:41 +01:00
}
}
}
if star , ok := node . ( * ast . StarExpr ) ; ok {
if unary , ok := star . X . ( * ast . UnaryExpr ) ; ok && unary . Op == token . AND {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , star , "*&x will be simplified to x. It will not copy x." )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . UnaryExpr ) ( nil ) , ( * ast . StarExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckDiffSizeComparison ( pass * analysis . Pass ) ( interface { } , error ) {
ranges := pass . ResultOf [ valueRangesAnalyzer ] . ( map [ * ssa . Function ] vrp . Ranges )
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , b := range ssafn . Blocks {
for _ , ins := range b . Instrs {
binop , ok := ins . ( * ssa . BinOp )
if ! ok {
continue
}
if binop . Op != token . EQL && binop . Op != token . NEQ {
continue
}
_ , ok1 := binop . X . ( * ssa . Slice )
_ , ok2 := binop . Y . ( * ssa . Slice )
if ! ok1 && ! ok2 {
continue
}
2020-05-09 15:44:17 +02:00
r := ranges [ ssafn ]
2018-12-31 02:18:41 +01:00
r1 , ok1 := r . Get ( binop . X ) . ( vrp . StringInterval )
r2 , ok2 := r . Get ( binop . Y ) . ( vrp . StringInterval )
if ! ok1 || ! ok2 {
continue
}
if r1 . Length . Intersection ( r2 . Length ) . Empty ( ) {
2020-05-09 15:44:17 +02:00
pass . Reportf ( binop . Pos ( ) , "comparing strings of different sizes for equality will always return false" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckCanonicalHeaderKey ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( node ast . Node , push bool ) bool {
if ! push {
return false
}
2018-12-31 02:18:41 +01:00
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-05-09 15:44:17 +02:00
if IsOfType ( pass , op . X , "net/http.Header" ) {
2018-12-31 02:18:41 +01:00
return false
}
}
return true
}
op , ok := node . ( * ast . IndexExpr )
if ! ok {
return true
}
2020-05-09 15:44:17 +02:00
if ! IsOfType ( pass , op . X , "net/http.Header" ) {
2018-12-31 02:18:41 +01:00
return true
}
2020-05-09 15:44:17 +02:00
s , ok := ExprToString ( pass , op . Index )
2018-12-31 02:18:41 +01:00
if ! ok {
return true
}
if s == http . CanonicalHeaderKey ( s ) {
return true
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , op , "keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey" , s )
2018-12-31 02:18:41 +01:00
return true
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Nodes ( [ ] ast . Node { ( * ast . AssignStmt ) ( nil ) , ( * ast . IndexExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckBenchmarkN ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
assign := node . ( * ast . AssignStmt )
2018-12-31 02:18:41 +01:00
if len ( assign . Lhs ) != 1 || len ( assign . Rhs ) != 1 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
sel , ok := assign . Lhs [ 0 ] . ( * ast . SelectorExpr )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if sel . Sel . Name != "N" {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if ! IsOfType ( pass , sel . X , "*testing.B" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , assign , "should not assign to %s" , Render ( pass , sel ) )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . AssignStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckUnreadVariableValues ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
if IsExample ( ssafn ) {
continue
}
node := ssafn . Syntax ( )
if node == nil {
continue
}
2020-05-09 15:44:17 +02:00
if gen , ok := Generator ( pass , node . Pos ( ) ) ; ok && gen == facts . Goyacc {
// Don't flag unused values in code generated by goyacc.
// There may be hundreds of those due to the way the state
// machine is constructed.
continue
}
switchTags := map [ ssa . Value ] struct { } { }
ast . Inspect ( node , func ( node ast . Node ) bool {
s , ok := node . ( * ast . SwitchStmt )
if ! ok {
return true
}
v , _ := ssafn . ValueForExpr ( s . Tag )
switchTags [ v ] = struct { } { }
return true
} )
hasUse := func ( v ssa . Value ) bool {
if _ , ok := switchTags [ v ] ; ok {
return true
}
refs := v . Referrers ( )
if refs == nil {
// TODO investigate why refs can be nil
return true
}
return len ( FilterDebug ( * refs ) ) > 0
}
2018-12-31 02:18:41 +01: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
val , _ := ssafn . ValueForExpr ( assign . Rhs [ 0 ] )
if val == nil {
return true
}
refs := val . Referrers ( )
if refs == nil {
return true
}
for _ , ref := range * refs {
ex , ok := ref . ( * ssa . Extract )
if ! ok {
continue
}
2020-05-09 15:44:17 +02:00
if ! hasUse ( ex ) {
2018-12-31 02:18:41 +01:00
lhs := assign . Lhs [ ex . Index ]
if ident , ok := lhs . ( * ast . Ident ) ; ! ok || ok && ident . Name == "_" {
continue
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , lhs , "this value of %s is never used" , lhs )
2018-12-31 02:18:41 +01:00
}
}
return true
}
for i , lhs := range assign . Lhs {
rhs := assign . Rhs [ i ]
if ident , ok := lhs . ( * ast . Ident ) ; ! ok || ok && ident . Name == "_" {
continue
}
val , _ := ssafn . ValueForExpr ( rhs )
if val == nil {
continue
}
2020-05-09 15:44:17 +02:00
if ! hasUse ( val ) {
ReportNodef ( pass , lhs , "this value of %s is never used" , lhs )
2018-12-31 02:18:41 +01:00
}
}
return true
} )
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckPredeterminedBooleanExprs ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
ssabinop , ok := ins . ( * ssa . BinOp )
if ! ok {
continue
}
switch ssabinop . Op {
case token . GTR , token . LSS , token . EQL , token . NEQ , token . LEQ , token . GEQ :
default :
continue
}
xs , ok1 := consts ( ssabinop . X , nil , nil )
ys , ok2 := consts ( ssabinop . Y , nil , nil )
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
}
if constant . Compare ( x . Value , ssabinop . Op , y . Value ) {
trues ++
}
}
}
b := trues != 0
if trues == 0 || trues == len ( xs ) * len ( ys ) {
2020-05-09 15:44:17 +02:00
pass . Reportf ( ssabinop . Pos ( ) , "binary expression is always %t for all possible values (%s %s %s)" ,
2018-12-31 02:18:41 +01:00
b , xs , ssabinop . Op , ys )
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckNilMaps ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
mu , ok := ins . ( * ssa . MapUpdate )
if ! ok {
continue
}
c , ok := mu . Map . ( * ssa . Const )
if ! ok {
continue
}
if c . Value != nil {
continue
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( mu . Pos ( ) , "assignment to nil map" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckExtremeComparison ( pass * analysis . Pass ) ( interface { } , error ) {
2019-02-18 20:32:41 +01:00
isobj := func ( expr ast . Expr , name string ) bool {
sel , ok := expr . ( * ast . SelectorExpr )
if ! ok {
return false
}
2020-05-09 15:44:17 +02:00
return IsObject ( pass . TypesInfo . ObjectOf ( sel . Sel ) , name )
2019-02-18 20:32:41 +01:00
}
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
expr := node . ( * ast . BinaryExpr )
2020-05-09 15:44:17 +02:00
tx := pass . TypesInfo . TypeOf ( expr . X )
2018-12-31 02:18:41 +01:00
basic , ok := tx . Underlying ( ) . ( * types . Basic )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
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-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "no value of type %s is greater than %s" , basic , max )
2019-02-18 20:32:41 +01:00
}
if expr . Op == token . LEQ && isobj ( expr . Y , max ) ||
expr . Op == token . GEQ && isobj ( expr . X , max ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "every value of type %s is <= %s" , basic , max )
2019-02-18 20:32:41 +01:00
}
if ( basic . Info ( ) & types . IsUnsigned ) != 0 {
if ( expr . Op == token . LSS || expr . Op == token . LEQ ) && IsIntLiteral ( expr . Y , "0" ) ||
( expr . Op == token . GTR || expr . Op == token . GEQ ) && IsIntLiteral ( expr . X , "0" ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "no value of type %s is less than 0" , basic )
2019-02-18 20:32:41 +01:00
}
if expr . Op == token . GEQ && IsIntLiteral ( expr . Y , "0" ) ||
expr . Op == token . LEQ && IsIntLiteral ( expr . X , "0" ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "every value of type %s is >= 0" , basic )
2019-02-18 20:32:41 +01: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-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "no value of type %s is less than %s" , basic , min )
2019-02-18 20:32:41 +01:00
}
if expr . Op == token . GEQ && isobj ( expr . Y , min ) ||
expr . Op == token . LEQ && isobj ( expr . X , min ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , expr , "every value of type %s is >= %s" , basic , min )
2019-02-18 20:32:41 +01:00
}
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . BinaryExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
func consts ( val ssa . Value , out [ ] * ssa . Const , visitedPhis map [ string ] bool ) ( [ ] * ssa . Const , bool ) {
if visitedPhis == nil {
visitedPhis = map [ string ] bool { }
}
var ok bool
switch val := val . ( type ) {
case * ssa . Phi :
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
}
}
case * ssa . Const :
out = append ( out , val )
case * ssa . Convert :
out , ok = consts ( val . X , out , visitedPhis )
if ! ok {
return nil , false
}
default :
return nil , false
}
if len ( out ) < 2 {
return out , true
}
uniq := [ ] * ssa . Const { out [ 0 ] }
for _ , val := range out [ 1 : ] {
if val . Value == uniq [ len ( uniq ) - 1 ] . Value {
continue
}
uniq = append ( uniq , val )
}
return uniq , true
}
2020-05-09 15:44:17 +02:00
func CheckLoopCondition ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
fn := func ( node ast . Node ) bool {
loop , ok := node . ( * ast . ForStmt )
if ! ok {
return true
}
if loop . Init == nil || loop . Cond == nil || loop . Post == nil {
return true
}
init , ok := loop . Init . ( * ast . AssignStmt )
if ! ok || len ( init . Lhs ) != 1 || len ( init . Rhs ) != 1 {
return true
}
cond , ok := loop . Cond . ( * ast . BinaryExpr )
if ! ok {
return true
}
x , ok := cond . X . ( * ast . Ident )
if ! ok {
return true
}
lhs , ok := init . Lhs [ 0 ] . ( * ast . Ident )
if ! ok {
return true
}
if x . Obj != lhs . Obj {
return true
}
if _ , ok := loop . Post . ( * ast . IncDecStmt ) ; ! ok {
return true
}
v , isAddr := ssafn . ValueForExpr ( cond . X )
if v == nil || isAddr {
return true
}
switch v := v . ( type ) {
case * ssa . Phi :
ops := v . Operands ( nil )
if len ( ops ) != 2 {
return true
}
_ , ok := ( * ops [ 0 ] ) . ( * ssa . Const )
if ! ok {
return true
}
sigma , ok := ( * ops [ 1 ] ) . ( * ssa . Sigma )
if ! ok {
return true
}
if sigma . X != v {
return true
}
case * ssa . UnOp :
return true
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , cond , "variable in loop condition never changes" )
2018-12-31 02:18:41 +01:00
return true
}
Inspect ( ssafn . Syntax ( ) , fn )
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckArgOverwritten ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
fn := func ( node ast . Node ) bool {
var typ * ast . FuncType
var body * ast . BlockStmt
switch fn := node . ( type ) {
case * ast . FuncDecl :
typ = fn . Type
body = fn . Body
case * ast . FuncLit :
typ = fn . Type
body = fn . Body
}
if body == nil {
return true
}
if len ( typ . Params . List ) == 0 {
return true
}
for _ , field := range typ . Params . List {
for _ , arg := range field . Names {
2020-05-09 15:44:17 +02:00
obj := pass . TypesInfo . ObjectOf ( arg )
2018-12-31 02:18:41 +01:00
var ssaobj * ssa . Parameter
for _ , param := range ssafn . Params {
if param . Object ( ) == obj {
ssaobj = param
break
}
}
if ssaobj == nil {
continue
}
refs := ssaobj . Referrers ( )
if refs == nil {
continue
}
if len ( FilterDebug ( * refs ) ) != 0 {
continue
}
assigned := false
ast . Inspect ( body , func ( node ast . Node ) bool {
assign , ok := node . ( * ast . AssignStmt )
if ! ok {
return true
}
for _ , lhs := range assign . Lhs {
ident , ok := lhs . ( * ast . Ident )
if ! ok {
continue
}
2020-05-09 15:44:17 +02:00
if pass . TypesInfo . ObjectOf ( ident ) == obj {
2018-12-31 02:18:41 +01:00
assigned = true
return false
}
}
return true
} )
if assigned {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , arg , "argument %s is overwritten before first use" , arg )
2018-12-31 02:18:41 +01:00
}
}
}
return true
}
Inspect ( ssafn . Syntax ( ) , fn )
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckIneffectiveLoop ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
// 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.
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
2018-12-31 02:18:41 +01:00
var body * ast . BlockStmt
switch fn := node . ( type ) {
case * ast . FuncDecl :
body = fn . Body
case * ast . FuncLit :
body = fn . Body
default :
2019-04-22 12:59:42 +02:00
panic ( fmt . Sprintf ( "unreachable: %T" , node ) )
2018-12-31 02:18:41 +01:00
}
if body == nil {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
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 :
2020-05-09 15:44:17 +02:00
typ := pass . TypesInfo . TypeOf ( node . X )
2018-12-31 02:18:41 +01:00
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-05-09 15:44:17 +02:00
ReportNodef ( pass , unconditionalExit , "the surrounding loop is unconditionally terminated" )
2018-12-31 02:18:41 +01:00
}
return true
} )
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . FuncDecl ) ( nil ) , ( * ast . FuncLit ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckNilContext ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2018-12-31 02:18:41 +01:00
if len ( call . Args ) == 0 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if typ , ok := pass . TypesInfo . TypeOf ( call . Args [ 0 ] ) . ( * types . Basic ) ; ! ok || typ . Kind ( ) != types . UntypedNil {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
sig , ok := pass . TypesInfo . TypeOf ( call . Fun ) . ( * types . Signature )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if sig . Params ( ) . Len ( ) == 0 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if ! IsType ( sig . Params ( ) . At ( 0 ) . Type ( ) , "context.Context" ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ 0 ] ,
2018-12-31 02:18:41 +01:00
"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use" )
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckSeeker ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2018-12-31 02:18:41 +01:00
sel , ok := call . Fun . ( * ast . SelectorExpr )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if sel . Sel . Name != "Seek" {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if len ( call . Args ) != 2 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
arg0 , ok := call . Args [ Arg ( "(io.Seeker).Seek.offset" ) ] . ( * ast . SelectorExpr )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
switch arg0 . Sel . Name {
case "SeekStart" , "SeekCurrent" , "SeekEnd" :
default :
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
pkg , ok := arg0 . X . ( * ast . Ident )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if pkg . Name != "io" {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , call , "the first argument of io.Seeker is the offset, but an io.Seek* constant is being used instead" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckIneffectiveAppend ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
isAppend := func ( ins ssa . Value ) bool {
call , ok := ins . ( * ssa . Call )
if ! ok {
return false
}
if call . Call . IsInvoke ( ) {
return false
}
if builtin , ok := call . Call . Value . ( * ssa . Builtin ) ; ! ok || builtin . Name ( ) != "append" {
return false
}
return true
}
2020-05-09 15:44:17 +02:00
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
val , ok := ins . ( ssa . Value )
if ! ok || ! isAppend ( val ) {
continue
}
isUsed := false
visited := map [ ssa . Instruction ] bool { }
var walkRefs func ( refs [ ] ssa . Instruction )
walkRefs = func ( refs [ ] ssa . Instruction ) {
loop :
for _ , ref := range refs {
if visited [ ref ] {
continue
}
visited [ ref ] = true
if _ , ok := ref . ( * ssa . DebugRef ) ; ok {
continue
}
switch ref := ref . ( type ) {
case * ssa . Phi :
walkRefs ( * ref . Referrers ( ) )
case * ssa . Sigma :
walkRefs ( * ref . Referrers ( ) )
case ssa . Value :
if ! isAppend ( ref ) {
isUsed = true
} else {
walkRefs ( * ref . Referrers ( ) )
}
case ssa . Instruction :
isUsed = true
break loop
}
}
}
refs := val . Referrers ( )
if refs == nil {
continue
}
walkRefs ( * refs )
if ! isUsed {
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "this result of append is never used, except maybe in other appends" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckConcurrentTesting ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
gostmt , ok := ins . ( * ssa . Go )
if ! ok {
continue
}
var fn * ssa . Function
switch val := gostmt . Call . Value . ( type ) {
case * ssa . Function :
fn = val
case * ssa . MakeClosure :
fn = val . Fn . ( * ssa . Function )
default :
continue
}
if fn . Blocks == nil {
continue
}
for _ , block := range fn . Blocks {
for _ , ins := range block . Instrs {
call , ok := ins . ( * ssa . Call )
if ! ok {
continue
}
if call . Call . IsInvoke ( ) {
continue
}
callee := call . Call . StaticCallee ( )
if callee == nil {
continue
}
recv := callee . Signature . Recv ( )
if recv == nil {
continue
}
if ! IsType ( recv . Type ( ) , "*testing.common" ) {
continue
}
fn , ok := call . Call . StaticCallee ( ) . Object ( ) . ( * types . Func )
if ! ok {
continue
}
name := fn . Name ( )
switch name {
case "FailNow" , "Fatal" , "Fatalf" , "SkipNow" , "Skip" , "Skipf" :
default :
continue
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( gostmt . Pos ( ) , "the goroutine calls T.%s, which must be called in the same goroutine as the test" , name )
2018-12-31 02:18:41 +01:00
}
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func eachCall ( ssafn * ssa . Function , fn func ( caller * ssa . Function , site ssa . CallInstruction , callee * ssa . Function ) ) {
for _ , b := range ssafn . Blocks {
for _ , instr := range b . Instrs {
if site , ok := instr . ( ssa . CallInstruction ) ; ok {
if g := site . Common ( ) . StaticCallee ( ) ; g != nil {
fn ( ssafn , site , g )
2018-12-31 02:18:41 +01:00
}
}
}
}
}
2020-05-09 15:44:17 +02:00
func CheckCyclicFinalizer ( pass * analysis . Pass ) ( interface { } , error ) {
fn := func ( caller * ssa . Function , site ssa . CallInstruction , callee * ssa . Function ) {
if callee . RelString ( nil ) != "runtime.SetFinalizer" {
return
}
arg0 := site . Common ( ) . Args [ Arg ( "runtime.SetFinalizer.obj" ) ]
if iface , ok := arg0 . ( * ssa . MakeInterface ) ; ok {
arg0 = iface . X
}
unop , ok := arg0 . ( * ssa . UnOp )
if ! ok {
return
}
v , ok := unop . X . ( * ssa . Alloc )
if ! ok {
return
}
arg1 := site . Common ( ) . Args [ Arg ( "runtime.SetFinalizer.finalizer" ) ]
if iface , ok := arg1 . ( * ssa . MakeInterface ) ; ok {
arg1 = iface . X
}
mc , ok := arg1 . ( * ssa . MakeClosure )
if ! ok {
return
}
for _ , b := range mc . Bindings {
if b == v {
pos := lint . DisplayPosition ( pass . Fset , mc . Fn . Pos ( ) )
pass . Reportf ( site . Pos ( ) , "the finalizer closes over the object, preventing the finalizer from ever running (at %s)" , pos )
}
}
}
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
eachCall ( ssafn , fn )
}
return nil , nil
}
/ *
func CheckSliceOutOfBounds ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
ia , ok := ins . ( * ssa . IndexAddr )
if ! ok {
continue
}
if _ , ok := ia . X . Type ( ) . Underlying ( ) . ( * types . Slice ) ; ! ok {
continue
}
sr , ok1 := c . funcDescs . Get ( ssafn ) . Ranges [ ia . X ] . ( vrp . SliceInterval )
idxr , ok2 := c . funcDescs . Get ( ssafn ) . Ranges [ ia . Index ] . ( vrp . IntInterval )
if ! ok1 || ! ok2 || ! sr . IsKnown ( ) || ! idxr . IsKnown ( ) || sr . Length . Empty ( ) || idxr . Empty ( ) {
continue
}
if idxr . Lower . Cmp ( sr . Length . Upper ) >= 0 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , ia , "index out of bounds" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
* /
2018-12-31 02:18:41 +01:00
2020-05-09 15:44:17 +02:00
func CheckDeferLock ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
instrs := FilterDebug ( block . Instrs )
if len ( instrs ) < 2 {
continue
}
for i , ins := range instrs [ : len ( instrs ) - 1 ] {
call , ok := ins . ( * ssa . Call )
if ! ok {
continue
}
if ! IsCallTo ( call . Common ( ) , "(*sync.Mutex).Lock" ) && ! IsCallTo ( call . Common ( ) , "(*sync.RWMutex).RLock" ) {
continue
}
nins , ok := instrs [ i + 1 ] . ( * ssa . Defer )
if ! ok {
continue
}
if ! IsCallTo ( & nins . Call , "(*sync.Mutex).Lock" ) && ! IsCallTo ( & nins . Call , "(*sync.RWMutex).RLock" ) {
continue
}
if call . Common ( ) . Args [ 0 ] != nins . Call . Args [ 0 ] {
continue
}
name := shortCallName ( call . Common ( ) )
alt := ""
switch name {
case "Lock" :
alt = "Unlock"
case "RLock" :
alt = "RUnlock"
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( nins . Pos ( ) , "deferring %s right after having locked already; did you mean to defer %s?" , name , alt )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckNaNComparison ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
isNaN := func ( v ssa . Value ) bool {
call , ok := v . ( * ssa . Call )
if ! ok {
return false
}
return IsCallTo ( call . Common ( ) , "math.NaN" )
}
2020-05-09 15:44:17 +02:00
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
ins , ok := ins . ( * ssa . BinOp )
if ! ok {
continue
}
if isNaN ( ins . X ) || isNaN ( ins . Y ) {
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "no value is equal to NaN, not even NaN itself" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckInfiniteRecursion ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
eachCall ( ssafn , func ( caller * ssa . Function , site ssa . CallInstruction , callee * ssa . Function ) {
if callee != ssafn {
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
if _ , ok := site . ( * ssa . Go ) ; ok {
2018-12-31 02:18:41 +01:00
// Recursively spawning goroutines doesn't consume
// stack space infinitely, so don't flag it.
2020-05-09 15:44:17 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
block := site . Block ( )
2018-12-31 02:18:41 +01:00
canReturn := false
for _ , b := range ssafn . Blocks {
if block . Dominates ( b ) {
continue
}
if len ( b . Instrs ) == 0 {
continue
}
if _ , ok := b . Instrs [ len ( b . Instrs ) - 1 ] . ( * ssa . Return ) ; ok {
canReturn = true
break
}
}
if canReturn {
2020-05-09 15:44:17 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( site . Pos ( ) , "infinite recursive call" )
} )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
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 {
2019-02-18 20:32:41 +01:00
s := obj . Pkg ( ) . Path ( )
2018-12-31 02:18:41 +01:00
if s != "" {
name += s + "."
}
}
name += obj . Name ( )
return name
}
2020-05-09 15:44:17 +02:00
func isName ( pass * analysis . Pass , expr ast . Expr , name string ) bool {
2018-12-31 02:18:41 +01:00
var obj types . Object
switch expr := expr . ( type ) {
case * ast . Ident :
2020-05-09 15:44:17 +02:00
obj = pass . TypesInfo . ObjectOf ( expr )
2018-12-31 02:18:41 +01:00
case * ast . SelectorExpr :
2020-05-09 15:44:17 +02:00
obj = pass . TypesInfo . ObjectOf ( expr . Sel )
2018-12-31 02:18:41 +01:00
}
return objectName ( obj ) == name
}
2020-05-09 15:44:17 +02:00
func CheckLeakyTimeTick ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
if IsInMain ( pass , ssafn ) || IsInTest ( pass , ssafn ) {
2018-12-31 02:18:41 +01:00
continue
}
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
call , ok := ins . ( * ssa . Call )
if ! ok || ! IsCallTo ( call . Common ( ) , "time.Tick" ) {
continue
}
2020-05-09 15:44:17 +02:00
if ! functions . Terminates ( call . Parent ( ) ) {
2018-12-31 02:18:41 +01:00
continue
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( call . Pos ( ) , "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckDoubleNegation ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
unary1 := node . ( * ast . UnaryExpr )
2018-12-31 02:18:41 +01:00
unary2 , ok := unary1 . X . ( * ast . UnaryExpr )
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if unary1 . Op != token . NOT || unary2 . Op != token . NOT {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , unary1 , "negating a boolean twice has no effect; is this a typo?" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . UnaryExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
func hasSideEffects ( node ast . Node ) bool {
dynamic := false
ast . Inspect ( node , func ( node ast . Node ) bool {
switch node := node . ( type ) {
case * ast . CallExpr :
dynamic = true
return false
case * ast . UnaryExpr :
if node . Op == token . ARROW {
dynamic = true
return false
}
}
return true
} )
return dynamic
}
2020-05-09 15:44:17 +02:00
func CheckRepeatedIfElse ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
seen := map [ ast . Node ] bool { }
var collectConds func ( ifstmt * ast . IfStmt , inits [ ] ast . Stmt , conds [ ] ast . Expr ) ( [ ] ast . Stmt , [ ] ast . Expr )
collectConds = func ( ifstmt * ast . IfStmt , inits [ ] ast . Stmt , conds [ ] ast . Expr ) ( [ ] ast . Stmt , [ ] ast . Expr ) {
seen [ ifstmt ] = true
if ifstmt . Init != nil {
inits = append ( inits , ifstmt . Init )
}
conds = append ( conds , ifstmt . Cond )
if elsestmt , ok := ifstmt . Else . ( * ast . IfStmt ) ; ok {
return collectConds ( elsestmt , inits , conds )
}
return inits , conds
}
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
ifstmt := node . ( * ast . IfStmt )
2018-12-31 02:18:41 +01:00
if seen [ ifstmt ] {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
inits , conds := collectConds ( ifstmt , nil , nil )
if len ( inits ) > 0 {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
for _ , cond := range conds {
if hasSideEffects ( cond ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
}
counts := map [ string ] int { }
for _ , cond := range conds {
2020-05-09 15:44:17 +02:00
s := Render ( pass , cond )
2018-12-31 02:18:41 +01:00
counts [ s ] ++
if counts [ s ] == 2 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , cond , "this condition occurs multiple times in this if/else if chain" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . IfStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckSillyBitwiseOps ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
ins , ok := ins . ( * ssa . BinOp )
if ! ok {
continue
}
if c , ok := ins . Y . ( * ssa . 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
}
2020-05-09 15:44:17 +02:00
path , _ := astutil . PathEnclosingInterval ( File ( pass , ins ) , ins . Pos ( ) , ins . Pos ( ) )
2018-12-31 02:18:41 +01:00
if len ( path ) == 0 {
continue
}
if node , ok := path [ 0 ] . ( * ast . BinaryExpr ) ; ! ok || ! IsZero ( node . Y ) {
continue
}
switch ins . Op {
case token . AND :
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "x & 0 always equals 0" )
2018-12-31 02:18:41 +01:00
case token . OR , token . XOR :
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "x %s 0 always equals x" , ins . Op )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckNonOctalFileMode ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
call := node . ( * ast . CallExpr )
2020-05-09 15:44:17 +02:00
sig , ok := pass . TypesInfo . TypeOf ( call . Fun ) . ( * types . Signature )
2018-12-31 02:18:41 +01:00
if ! ok {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
n := sig . Params ( ) . Len ( )
var args [ ] int
for i := 0 ; i < n ; i ++ {
typ := sig . Params ( ) . At ( i ) . Type ( )
if IsType ( typ , "os.FileMode" ) {
args = append ( args , i )
}
}
for _ , i := range args {
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-05-09 15:44:17 +02:00
ReportNodef ( pass , call . Args [ i ] , "file mode '%s' evaluates to %#o; did you mean '0%s'?" , lit . Value , v , lit . Value )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckPureFunctions ( pass * analysis . Pass ) ( interface { } , error ) {
pure := pass . ResultOf [ facts . Purity ] . ( facts . PurityResult )
2018-12-31 02:18:41 +01:00
fnLoop :
2020-05-09 15:44:17 +02:00
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
if IsInTest ( pass , ssafn ) {
2018-12-31 02:18:41 +01:00
params := ssafn . Signature . Params ( )
for i := 0 ; i < params . Len ( ) ; i ++ {
param := params . At ( i )
if IsType ( param . Type ( ) , "*testing.B" ) {
// Ignore discarded pure functions in code related
// to benchmarks. Instead of matching BenchmarkFoo
// functions, we match any function accepting a
// *testing.B. Benchmarks sometimes call generic
// functions for doing the actual work, and
// checking for the parameter is a lot easier and
// faster than analyzing call trees.
continue fnLoop
}
}
}
for _ , b := range ssafn . Blocks {
for _ , ins := range b . Instrs {
ins , ok := ins . ( * ssa . Call )
if ! ok {
continue
}
refs := ins . Referrers ( )
if refs == nil || len ( FilterDebug ( * refs ) ) > 0 {
continue
}
callee := ins . Common ( ) . StaticCallee ( )
if callee == nil {
continue
}
2020-05-09 15:44:17 +02:00
if callee . Object ( ) == nil {
// TODO(dh): support anonymous functions
continue
}
if _ , ok := pure [ callee . Object ( ) . ( * types . Func ) ] ; ok {
pass . Reportf ( ins . Pos ( ) , "%s is a pure function but its return value is ignored" , callee . Name ( ) )
2018-12-31 02:18:41 +01:00
continue
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckDeprecated ( pass * analysis . Pass ) ( interface { } , error ) {
deprs := pass . ResultOf [ facts . Deprecated ] . ( facts . DeprecatedResult )
2018-12-31 02:18:41 +01:00
// Selectors can appear outside of function literals, e.g. when
// declaring package level variables.
2020-05-09 15:44:17 +02:00
var tfn types . Object
2018-12-31 02:18:41 +01:00
stack := 0
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node , push bool ) bool {
if ! push {
2018-12-31 02:18:41 +01:00
stack --
2020-05-09 15:44:17 +02:00
return false
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
stack ++
2018-12-31 02:18:41 +01:00
if stack == 1 {
2020-05-09 15:44:17 +02:00
tfn = nil
2018-12-31 02:18:41 +01:00
}
if fn , ok := node . ( * ast . FuncDecl ) ; ok {
2020-05-09 15:44:17 +02:00
tfn = pass . TypesInfo . ObjectOf ( fn . Name )
2018-12-31 02:18:41 +01:00
}
sel , ok := node . ( * ast . SelectorExpr )
if ! ok {
return true
}
2020-05-09 15:44:17 +02:00
obj := pass . TypesInfo . ObjectOf ( sel . Sel )
2018-12-31 02:18:41 +01:00
if obj . Pkg ( ) == nil {
return true
}
2020-05-09 15:44:17 +02:00
if pass . Pkg == obj . Pkg ( ) || obj . Pkg ( ) . Path ( ) + "_test" == pass . Pkg . Path ( ) {
2018-12-31 02:18:41 +01:00
// Don't flag stuff in our own package
return true
}
2020-05-09 15:44:17 +02:00
if depr , ok := deprs . Objects [ obj ] ; ok {
2018-12-31 02:18:41 +01:00
// 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-05-09 15:44:17 +02:00
minVersion := deprecated . Stdlib [ SelectorName ( pass , sel ) ] . AlternativeAvailableSince
if ! IsGoVersion ( pass , minVersion ) {
2018-12-31 02:18:41 +01:00
return true
}
2020-05-09 15:44:17 +02:00
if tfn != nil {
if _ , ok := deprs . Objects [ tfn ] ; ok {
2018-12-31 02:18:41 +01:00
// functions that are deprecated may use deprecated
// symbols
return true
}
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , sel , "%s is deprecated: %s" , Render ( pass , sel ) , depr . Msg )
2018-12-31 02:18:41 +01:00
return true
}
return true
}
2020-05-09 15:44:17 +02:00
imps := map [ string ] * types . Package { }
for _ , imp := range pass . Pkg . Imports ( ) {
imps [ imp . Path ( ) ] = imp
}
fn2 := func ( node ast . Node ) {
spec := node . ( * ast . ImportSpec )
p := spec . Path . Value
path := p [ 1 : len ( p ) - 1 ]
imp := imps [ path ]
if depr , ok := deprs . Packages [ imp ] ; ok {
ReportNodef ( pass , spec , "Package %s is deprecated: %s" , path , depr . Msg )
}
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Nodes ( nil , fn )
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . ImportSpec ) ( nil ) } , fn2 )
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func callChecker ( rules map [ string ] CallCheck ) func ( pass * analysis . Pass ) ( interface { } , error ) {
return func ( pass * analysis . Pass ) ( interface { } , error ) {
return checkCalls ( pass , rules )
2018-12-31 02:18:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
func checkCalls ( pass * analysis . Pass , rules map [ string ] CallCheck ) ( interface { } , error ) {
ranges := pass . ResultOf [ valueRangesAnalyzer ] . ( map [ * ssa . Function ] vrp . Ranges )
fn := func ( caller * ssa . Function , site ssa . CallInstruction , callee * ssa . Function ) {
obj , ok := callee . Object ( ) . ( * types . Func )
if ! ok {
return
}
2018-12-31 02:18:41 +01:00
2020-05-09 15:44:17 +02:00
r , ok := rules [ lint . FuncName ( obj ) ]
if ! ok {
return
}
var args [ ] * Argument
ssaargs := site . Common ( ) . Args
if callee . Signature . Recv ( ) != nil {
ssaargs = ssaargs [ 1 : ]
}
for _ , arg := range ssaargs {
if iarg , ok := arg . ( * ssa . MakeInterface ) ; ok {
arg = iarg . X
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
vr := ranges [ site . Parent ( ) ] [ arg ]
args = append ( args , & Argument { Value : Value { arg , vr } } )
}
call := & Call {
Pass : pass ,
Instr : site ,
Args : args ,
Parent : site . Parent ( ) ,
}
r ( call )
for idx , arg := range call . Args {
_ = idx
for _ , e := range arg . invalids {
// path, _ := astutil.PathEnclosingInterval(f.File, edge.Site.Pos(), edge.Site.Pos())
// if len(path) < 2 {
// continue
// }
// astcall, ok := path[0].(*ast.CallExpr)
// if !ok {
// continue
// }
// pass.Reportf(astcall.Args[idx], "%s", e)
2018-12-31 02:18:41 +01:00
2020-05-09 15:44:17 +02:00
pass . Reportf ( site . Pos ( ) , "%s" , e )
2018-12-31 02:18:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
for _ , e := range call . invalids {
pass . Reportf ( call . Instr . Common ( ) . Pos ( ) , "%s" , e )
}
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
eachCall ( ssafn , fn )
}
return nil , nil
2018-12-31 02:18:41 +01:00
}
func shortCallName ( call * ssa . CallCommon ) string {
if call . IsInvoke ( ) {
return ""
}
switch v := call . Value . ( type ) {
case * ssa . Function :
fn , ok := v . Object ( ) . ( * types . Func )
if ! ok {
return ""
}
return fn . Name ( )
case * ssa . Builtin :
return v . Name ( )
}
return ""
}
2020-05-09 15:44:17 +02:00
func CheckWriterBufferModified ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
// 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-05-09 15:44:17 +02:00
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
sig := ssafn . Signature
if ssafn . Name ( ) != "Write" || sig . Recv ( ) == nil || sig . Params ( ) . Len ( ) != 1 || sig . Results ( ) . Len ( ) != 2 {
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
}
if named , ok := sig . Results ( ) . At ( 1 ) . Type ( ) . ( * types . Named ) ; ! ok || ! IsType ( named , "error" ) {
continue
}
for _ , block := range ssafn . Blocks {
for _ , ins := range block . Instrs {
switch ins := ins . ( type ) {
case * ssa . Store :
addr , ok := ins . Addr . ( * ssa . IndexAddr )
if ! ok {
continue
}
if addr . X != ssafn . Params [ 1 ] {
continue
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "io.Writer.Write must not modify the provided buffer, not even temporarily" )
2018-12-31 02:18:41 +01:00
case * ssa . Call :
if ! IsCallTo ( ins . Common ( ) , "append" ) {
continue
}
if ins . Common ( ) . Args [ 0 ] != ssafn . Params [ 1 ] {
continue
}
2020-05-09 15:44:17 +02:00
pass . Reportf ( ins . Pos ( ) , "io.Writer.Write must not modify the provided buffer, not even temporarily" )
2018-12-31 02:18:41 +01:00
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
func loopedRegexp ( name string ) CallCheck {
return func ( call * Call ) {
if len ( extractConsts ( call . Args [ 0 ] . Value . Value ) ) == 0 {
return
}
2020-05-09 15:44:17 +02:00
if ! isInLoop ( call . Instr . Block ( ) ) {
2018-12-31 02:18:41 +01:00
return
}
call . Invalid ( fmt . Sprintf ( "calling %s in a loop has poor performance, consider using regexp.Compile" , name ) )
}
}
2020-05-09 15:44:17 +02:00
func CheckEmptyBranch ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , ssafn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
if ssafn . Syntax ( ) == nil {
continue
}
if IsExample ( ssafn ) {
continue
}
fn := func ( node ast . Node ) bool {
ifstmt , ok := node . ( * ast . IfStmt )
if ! ok {
return true
}
if ifstmt . Else != nil {
b , ok := ifstmt . Else . ( * ast . BlockStmt )
if ! ok || len ( b . List ) != 0 {
return true
}
2020-05-09 15:44:17 +02:00
ReportfFG ( pass , ifstmt . Else . Pos ( ) , "empty branch" )
2018-12-31 02:18:41 +01:00
}
if len ( ifstmt . Body . List ) != 0 {
return true
}
2020-05-09 15:44:17 +02:00
ReportfFG ( pass , ifstmt . Pos ( ) , "empty branch" )
2018-12-31 02:18:41 +01:00
return true
}
Inspect ( ssafn . Syntax ( ) , fn )
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckMapBytesKey ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , fn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , b := range fn . Blocks {
insLoop :
for _ , ins := range b . Instrs {
// find []byte -> string conversions
conv , ok := ins . ( * ssa . Convert )
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 ) {
case * ssa . DebugRef :
if _ , ok := ref . Expr . ( * ast . Ident ) ; ! ok {
// the string seems to be used somewhere
// unexpected; the default branch should
// catch this already, but be safe
continue insLoop
} else {
ident = true
}
case * ssa . Lookup :
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-05-09 15:44:17 +02:00
pass . Reportf ( conv . Pos ( ) , "m[string(key)] would be more efficient than k := string(key); m[k]" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckRangeStringRunes ( pass * analysis . Pass ) ( interface { } , error ) {
return sharedcheck . CheckRangeStringRunes ( pass )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckSelfAssignment ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
assign := node . ( * ast . AssignStmt )
2018-12-31 02:18:41 +01:00
if assign . Tok != token . ASSIGN || len ( assign . Lhs ) != len ( assign . Rhs ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
for i , stmt := range assign . Lhs {
2020-05-09 15:44:17 +02:00
rlh := Render ( pass , stmt )
rrh := Render ( pass , assign . Rhs [ i ] )
2018-12-31 02:18:41 +01:00
if rlh == rrh {
2020-05-09 15:44:17 +02:00
ReportfFG ( pass , assign . Pos ( ) , "self-assignment of %s to %s" , rrh , rlh )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . AssignStmt ) ( nil ) } , fn )
return nil , nil
2018-12-31 02:18:41 +01:00
}
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
}
2020-05-09 15:44:17 +02:00
func CheckDuplicateBuildConstraints ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , f := range pass . Files {
2018-12-31 02:18:41 +01:00
constraints := buildTags ( f )
for i , constraint1 := range constraints {
for j , constraint2 := range constraints {
if i >= j {
continue
}
if buildTagsIdentical ( constraint1 , constraint2 ) {
2020-05-09 15:44:17 +02:00
ReportfFG ( pass , f . Pos ( ) , "identical build constraints %q and %q" ,
2018-12-31 02:18:41 +01:00
strings . Join ( constraint1 , " " ) ,
strings . Join ( constraint2 , " " ) )
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckSillyRegexp ( pass * analysis . Pass ) ( interface { } , error ) {
2018-12-31 02:18:41 +01:00
// We could use the rule checking engine for this, but the
// arguments aren't really invalid.
2020-05-09 15:44:17 +02:00
for _ , fn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2018-12-31 02:18:41 +01:00
for _ , b := range fn . Blocks {
for _ , ins := range b . Instrs {
call , ok := ins . ( * ssa . Call )
if ! ok {
continue
}
switch CallName ( call . Common ( ) ) {
case "regexp.MustCompile" , "regexp.Compile" , "regexp.Match" , "regexp.MatchReader" , "regexp.MatchString" :
default :
continue
}
c , ok := call . Common ( ) . Args [ 0 ] . ( * ssa . Const )
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-05-09 15:44:17 +02:00
pass . Reportf ( call . Pos ( ) , "regular expression does not contain any meta characters" )
2018-12-31 02:18:41 +01:00
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckMissingEnumTypesInDeclaration ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
decl := node . ( * ast . GenDecl )
2018-12-31 02:18:41 +01:00
if ! decl . Lparen . IsValid ( ) {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
if decl . Tok != token . CONST {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
2020-05-09 15:44:17 +02:00
groups := GroupSpecs ( pass . Fset , decl . Specs )
2019-02-18 20:32:41 +01: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-05-09 15:44:17 +02:00
ReportNodef ( pass , group [ 0 ] , "only the first constant in this group has an explicit type" )
2019-02-18 20:32:41 +01:00
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . GenDecl ) ( nil ) } , fn )
return nil , nil
2019-02-18 20:32:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckTimerResetReturnValue ( pass * analysis . Pass ) ( interface { } , error ) {
for _ , fn := range pass . ResultOf [ buildssa . Analyzer ] . ( * buildssa . SSA ) . SrcFuncs {
2019-02-18 20:32:41 +01:00
for _ , block := range fn . Blocks {
for _ , ins := range block . Instrs {
call , ok := ins . ( * ssa . Call )
if ! ok {
continue
}
if ! IsCallTo ( call . Common ( ) , "(*time.Timer).Reset" ) {
continue
}
refs := call . Referrers ( )
if refs == nil {
continue
}
for _ , ref := range FilterDebug ( * refs ) {
ifstmt , ok := ref . ( * ssa . If )
if ! ok {
continue
}
found := false
for _ , succ := range ifstmt . Block ( ) . Succs {
if len ( succ . Preds ) != 1 {
// Merge point, not a branch in the
// syntactical sense.
// FIXME(dh): this is broken for if
// statements a la "if x || y"
continue
}
ssautil . Walk ( succ , func ( b * ssa . BasicBlock ) bool {
if ! succ . Dominates ( b ) {
// We've reached the end of the branch
return false
}
for _ , ins := range b . Instrs {
// TODO(dh): we should check that
// we're receiving from the channel of
// a time.Timer to further reduce
// false positives. Not a key
// priority, considering the rarity of
// Reset and the tiny likeliness of a
// false positive
if ins , ok := ins . ( * ssa . UnOp ) ; ok && ins . Op == token . ARROW && IsType ( ins . X . Type ( ) , "<-chan time.Time" ) {
found = true
return false
}
}
return true
} )
}
if found {
2020-05-09 15:44:17 +02:00
pass . Reportf ( call . Pos ( ) , "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-02-18 20:32:41 +01:00
}
}
}
}
}
2020-05-09 15:44:17 +02:00
return nil , nil
2019-02-18 20:32:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckToLowerToUpperComparison ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
binExpr := node . ( * ast . BinaryExpr )
2019-02-18 20:32:41 +01:00
var negative bool
switch binExpr . Op {
case token . EQL :
negative = false
case token . NEQ :
negative = true
default :
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
const (
lo = "strings.ToLower"
up = "strings.ToUpper"
)
var call string
2020-05-09 15:44:17 +02:00
if IsCallToAST ( pass , binExpr . X , lo ) && IsCallToAST ( pass , binExpr . Y , lo ) {
2019-02-18 20:32:41 +01:00
call = lo
2020-05-09 15:44:17 +02:00
} else if IsCallToAST ( pass , binExpr . X , up ) && IsCallToAST ( pass , binExpr . Y , up ) {
2019-02-18 20:32:41 +01:00
call = up
} else {
2019-04-22 12:59:42 +02:00
return
2018-12-31 02:18:41 +01:00
}
2019-02-18 20:32:41 +01:00
bang := ""
if negative {
bang = "!"
}
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , binExpr , "should use %sstrings.EqualFold(a, b) instead of %s(a) %s %s(b)" , bang , call , binExpr . Op , call )
2019-02-18 20:32:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . BinaryExpr ) ( nil ) } , fn )
return nil , nil
2019-02-18 20:32:41 +01:00
}
2020-05-09 15:44:17 +02:00
func CheckUnreachableTypeCases ( pass * analysis . Pass ) ( interface { } , error ) {
2019-02-18 20:32:41 +01:00
// 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
2018-12-31 02:18:41 +01:00
}
}
2019-02-18 20:32:41 +01:00
}
return nil , nil , false
}
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
tsStmt := node . ( * ast . TypeSwitchStmt )
2019-02-18 20:32:41 +01:00
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 {
2018-12-31 02:18:41 +01:00
continue
}
2019-02-18 20:32:41 +01:00
Ts := make ( [ ] types . Type , len ( cc . List ) )
for i , expr := range cc . List {
2020-05-09 15:44:17 +02:00
Ts [ i ] = pass . TypesInfo . TypeOf ( expr )
2019-02-18 20:32:41 +01:00
}
ccs = append ( ccs , ccAndTypes { cc : cc , types : Ts } )
}
if len ( ccs ) <= 1 {
// Zero or one case clauses, nothing to check.
2019-04-22 12:59:42 +02:00
return
2019-02-18 20:32:41 +01:00
}
// 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-05-09 15:44:17 +02:00
ReportNodef ( pass , next . cc , "unreachable case clause: %s will always match before %s" , T . String ( ) , V . String ( ) )
2019-02-18 20:32:41 +01:00
}
2018-12-31 02:18:41 +01:00
}
}
2019-04-22 12:59:42 +02:00
}
2019-02-18 20:32:41 +01:00
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . TypeSwitchStmt ) ( nil ) } , fn )
return nil , nil
2019-04-22 12:59:42 +02:00
}
2020-05-09 15:44:17 +02:00
func CheckSingleArgAppend ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02:00
fn := func ( node ast . Node ) {
2020-05-09 15:44:17 +02:00
if ! IsCallToAST ( pass , node , "append" ) {
2019-04-22 12:59:42 +02:00
return
}
call := node . ( * ast . CallExpr )
if len ( call . Args ) != 1 {
return
}
2020-05-09 15:44:17 +02:00
ReportfFG ( pass , call . Pos ( ) , "x = append(y) is equivalent to x = y" )
2018-12-31 02:18:41 +01:00
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . CallExpr ) ( nil ) } , fn )
return nil , nil
2019-04-22 12:59:42 +02:00
}
2019-02-18 20:32:41 +01:00
2020-05-09 15:44:17 +02:00
func CheckStructTags ( pass * analysis . Pass ) ( interface { } , error ) {
2019-04-22 12:59:42 +02: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-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "unparseable struct tag: %s" , err )
2019-04-22 12:59:42 +02:00
continue
}
for k , v := range tags {
if len ( v ) > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "duplicate struct tag %q" , k )
2019-04-22 12:59:42 +02:00
continue
}
switch k {
case "json" :
2020-05-09 15:44:17 +02:00
checkJSONTag ( pass , field , v [ 0 ] )
2019-04-22 12:59:42 +02:00
case "xml" :
2020-05-09 15:44:17 +02:00
checkXMLTag ( pass , field , v [ 0 ] )
2019-04-22 12:59:42 +02:00
}
}
}
}
2020-05-09 15:44:17 +02:00
pass . ResultOf [ inspect . Analyzer ] . ( * inspector . Inspector ) . Preorder ( [ ] ast . Node { ( * ast . StructType ) ( nil ) } , fn )
return nil , nil
2019-04-22 12:59:42 +02:00
}
2020-05-09 15:44:17 +02:00
func checkJSONTag ( pass * analysis . Pass , field * ast . Field , tag string ) {
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
2019-04-22 12:59:42 +02:00
if len ( tag ) == 0 {
}
fields := strings . Split ( tag , "," )
for _ , r := range fields [ 0 ] {
if ! unicode . IsLetter ( r ) && ! unicode . IsDigit ( r ) && ! strings . ContainsRune ( "!#$%&()*+-./:<=>?@[]^_{|}~ " , r ) {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "invalid JSON field name %q" , fields [ 0 ] )
2019-04-22 12:59:42 +02: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-05-09 15:44:17 +02:00
T := Dereference ( pass . TypesInfo . TypeOf ( field . Type ) . Underlying ( ) ) . Underlying ( )
2019-04-22 12:59:42 +02:00
basic , ok := T . ( * types . Basic )
if ! ok || ( basic . Info ( ) & ( types . IsBoolean | types . IsInteger | types . IsFloat | types . IsString ) ) == 0 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "the JSON string option only applies to fields of type string, floating point, integer or bool, or pointers to those" )
2019-04-22 12:59:42 +02:00
}
case "inline" :
ci ++
default :
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "unknown JSON option %q" , s )
2019-04-22 12:59:42 +02:00
}
}
if co > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , ` duplicate JSON option "omitempty" ` )
2019-04-22 12:59:42 +02:00
}
if cs > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , ` duplicate JSON option "string" ` )
2019-04-22 12:59:42 +02:00
}
if ci > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , ` duplicate JSON option "inline" ` )
2019-04-22 12:59:42 +02:00
}
}
2020-05-09 15:44:17 +02:00
func checkXMLTag ( pass * analysis . Pass , field * ast . Field , tag string ) {
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
2019-04-22 12:59:42 +02:00
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-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "unknown XML option %q" , s )
2019-04-22 12:59:42 +02:00
}
}
for k , v := range counts {
if v > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "duplicate XML option %q" , k )
2019-04-22 12:59:42 +02:00
}
}
if len ( exclusives ) > 1 {
2020-05-09 15:44:17 +02:00
ReportNodef ( pass , field . Tag , "XML options %s are mutually exclusive" , strings . Join ( exclusives , " and " ) )
2018-12-31 02:18:41 +01:00
}
}