Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.0.1-2020.1.5 / staticcheck / lint.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.0.1-2020.1.5/staticcheck/lint.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/honnef.co/go/tools@v0.0.1-2020.1.5/staticcheck/lint.go
new file mode 100644 (file)
index 0000000..22b6bbc
--- /dev/null
@@ -0,0 +1,3829 @@
+// Package staticcheck contains a linter for Go source code.
+package staticcheck // import "honnef.co/go/tools/staticcheck"
+
+import (
+       "fmt"
+       "go/ast"
+       "go/constant"
+       "go/token"
+       "go/types"
+       htmltemplate "html/template"
+       "net/http"
+       "reflect"
+       "regexp"
+       "regexp/syntax"
+       "sort"
+       "strconv"
+       "strings"
+       texttemplate "text/template"
+       "unicode"
+
+       . "honnef.co/go/tools/arg"
+       "honnef.co/go/tools/code"
+       "honnef.co/go/tools/deprecated"
+       "honnef.co/go/tools/edit"
+       "honnef.co/go/tools/facts"
+       "honnef.co/go/tools/functions"
+       "honnef.co/go/tools/internal/passes/buildir"
+       "honnef.co/go/tools/internal/sharedcheck"
+       "honnef.co/go/tools/ir"
+       "honnef.co/go/tools/ir/irutil"
+       "honnef.co/go/tools/lint"
+       . "honnef.co/go/tools/lint/lintdsl"
+       "honnef.co/go/tools/pattern"
+       "honnef.co/go/tools/printf"
+       "honnef.co/go/tools/report"
+
+       "golang.org/x/tools/go/analysis"
+       "golang.org/x/tools/go/analysis/passes/inspect"
+       "golang.org/x/tools/go/ast/astutil"
+       "golang.org/x/tools/go/ast/inspector"
+       "golang.org/x/tools/go/types/typeutil"
+)
+
+func checkSortSlice(call *Call) {
+       c := call.Instr.Common().StaticCallee()
+       arg := call.Args[0]
+
+       T := arg.Value.Value.Type().Underlying()
+       switch T.(type) {
+       case *types.Interface:
+               // we don't know.
+               // TODO(dh): if the value is a phi node we can look at its edges
+               if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value == nil {
+                       // literal nil, e.g. sort.Sort(nil, ...)
+                       arg.Invalid(fmt.Sprintf("cannot call %s on nil literal", c))
+               }
+       case *types.Slice:
+               // this is fine
+       default:
+               // this is not fine
+               arg.Invalid(fmt.Sprintf("%s must only be called on slices, was called on %s", c, T))
+       }
+}
+
+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", code.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) {
+                       arg := call.Args[Arg("time.Parse.layout")]
+                       err := ValidateTimeLayout(arg.Value)
+                       if err != nil {
+                               arg.Invalid(err.Error())
+                       }
+               },
+       }
+
+       checkEncodingBinaryRules = map[string]CallCheck{
+               "encoding/binary.Write": func(call *Call) {
+                       arg := call.Args[Arg("encoding/binary.Write.data")]
+                       if !CanBinaryMarshal(call.Pass, arg.Value) {
+                               arg.Invalid(fmt.Sprintf("value of type %s cannot be used with binary.Write", arg.Value.Value.Type()))
+                       }
+               },
+       }
+
+       checkURLsRules = map[string]CallCheck{
+               "net/url.Parse": func(call *Call) {
+                       arg := call.Args[Arg("net/url.Parse.rawurl")]
+                       err := ValidateURL(arg.Value)
+                       if err != nil {
+                               arg.Invalid(err.Error())
+                       }
+               },
+       }
+
+       checkSyncPoolValueRules = map[string]CallCheck{
+               "(*sync.Pool).Put": func(call *Call) {
+                       arg := call.Args[Arg("(*sync.Pool).Put.x")]
+                       typ := arg.Value.Value.Type()
+                       if !code.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) {
+                       arg := call.Args[Arg("os/signal.Notify.c")]
+                       if UnbufferedChannel(arg.Value) {
+                               arg.Invalid("the channel used with signal.Notify should be buffered")
+                       }
+               },
+       }
+
+       checkMathIntRules = map[string]CallCheck{
+               "math.Ceil":  pointlessIntMath,
+               "math.Floor": pointlessIntMath,
+               "math.IsNaN": pointlessIntMath,
+               "math.Trunc": pointlessIntMath,
+               "math.IsInf": pointlessIntMath,
+       }
+
+       checkStringsReplaceZeroRules = map[string]CallCheck{
+               "strings.Replace": RepeatZeroTimes("strings.Replace", 3),
+               "bytes.Replace":   RepeatZeroTimes("bytes.Replace", 3),
+       }
+
+       checkListenAddressRules = map[string]CallCheck{
+               "net/http.ListenAndServe":    checkValidHostPort(0),
+               "net/http.ListenAndServeTLS": checkValidHostPort(0),
+       }
+
+       checkBytesEqualIPRules = map[string]CallCheck{
+               "bytes.Equal": func(call *Call) {
+                       if ConvertedFrom(call.Args[Arg("bytes.Equal.a")].Value, "net.IP") &&
+                               ConvertedFrom(call.Args[Arg("bytes.Equal.b")].Value, "net.IP") {
+                               call.Invalid("use net.IP.Equal to compare net.IPs, not bytes.Equal")
+                       }
+               },
+       }
+
+       checkRegexpMatchLoopRules = map[string]CallCheck{
+               "regexp.Match":       loopedRegexp("regexp.Match"),
+               "regexp.MatchReader": loopedRegexp("regexp.MatchReader"),
+               "regexp.MatchString": loopedRegexp("regexp.MatchString"),
+       }
+
+       checkNoopMarshal = map[string]CallCheck{
+               // TODO(dh): should we really flag XML? Even an empty struct
+               // produces a non-zero amount of data, namely its type name.
+               // Let's see if we encounter any false positives.
+               //
+               // Also, should we flag gob?
+               "encoding/json.Marshal":           checkNoopMarshalImpl(Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"),
+               "encoding/xml.Marshal":            checkNoopMarshalImpl(Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"),
+               "(*encoding/json.Encoder).Encode": checkNoopMarshalImpl(Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"),
+               "(*encoding/xml.Encoder).Encode":  checkNoopMarshalImpl(Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"),
+
+               "encoding/json.Unmarshal":         checkNoopMarshalImpl(Arg("json.Unmarshal.v"), "UnmarshalJSON", "UnmarshalText"),
+               "encoding/xml.Unmarshal":          checkNoopMarshalImpl(Arg("xml.Unmarshal.v"), "UnmarshalXML", "UnmarshalText"),
+               "(*encoding/json.Decoder).Decode": checkNoopMarshalImpl(Arg("(*encoding/json.Decoder).Decode.v"), "UnmarshalJSON", "UnmarshalText"),
+               "(*encoding/xml.Decoder).Decode":  checkNoopMarshalImpl(Arg("(*encoding/xml.Decoder).Decode.v"), "UnmarshalXML", "UnmarshalText"),
+       }
+
+       checkUnsupportedMarshal = map[string]CallCheck{
+               "encoding/json.Marshal":           checkUnsupportedMarshalImpl(Arg("json.Marshal.v"), "json", "MarshalJSON", "MarshalText"),
+               "encoding/xml.Marshal":            checkUnsupportedMarshalImpl(Arg("xml.Marshal.v"), "xml", "MarshalXML", "MarshalText"),
+               "(*encoding/json.Encoder).Encode": checkUnsupportedMarshalImpl(Arg("(*encoding/json.Encoder).Encode.v"), "json", "MarshalJSON", "MarshalText"),
+               "(*encoding/xml.Encoder).Encode":  checkUnsupportedMarshalImpl(Arg("(*encoding/xml.Encoder).Encode.v"), "xml", "MarshalXML", "MarshalText"),
+       }
+
+       checkAtomicAlignment = map[string]CallCheck{
+               "sync/atomic.AddInt64":             checkAtomicAlignmentImpl,
+               "sync/atomic.AddUint64":            checkAtomicAlignmentImpl,
+               "sync/atomic.CompareAndSwapInt64":  checkAtomicAlignmentImpl,
+               "sync/atomic.CompareAndSwapUint64": checkAtomicAlignmentImpl,
+               "sync/atomic.LoadInt64":            checkAtomicAlignmentImpl,
+               "sync/atomic.LoadUint64":           checkAtomicAlignmentImpl,
+               "sync/atomic.StoreInt64":           checkAtomicAlignmentImpl,
+               "sync/atomic.StoreUint64":          checkAtomicAlignmentImpl,
+               "sync/atomic.SwapInt64":            checkAtomicAlignmentImpl,
+               "sync/atomic.SwapUint64":           checkAtomicAlignmentImpl,
+       }
+
+       // TODO(dh): detect printf wrappers
+       checkPrintfRules = map[string]CallCheck{
+               "fmt.Errorf":                  func(call *Call) { checkPrintfCall(call, 0, 1) },
+               "fmt.Printf":                  func(call *Call) { checkPrintfCall(call, 0, 1) },
+               "fmt.Sprintf":                 func(call *Call) { checkPrintfCall(call, 0, 1) },
+               "fmt.Fprintf":                 func(call *Call) { checkPrintfCall(call, 1, 2) },
+               "golang.org/x/xerrors.Errorf": func(call *Call) { checkPrintfCall(call, 0, 1) },
+       }
+
+       checkSortSliceRules = map[string]CallCheck{
+               "sort.Slice":         checkSortSlice,
+               "sort.SliceIsSorted": checkSortSlice,
+               "sort.SliceStable":   checkSortSlice,
+       }
+
+       checkWithValueKeyRules = map[string]CallCheck{
+               "context.WithValue": checkWithValueKey,
+       }
+)
+
+func checkPrintfCall(call *Call, fIdx, vIdx int) {
+       f := call.Args[fIdx]
+       var args []ir.Value
+       switch v := call.Args[vIdx].Value.Value.(type) {
+       case *ir.Slice:
+               var ok bool
+               args, ok = irutil.Vararg(v)
+               if !ok {
+                       // We don't know what the actual arguments to the function are
+                       return
+               }
+       case *ir.Const:
+               // nil, i.e. no arguments
+       default:
+               // We don't know what the actual arguments to the function are
+               return
+       }
+       checkPrintfCallImpl(f, f.Value.Value, args)
+}
+
+type verbFlag int
+
+const (
+       isInt verbFlag = 1 << iota
+       isBool
+       isFP
+       isString
+       isPointer
+       // Verbs that accept "pseudo pointers" will sometimes dereference
+       // non-nil pointers. For example, %x on a non-nil *struct will print the
+       // individual fields, but on a nil pointer it will print the address.
+       isPseudoPointer
+       isSlice
+       isAny
+       noRecurse
+)
+
+var verbs = [...]verbFlag{
+       'b': isPseudoPointer | isInt | isFP,
+       'c': isInt,
+       'd': isPseudoPointer | isInt,
+       'e': isFP,
+       'E': isFP,
+       'f': isFP,
+       'F': isFP,
+       'g': isFP,
+       'G': isFP,
+       'o': isPseudoPointer | isInt,
+       'O': isPseudoPointer | isInt,
+       'p': isSlice | isPointer | noRecurse,
+       'q': isInt | isString,
+       's': isString,
+       't': isBool,
+       'T': isAny,
+       'U': isInt,
+       'v': isAny,
+       'X': isPseudoPointer | isInt | isFP | isString,
+       'x': isPseudoPointer | isInt | isFP | isString,
+}
+
+func checkPrintfCallImpl(carg *Argument, f ir.Value, args []ir.Value) {
+       var msCache *typeutil.MethodSetCache
+       if f.Parent() != nil {
+               msCache = &f.Parent().Prog.MethodSets
+       }
+
+       elem := func(T types.Type, verb rune) ([]types.Type, bool) {
+               if verbs[verb]&noRecurse != 0 {
+                       return []types.Type{T}, false
+               }
+               switch T := T.(type) {
+               case *types.Slice:
+                       if verbs[verb]&isSlice != 0 {
+                               return []types.Type{T}, false
+                       }
+                       if verbs[verb]&isString != 0 && code.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 !code.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 !code.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
+               }
+
+               ms := msCache.MethodSet(T)
+               if isFormatter(T, ms) {
+                       // the value is responsible for formatting itself
+                       return true
+               }
+
+               if flags&isString != 0 && (isStringer(T, ms) || isError(T, ms)) {
+                       // Check for stringer early because we're about to dereference
+                       return true
+               }
+
+               T = T.Underlying()
+               if flags&(isPointer|isPseudoPointer) == 0 && top {
+                       T = code.Dereference(T)
+               }
+               if flags&isPseudoPointer != 0 && top {
+                       t := code.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 && (code.IsType(T, "[]byte") || isStringer(T, ms) || isError(T, ms)) {
+                       return true
+               }
+
+               if flags&isPointer != 0 && code.IsPointerLike(T) {
+                       return true
+               }
+               if flags&isPseudoPointer != 0 {
+                       switch U := T.Underlying().(type) {
+                       case *types.Pointer:
+                               if !top {
+                                       return true
+                               }
+
+                               if _, ok := U.Elem().Underlying().(*types.Struct); !ok {
+                                       // TODO(dh): can this condition ever be false? For
+                                       // *T, if T is a struct, we'll already have
+                                       // dereferenced it, meaning the *types.Pointer
+                                       // branch couldn't have been taken. For T that
+                                       // aren't structs, this condition will always
+                                       // evaluate to true.
+                                       return true
+                               }
+                       case *types.Chan, *types.Signature:
+                               // Channels and functions are always treated as
+                               // pointers and never recursed into.
+                               return true
+                       case *types.Basic:
+                               if U.Kind() == types.UnsafePointer {
+                                       return true
+                               }
+                       case *types.Interface:
+                               // we will already have bailed if the type is an
+                               // interface.
+                               panic("unreachable")
+                       default:
+                               // other pointer-like types, such as maps or slices,
+                               // will be printed element-wise.
+                       }
+               }
+
+               if flags&isSlice != 0 {
+                       if _, ok := T.(*types.Slice); ok {
+                               return true
+                       }
+               }
+
+               if flags&isAny != 0 {
+                       return true
+               }
+
+               elems, ok := elem(T.Underlying(), verb)
+               if !ok {
+                       return false
+               }
+               for _, elem := range elems {
+                       if !checkType(verb, elem, false) {
+                               return false
+                       }
+               }
+
+               return true
+       }
+
+       k, ok := f.(*ir.Const)
+       if !ok {
+               return
+       }
+       actions, err := printf.Parse(constant.StringVal(k.Value))
+       if err != nil {
+               carg.Invalid("couldn't parse format string")
+               return
+       }
+
+       ptr := 1
+       hasExplicit := false
+
+       checkStar := func(verb printf.Verb, star printf.Argument) bool {
+               if star, ok := star.(printf.Star); ok {
+                       idx := 0
+                       if star.Index == -1 {
+                               idx = ptr
+                               ptr++
+                       } else {
+                               hasExplicit = true
+                               idx = star.Index
+                               ptr = star.Index + 1
+                       }
+                       if idx == 0 {
+                               carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
+                               return false
+                       }
+                       if idx > len(args) {
+                               carg.Invalid(
+                                       fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
+                                               verb.Raw, idx, len(args)))
+                               return false
+                       }
+                       if arg, ok := args[idx-1].(*ir.MakeInterface); ok {
+                               if !isInfo(arg.X.Type(), types.IsInteger) {
+                                       carg.Invalid(fmt.Sprintf("Printf format %s reads non-int arg #%d as argument of *", verb.Raw, idx))
+                               }
+                       }
+               }
+               return true
+       }
+
+       // We only report one problem per format string. Making a
+       // mistake with an index tends to invalidate all future
+       // implicit indices.
+       for _, action := range actions {
+               verb, ok := action.(printf.Verb)
+               if !ok {
+                       continue
+               }
+
+               if !checkStar(verb, verb.Width) || !checkStar(verb, verb.Precision) {
+                       return
+               }
+
+               off := ptr
+               if verb.Value != -1 {
+                       hasExplicit = true
+                       off = verb.Value
+               }
+               if off > len(args) {
+                       carg.Invalid(
+                               fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
+                                       verb.Raw, off, len(args)))
+                       return
+               } else if verb.Value == 0 && verb.Letter != '%' {
+                       carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
+                       return
+               } else if off != 0 {
+                       arg, ok := args[off-1].(*ir.MakeInterface)
+                       if ok {
+                               if !checkType(verb.Letter, arg.X.Type(), true) {
+                                       carg.Invalid(fmt.Sprintf("Printf format %s has arg #%d of wrong type %s",
+                                               verb.Raw, ptr, args[ptr-1].(*ir.MakeInterface).X.Type()))
+                                       return
+                               }
+                       }
+               }
+
+               switch verb.Value {
+               case -1:
+                       // Consume next argument
+                       ptr++
+               case 0:
+                       // Don't consume any arguments
+               default:
+                       ptr = verb.Value + 1
+               }
+       }
+
+       if !hasExplicit && ptr <= len(args) {
+               carg.Invalid(fmt.Sprintf("Printf call needs %d args but has %d args", ptr-1, len(args)))
+       }
+}
+
+func checkAtomicAlignmentImpl(call *Call) {
+       sizes := call.Pass.TypesSizes
+       if sizes.Sizeof(types.Typ[types.Uintptr]) != 4 {
+               // Not running on a 32-bit platform
+               return
+       }
+       v, ok := call.Args[0].Value.Value.(*ir.FieldAddr)
+       if !ok {
+               // TODO(dh): also check indexing into arrays and slices
+               return
+       }
+       T := v.X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct)
+       fields := make([]*types.Var, 0, T.NumFields())
+       for i := 0; i < T.NumFields() && i <= v.Field; i++ {
+               fields = append(fields, T.Field(i))
+       }
+
+       off := sizes.Offsetsof(fields)[v.Field]
+       if off%8 != 0 {
+               msg := fmt.Sprintf("address of non 64-bit aligned field %s passed to %s",
+                       T.Field(v.Field).Name(),
+                       code.CallName(call.Instr.Common()))
+               call.Invalid(msg)
+       }
+}
+
+func checkNoopMarshalImpl(argN int, meths ...string) CallCheck {
+       return func(call *Call) {
+               if code.IsGenerated(call.Pass, call.Instr.Pos()) {
+                       return
+               }
+               arg := call.Args[argN]
+               T := arg.Value.Value.Type()
+               Ts, ok := code.Dereference(T).Underlying().(*types.Struct)
+               if !ok {
+                       return
+               }
+               if Ts.NumFields() == 0 {
+                       return
+               }
+               fields := code.FlattenFields(Ts)
+               for _, field := range fields {
+                       if field.Var.Exported() {
+                               return
+                       }
+               }
+               // OPT(dh): we could use a method set cache here
+               ms := call.Instr.Parent().Prog.MethodSets.MethodSet(T)
+               // TODO(dh): we're not checking the signature, which can cause false negatives.
+               // This isn't a huge problem, however, since vet complains about incorrect signatures.
+               for _, meth := range meths {
+                       if ms.Lookup(nil, meth) != nil {
+                               return
+                       }
+               }
+               arg.Invalid("struct doesn't have any exported fields, nor custom marshaling")
+       }
+}
+
+func checkUnsupportedMarshalImpl(argN int, tag string, meths ...string) CallCheck {
+       // TODO(dh): flag slices and maps of unsupported types
+       return func(call *Call) {
+               msCache := &call.Instr.Parent().Prog.MethodSets
+
+               arg := call.Args[argN]
+               T := arg.Value.Value.Type()
+               Ts, ok := code.Dereference(T).Underlying().(*types.Struct)
+               if !ok {
+                       return
+               }
+               ms := msCache.MethodSet(T)
+               // TODO(dh): we're not checking the signature, which can cause false negatives.
+               // This isn't a huge problem, however, since vet complains about incorrect signatures.
+               for _, meth := range meths {
+                       if ms.Lookup(nil, meth) != nil {
+                               return
+                       }
+               }
+               fields := code.FlattenFields(Ts)
+               for _, field := range fields {
+                       if !(field.Var.Exported()) {
+                               continue
+                       }
+                       if reflect.StructTag(field.Tag).Get(tag) == "-" {
+                               continue
+                       }
+                       ms := msCache.MethodSet(field.Var.Type())
+                       // TODO(dh): we're not checking the signature, which can cause false negatives.
+                       // This isn't a huge problem, however, since vet complains about incorrect signatures.
+                       for _, meth := range meths {
+                               if ms.Lookup(nil, meth) != nil {
+                                       return
+                               }
+                       }
+                       switch field.Var.Type().Underlying().(type) {
+                       case *types.Chan, *types.Signature:
+                               arg.Invalid(fmt.Sprintf("trying to marshal chan or func value, field %s", fieldPath(T, field.Path)))
+                       }
+               }
+       }
+}
+
+func fieldPath(start types.Type, indices []int) string {
+       p := start.String()
+       for _, idx := range indices {
+               field := code.Dereference(start).Underlying().(*types.Struct).Field(idx)
+               start = field.Type()
+               p += "." + field.Name()
+       }
+       return p
+}
+
+func isInLoop(b *ir.BasicBlock) bool {
+       sets := functions.FindLoops(b.Parent())
+       for _, set := range sets {
+               if set.Has(b) {
+                       return true
+               }
+       }
+       return false
+}
+
+func CheckUntrappableSignal(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               if !code.IsCallToAnyAST(pass, call,
+                       "os/signal.Ignore", "os/signal.Notify", "os/signal.Reset") {
+                       return
+               }
+
+               hasSigterm := false
+               for _, arg := range call.Args {
+                       if conv, ok := arg.(*ast.CallExpr); ok && isName(pass, conv.Fun, "os.Signal") {
+                               arg = conv.Args[0]
+                       }
+
+                       if isName(pass, arg, "syscall.SIGTERM") {
+                               hasSigterm = true
+                               break
+                       }
+
+               }
+               for i, arg := range call.Args {
+                       if conv, ok := arg.(*ast.CallExpr); ok && isName(pass, conv.Fun, "os.Signal") {
+                               arg = conv.Args[0]
+                       }
+
+                       if isName(pass, arg, "os.Kill") || isName(pass, arg, "syscall.SIGKILL") {
+                               var fixes []analysis.SuggestedFix
+                               if !hasSigterm {
+                                       nargs := make([]ast.Expr, len(call.Args))
+                                       for j, a := range call.Args {
+                                               if i == j {
+                                                       nargs[j] = Selector("syscall", "SIGTERM")
+                                               } else {
+                                                       nargs[j] = a
+                                               }
+                                       }
+                                       ncall := *call
+                                       ncall.Args = nargs
+                                       fixes = append(fixes, edit.Fix(fmt.Sprintf("use syscall.SIGTERM instead of %s", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
+                               }
+                               nargs := make([]ast.Expr, 0, len(call.Args))
+                               for j, a := range call.Args {
+                                       if i == j {
+                                               continue
+                                       }
+                                       nargs = append(nargs, a)
+                               }
+                               ncall := *call
+                               ncall.Args = nargs
+                               fixes = append(fixes, edit.Fix(fmt.Sprintf("remove %s from list of arguments", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
+                               report.Report(pass, arg, fmt.Sprintf("%s cannot be trapped (did you mean syscall.SIGTERM?)", report.Render(pass, arg)), report.Fixes(fixes...))
+                       }
+                       if isName(pass, arg, "syscall.SIGSTOP") {
+                               nargs := make([]ast.Expr, 0, len(call.Args)-1)
+                               for j, a := range call.Args {
+                                       if i == j {
+                                               continue
+                                       }
+                                       nargs = append(nargs, a)
+                               }
+                               ncall := *call
+                               ncall.Args = nargs
+                               report.Report(pass, arg, "syscall.SIGSTOP cannot be trapped", report.Fixes(edit.Fix("remove syscall.SIGSTOP from list of arguments", edit.ReplaceWithNode(pass.Fset, call, &ncall))))
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckTemplate(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               var kind string
+               switch code.CallNameAST(pass, call) {
+               case "(*text/template.Template).Parse":
+                       kind = "text"
+               case "(*html/template.Template).Parse":
+                       kind = "html"
+               default:
+                       return
+               }
+               sel := call.Fun.(*ast.SelectorExpr)
+               if !code.IsCallToAnyAST(pass, sel.X, "text/template.New", "html/template.New") {
+                       // TODO(dh): this is a cheap workaround for templates with
+                       // different delims. A better solution with less false
+                       // negatives would use data flow analysis to see where the
+                       // template comes from and where it has been
+                       return
+               }
+               s, ok := code.ExprToString(pass, call.Args[Arg("(*text/template.Template).Parse.text")])
+               if !ok {
+                       return
+               }
+               var err error
+               switch kind {
+               case "text":
+                       _, err = texttemplate.New("").Parse(s)
+               case "html":
+                       _, err = htmltemplate.New("").Parse(s)
+               }
+               if err != nil {
+                       // TODO(dominikh): whitelist other parse errors, if any
+                       if strings.Contains(err.Error(), "unexpected") {
+                               report.Report(pass, call.Args[Arg("(*text/template.Template).Parse.text")], err.Error())
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+var (
+       checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`)
+       checkTimeSleepConstantPatternRs  = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`)
+)
+
+func CheckTimeSleepConstant(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               if !code.IsCallToAST(pass, call, "time.Sleep") {
+                       return
+               }
+               lit, ok := call.Args[Arg("time.Sleep.d")].(*ast.BasicLit)
+               if !ok {
+                       return
+               }
+               n, err := strconv.Atoi(lit.Value)
+               if err != nil {
+                       return
+               }
+               if n == 0 || n > 120 {
+                       // time.Sleep(0) is a seldom used pattern in concurrency
+                       // tests. >120 might be intentional. 120 was chosen
+                       // because the user could've meant 2 minutes.
+                       return
+               }
+
+               report.Report(pass, lit,
+                       fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes(
+                               edit.Fix("explicitly use nanoseconds", edit.ReplaceWithPattern(pass, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit}, lit)),
+                               edit.Fix("use seconds", edit.ReplaceWithPattern(pass, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit}, lit))))
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+var checkWaitgroupAddQ = pattern.MustParse(`
+       (GoStmt
+               (CallExpr
+                       (FuncLit
+                               _
+                               call@(CallExpr (Function "(*sync.WaitGroup).Add") _):_) _))`)
+
+func CheckWaitgroupAdd(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               if m, ok := Match(pass, checkWaitgroupAddQ, node); ok {
+                       call := m.State["call"].(ast.Node)
+                       report.Report(pass, call, fmt.Sprintf("should call %s before starting the goroutine to avoid a race", report.Render(pass, call)))
+               }
+       }
+       code.Preorder(pass, fn, (*ast.GoStmt)(nil))
+       return nil, nil
+}
+
+func CheckInfiniteEmptyLoop(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               loop := node.(*ast.ForStmt)
+               if len(loop.Body.List) != 0 || loop.Post != nil {
+                       return
+               }
+
+               if loop.Init != nil {
+                       // TODO(dh): this isn't strictly necessary, it just makes
+                       // the check easier.
+                       return
+               }
+               // An empty loop is bad news in two cases: 1) The loop has no
+               // condition. In that case, it's just a loop that spins
+               // forever and as fast as it can, keeping a core busy. 2) The
+               // loop condition only consists of variable or field reads and
+               // operators on those. The only way those could change their
+               // value is with unsynchronised access, which constitutes a
+               // data race.
+               //
+               // If the condition contains any function calls, its behaviour
+               // is dynamic and the loop might terminate. Similarly for
+               // channel receives.
+
+               if loop.Cond != nil {
+                       if code.MayHaveSideEffects(pass, loop.Cond, nil) {
+                               return
+                       }
+                       if ident, ok := loop.Cond.(*ast.Ident); ok {
+                               if k, ok := pass.TypesInfo.ObjectOf(ident).(*types.Const); ok {
+                                       if !constant.BoolVal(k.Val()) {
+                                               // don't flag `for false {}` loops. They're a debug aid.
+                                               return
+                                       }
+                               }
+                       }
+                       report.Report(pass, loop, "loop condition never changes or has a race condition")
+               }
+               report.Report(pass, loop, "this loop will spin, using 100%% CPU", report.ShortRange())
+       }
+       code.Preorder(pass, fn, (*ast.ForStmt)(nil))
+       return nil, nil
+}
+
+func CheckDeferInInfiniteLoop(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               mightExit := false
+               var defers []ast.Stmt
+               loop := node.(*ast.ForStmt)
+               if loop.Cond != nil {
+                       return
+               }
+               fn2 := func(node ast.Node) bool {
+                       switch stmt := node.(type) {
+                       case *ast.ReturnStmt:
+                               mightExit = true
+                               return false
+                       case *ast.BranchStmt:
+                               // TODO(dominikh): if this sees a break in a switch or
+                               // select, it doesn't check if it breaks the loop or
+                               // just the select/switch. This causes some false
+                               // negatives.
+                               if stmt.Tok == token.BREAK {
+                                       mightExit = true
+                                       return false
+                               }
+                       case *ast.DeferStmt:
+                               defers = append(defers, stmt)
+                       case *ast.FuncLit:
+                               // Don't look into function bodies
+                               return false
+                       }
+                       return true
+               }
+               ast.Inspect(loop.Body, fn2)
+               if mightExit {
+                       return
+               }
+               for _, stmt := range defers {
+                       report.Report(pass, stmt, "defers in this infinite loop will never run")
+               }
+       }
+       code.Preorder(pass, fn, (*ast.ForStmt)(nil))
+       return nil, nil
+}
+
+func CheckDubiousDeferInChannelRangeLoop(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               loop := node.(*ast.RangeStmt)
+               typ := pass.TypesInfo.TypeOf(loop.X)
+               _, ok := typ.Underlying().(*types.Chan)
+               if !ok {
+                       return
+               }
+               fn2 := func(node ast.Node) bool {
+                       switch stmt := node.(type) {
+                       case *ast.DeferStmt:
+                               report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed")
+                       case *ast.FuncLit:
+                               // Don't look into function bodies
+                               return false
+                       }
+                       return true
+               }
+               ast.Inspect(loop.Body, fn2)
+       }
+       code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
+       return nil, nil
+}
+
+func CheckTestMainExit(pass *analysis.Pass) (interface{}, error) {
+       var (
+               fnmain    ast.Node
+               callsExit bool
+               callsRun  bool
+               arg       types.Object
+       )
+       fn := func(node ast.Node, push bool) bool {
+               if !push {
+                       if fnmain != nil && node == fnmain {
+                               if !callsExit && callsRun {
+                                       report.Report(pass, fnmain, "TestMain should call os.Exit to set exit code")
+                               }
+                               fnmain = nil
+                               callsExit = false
+                               callsRun = false
+                               arg = nil
+                       }
+                       return true
+               }
+
+               switch node := node.(type) {
+               case *ast.FuncDecl:
+                       if fnmain != nil {
+                               return true
+                       }
+                       if !isTestMain(pass, node) {
+                               return false
+                       }
+                       fnmain = node
+                       arg = pass.TypesInfo.ObjectOf(node.Type.Params.List[0].Names[0])
+                       return true
+               case *ast.CallExpr:
+                       if code.IsCallToAST(pass, node, "os.Exit") {
+                               callsExit = true
+                               return false
+                       }
+                       sel, ok := node.Fun.(*ast.SelectorExpr)
+                       if !ok {
+                               return true
+                       }
+                       ident, ok := sel.X.(*ast.Ident)
+                       if !ok {
+                               return true
+                       }
+                       if arg != pass.TypesInfo.ObjectOf(ident) {
+                               return true
+                       }
+                       if sel.Sel.Name == "Run" {
+                               callsRun = true
+                               return false
+                       }
+                       return true
+               default:
+                       ExhaustiveTypeSwitch(node)
+                       return true
+               }
+       }
+       pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.FuncDecl)(nil), (*ast.CallExpr)(nil)}, fn)
+       return nil, nil
+}
+
+func isTestMain(pass *analysis.Pass, decl *ast.FuncDecl) bool {
+       if decl.Name.Name != "TestMain" {
+               return false
+       }
+       if len(decl.Type.Params.List) != 1 {
+               return false
+       }
+       arg := decl.Type.Params.List[0]
+       if len(arg.Names) != 1 {
+               return false
+       }
+       return code.IsOfType(pass, arg.Type, "*testing.M")
+}
+
+func CheckExec(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               if !code.IsCallToAST(pass, call, "os/exec.Command") {
+                       return
+               }
+               val, ok := code.ExprToString(pass, call.Args[Arg("os/exec.Command.name")])
+               if !ok {
+                       return
+               }
+               if !strings.Contains(val, " ") || strings.Contains(val, `\`) || strings.Contains(val, "/") {
+                       return
+               }
+               report.Report(pass, call.Args[Arg("os/exec.Command.name")],
+                       "first argument to exec.Command looks like a shell command, but a program name or path are expected")
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckLoopEmptyDefault(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               loop := node.(*ast.ForStmt)
+               if len(loop.Body.List) != 1 || loop.Cond != nil || loop.Init != nil {
+                       return
+               }
+               sel, ok := loop.Body.List[0].(*ast.SelectStmt)
+               if !ok {
+                       return
+               }
+               for _, c := range sel.Body.List {
+                       // FIXME this leaves behind an empty line, and possibly
+                       // comments in the default branch. We can't easily fix
+                       // either.
+                       if comm, ok := c.(*ast.CommClause); ok && comm.Comm == nil && len(comm.Body) == 0 {
+                               report.Report(pass, comm, "should not have an empty default case in a for+select loop; the loop will spin",
+                                       report.Fixes(edit.Fix("remove empty default branch", edit.Delete(comm))))
+                               // there can only be one default case
+                               break
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.ForStmt)(nil))
+       return nil, nil
+}
+
+func CheckLhsRhsIdentical(pass *analysis.Pass) (interface{}, error) {
+       var isFloat func(T types.Type) bool
+       isFloat = func(T types.Type) bool {
+               switch T := T.Underlying().(type) {
+               case *types.Basic:
+                       kind := T.Kind()
+                       return kind == types.Float32 || kind == types.Float64
+               case *types.Array:
+                       return isFloat(T.Elem())
+               case *types.Struct:
+                       for i := 0; i < T.NumFields(); i++ {
+                               if !isFloat(T.Field(i).Type()) {
+                                       return false
+                               }
+                       }
+                       return true
+               default:
+                       return false
+               }
+       }
+
+       // TODO(dh): this check ignores the existence of side-effects and
+       // happily flags fn() == fn() â€“ so far, we've had nobody complain
+       // about a false positive, and it's caught several bugs in real
+       // code.
+       fn := func(node ast.Node) {
+               op := node.(*ast.BinaryExpr)
+               switch op.Op {
+               case token.EQL, token.NEQ:
+                       if isFloat(pass.TypesInfo.TypeOf(op.X)) {
+                               // f == f and f != f might be used to check for NaN
+                               return
+                       }
+               case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
+                       token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
+               default:
+                       // For some ops, such as + and *, it can make sense to
+                       // have identical operands
+                       return
+               }
+
+               if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
+                       return
+               }
+               if report.Render(pass, op.X) != report.Render(pass, op.Y) {
+                       return
+               }
+               l1, ok1 := op.X.(*ast.BasicLit)
+               l2, ok2 := op.Y.(*ast.BasicLit)
+               if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
+                       // cgo generates the following function call:
+                       // _cgoCheckPointer(_cgoBase0, 0 == 0) â€“ it uses 0 == 0
+                       // instead of true in case the user shadowed the
+                       // identifier. Ideally we'd restrict this exception to
+                       // calls of _cgoCheckPointer, but it's not worth the
+                       // hassle of keeping track of the stack. <lit> <op> <lit>
+                       // are very rare to begin with, and we're mostly checking
+                       // for them to catch typos such as 1 == 1 where the user
+                       // meant to type i == 1. The odds of a false negative for
+                       // 0 == 0 are slim.
+                       return
+               }
+               report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
+       }
+       code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
+       return nil, nil
+}
+
+func CheckScopedBreak(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               var body *ast.BlockStmt
+               switch node := node.(type) {
+               case *ast.ForStmt:
+                       body = node.Body
+               case *ast.RangeStmt:
+                       body = node.Body
+               default:
+                       ExhaustiveTypeSwitch(node)
+               }
+               for _, stmt := range body.List {
+                       var blocks [][]ast.Stmt
+                       switch stmt := stmt.(type) {
+                       case *ast.SwitchStmt:
+                               for _, c := range stmt.Body.List {
+                                       blocks = append(blocks, c.(*ast.CaseClause).Body)
+                               }
+                       case *ast.SelectStmt:
+                               for _, c := range stmt.Body.List {
+                                       blocks = append(blocks, c.(*ast.CommClause).Body)
+                               }
+                       default:
+                               continue
+                       }
+
+                       for _, body := range blocks {
+                               if len(body) == 0 {
+                                       continue
+                               }
+                               lasts := []ast.Stmt{body[len(body)-1]}
+                               // TODO(dh): unfold all levels of nested block
+                               // statements, not just a single level if statement
+                               if ifs, ok := lasts[0].(*ast.IfStmt); ok {
+                                       if len(ifs.Body.List) == 0 {
+                                               continue
+                                       }
+                                       lasts[0] = ifs.Body.List[len(ifs.Body.List)-1]
+
+                                       if block, ok := ifs.Else.(*ast.BlockStmt); ok {
+                                               if len(block.List) != 0 {
+                                                       lasts = append(lasts, block.List[len(block.List)-1])
+                                               }
+                                       }
+                               }
+                               for _, last := range lasts {
+                                       branch, ok := last.(*ast.BranchStmt)
+                                       if !ok || branch.Tok != token.BREAK || branch.Label != nil {
+                                               continue
+                                       }
+                                       report.Report(pass, branch, "ineffective break statement. Did you mean to break out of the outer loop?")
+                               }
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil))
+       return nil, nil
+}
+
+func CheckUnsafePrintf(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               name := code.CallNameAST(pass, call)
+               var arg int
+
+               switch name {
+               case "fmt.Printf", "fmt.Sprintf", "log.Printf":
+                       arg = Arg("fmt.Printf.format")
+               case "fmt.Fprintf":
+                       arg = Arg("fmt.Fprintf.format")
+               default:
+                       return
+               }
+               if len(call.Args) != arg+1 {
+                       return
+               }
+               switch call.Args[arg].(type) {
+               case *ast.CallExpr, *ast.Ident:
+               default:
+                       return
+               }
+
+               alt := name[:len(name)-1]
+               report.Report(pass, call,
+                       "printf-style function with dynamic format string and no further arguments should use print-style function instead",
+                       report.Fixes(edit.Fix(fmt.Sprintf("use %s instead of %s", alt, name), edit.ReplaceWithString(pass.Fset, call.Fun, alt))))
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckEarlyDefer(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               block := node.(*ast.BlockStmt)
+               if len(block.List) < 2 {
+                       return
+               }
+               for i, stmt := range block.List {
+                       if i == len(block.List)-1 {
+                               break
+                       }
+                       assign, ok := stmt.(*ast.AssignStmt)
+                       if !ok {
+                               continue
+                       }
+                       if len(assign.Rhs) != 1 {
+                               continue
+                       }
+                       if len(assign.Lhs) < 2 {
+                               continue
+                       }
+                       if lhs, ok := assign.Lhs[len(assign.Lhs)-1].(*ast.Ident); ok && lhs.Name == "_" {
+                               continue
+                       }
+                       call, ok := assign.Rhs[0].(*ast.CallExpr)
+                       if !ok {
+                               continue
+                       }
+                       sig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)
+                       if !ok {
+                               continue
+                       }
+                       if sig.Results().Len() < 2 {
+                               continue
+                       }
+                       last := sig.Results().At(sig.Results().Len() - 1)
+                       // FIXME(dh): check that it's error from universe, not
+                       // another type of the same name
+                       if last.Type().String() != "error" {
+                               continue
+                       }
+                       lhs, ok := assign.Lhs[0].(*ast.Ident)
+                       if !ok {
+                               continue
+                       }
+                       def, ok := block.List[i+1].(*ast.DeferStmt)
+                       if !ok {
+                               continue
+                       }
+                       sel, ok := def.Call.Fun.(*ast.SelectorExpr)
+                       if !ok {
+                               continue
+                       }
+                       ident, ok := selectorX(sel).(*ast.Ident)
+                       if !ok {
+                               continue
+                       }
+                       if ident.Obj != lhs.Obj {
+                               continue
+                       }
+                       if sel.Sel.Name != "Close" {
+                               continue
+                       }
+                       report.Report(pass, def, fmt.Sprintf("should check returned error before deferring %s", report.Render(pass, def.Call)))
+               }
+       }
+       code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
+       return nil, nil
+}
+
+func selectorX(sel *ast.SelectorExpr) ast.Node {
+       switch x := sel.X.(type) {
+       case *ast.SelectorExpr:
+               return selectorX(x)
+       default:
+               return x
+       }
+}
+
+func CheckEmptyCriticalSection(pass *analysis.Pass) (interface{}, error) {
+       if pass.Pkg.Path() == "sync_test" {
+               // exception for the sync package's tests
+               return nil, nil
+       }
+
+       // Initially it might seem like this check would be easier to
+       // implement using IR. After all, we're only checking for two
+       // consecutive method calls. In reality, however, there may be any
+       // number of other instructions between the lock and unlock, while
+       // still constituting an empty critical section. For example,
+       // given `m.x().Lock(); m.x().Unlock()`, there will be a call to
+       // x(). In the AST-based approach, this has a tiny potential for a
+       // false positive (the second call to x might be doing work that
+       // is protected by the mutex). In an IR-based approach, however,
+       // it would miss a lot of real bugs.
+
+       mutexParams := func(s ast.Stmt) (x ast.Expr, funcName string, ok bool) {
+               expr, ok := s.(*ast.ExprStmt)
+               if !ok {
+                       return nil, "", false
+               }
+               call, ok := expr.X.(*ast.CallExpr)
+               if !ok {
+                       return nil, "", false
+               }
+               sel, ok := call.Fun.(*ast.SelectorExpr)
+               if !ok {
+                       return nil, "", false
+               }
+
+               fn, ok := pass.TypesInfo.ObjectOf(sel.Sel).(*types.Func)
+               if !ok {
+                       return nil, "", false
+               }
+               sig := fn.Type().(*types.Signature)
+               if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
+                       return nil, "", false
+               }
+
+               return sel.X, fn.Name(), true
+       }
+
+       fn := func(node ast.Node) {
+               block := node.(*ast.BlockStmt)
+               if len(block.List) < 2 {
+                       return
+               }
+               for i := range block.List[:len(block.List)-1] {
+                       sel1, method1, ok1 := mutexParams(block.List[i])
+                       sel2, method2, ok2 := mutexParams(block.List[i+1])
+
+                       if !ok1 || !ok2 || report.Render(pass, sel1) != report.Render(pass, sel2) {
+                               continue
+                       }
+                       if (method1 == "Lock" && method2 == "Unlock") ||
+                               (method1 == "RLock" && method2 == "RUnlock") {
+                               report.Report(pass, block.List[i+1], "empty critical section")
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
+       return nil, nil
+}
+
+var (
+       // cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't
+       // want to flag.
+       cgoIdent               = regexp.MustCompile(`^_C(func|var)_.+$`)
+       checkIneffectiveCopyQ1 = pattern.MustParse(`(UnaryExpr "&" (StarExpr obj))`)
+       checkIneffectiveCopyQ2 = pattern.MustParse(`(StarExpr (UnaryExpr "&" _))`)
+)
+
+func CheckIneffectiveCopy(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               if m, ok := Match(pass, checkIneffectiveCopyQ1, node); ok {
+                       if ident, ok := m.State["obj"].(*ast.Ident); !ok || !cgoIdent.MatchString(ident.Name) {
+                               report.Report(pass, node, "&*x will be simplified to x. It will not copy x.")
+                       }
+               } else if _, ok := Match(pass, checkIneffectiveCopyQ2, node); ok {
+                       report.Report(pass, node, "*&x will be simplified to x. It will not copy x.")
+               }
+       }
+       code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.StarExpr)(nil))
+       return nil, nil
+}
+
+func CheckCanonicalHeaderKey(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node, push bool) bool {
+               if !push {
+                       return false
+               }
+               assign, ok := node.(*ast.AssignStmt)
+               if ok {
+                       // TODO(dh): This risks missing some Header reads, for
+                       // example in `h1["foo"] = h2["foo"]` â€“ these edge
+                       // cases are probably rare enough to ignore for now.
+                       for _, expr := range assign.Lhs {
+                               op, ok := expr.(*ast.IndexExpr)
+                               if !ok {
+                                       continue
+                               }
+                               if code.IsOfType(pass, op.X, "net/http.Header") {
+                                       return false
+                               }
+                       }
+                       return true
+               }
+               op, ok := node.(*ast.IndexExpr)
+               if !ok {
+                       return true
+               }
+               if !code.IsOfType(pass, op.X, "net/http.Header") {
+                       return true
+               }
+               s, ok := code.ExprToString(pass, op.Index)
+               if !ok {
+                       return true
+               }
+               canonical := http.CanonicalHeaderKey(s)
+               if s == canonical {
+                       return true
+               }
+               var fix analysis.SuggestedFix
+               switch op.Index.(type) {
+               case *ast.BasicLit:
+                       fix = edit.Fix("canonicalize header key", edit.ReplaceWithString(pass.Fset, op.Index, strconv.Quote(canonical)))
+               case *ast.Ident:
+                       call := &ast.CallExpr{
+                               Fun:  Selector("http", "CanonicalHeaderKey"),
+                               Args: []ast.Expr{op.Index},
+                       }
+                       fix = edit.Fix("wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
+               }
+               msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s)
+               if fix.Message != "" {
+                       report.Report(pass, op, msg, report.Fixes(fix))
+               } else {
+                       report.Report(pass, op, msg)
+               }
+               return true
+       }
+       pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn)
+       return nil, nil
+}
+
+func CheckBenchmarkN(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               assign := node.(*ast.AssignStmt)
+               if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
+                       return
+               }
+               sel, ok := assign.Lhs[0].(*ast.SelectorExpr)
+               if !ok {
+                       return
+               }
+               if sel.Sel.Name != "N" {
+                       return
+               }
+               if !code.IsOfType(pass, sel.X, "*testing.B") {
+                       return
+               }
+               report.Report(pass, assign, fmt.Sprintf("should not assign to %s", report.Render(pass, sel)))
+       }
+       code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
+       return nil, nil
+}
+
+func CheckUnreadVariableValues(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               if code.IsExample(fn) {
+                       continue
+               }
+               node := fn.Source()
+               if node == nil {
+                       continue
+               }
+               if gen, ok := code.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[ir.Value]struct{}{}
+               ast.Inspect(node, func(node ast.Node) bool {
+                       s, ok := node.(*ast.SwitchStmt)
+                       if !ok {
+                               return true
+                       }
+                       v, _ := fn.ValueForExpr(s.Tag)
+                       switchTags[v] = struct{}{}
+                       return true
+               })
+
+               // OPT(dh): don't use a map, possibly use a bitset
+               var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool
+               hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool {
+                       if _, ok := seen[v]; ok {
+                               return false
+                       }
+                       if _, ok := switchTags[v]; ok {
+                               return true
+                       }
+                       refs := v.Referrers()
+                       if refs == nil {
+                               // TODO investigate why refs can be nil
+                               return true
+                       }
+                       for _, ref := range *refs {
+                               switch ref := ref.(type) {
+                               case *ir.DebugRef:
+                               case *ir.Sigma:
+                                       if seen == nil {
+                                               seen = map[ir.Value]struct{}{}
+                                       }
+                                       seen[v] = struct{}{}
+                                       if hasUse(ref, seen) {
+                                               return true
+                                       }
+                               case *ir.Phi:
+                                       if seen == nil {
+                                               seen = map[ir.Value]struct{}{}
+                                       }
+                                       seen[v] = struct{}{}
+                                       if hasUse(ref, seen) {
+                                               return true
+                                       }
+                               default:
+                                       return true
+                               }
+                       }
+                       return false
+               }
+
+               ast.Inspect(node, func(node ast.Node) bool {
+                       assign, ok := node.(*ast.AssignStmt)
+                       if !ok {
+                               return true
+                       }
+                       if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 {
+                               // Either a function call with multiple return values,
+                               // or a comma-ok assignment
+
+                               val, _ := fn.ValueForExpr(assign.Rhs[0])
+                               if val == nil {
+                                       return true
+                               }
+                               refs := val.Referrers()
+                               if refs == nil {
+                                       return true
+                               }
+                               for _, ref := range *refs {
+                                       ex, ok := ref.(*ir.Extract)
+                                       if !ok {
+                                               continue
+                                       }
+                                       if !hasUse(ex, nil) {
+                                               lhs := assign.Lhs[ex.Index]
+                                               if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
+                                                       continue
+                                               }
+                                               report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
+                                       }
+                               }
+                               return true
+                       }
+                       for i, lhs := range assign.Lhs {
+                               rhs := assign.Rhs[i]
+                               if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
+                                       continue
+                               }
+                               val, _ := fn.ValueForExpr(rhs)
+                               if val == nil {
+                                       continue
+                               }
+
+                               if _, ok := val.(*ir.Const); ok {
+                                       // a zero-valued constant, for example in 'foo := []string(nil)'
+                                       continue
+                               }
+                               if !hasUse(val, nil) {
+                                       report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
+                               }
+                       }
+                       return true
+               })
+       }
+       return nil, nil
+}
+
+func CheckPredeterminedBooleanExprs(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               binop, ok := ins.(*ir.BinOp)
+                               if !ok {
+                                       continue
+                               }
+                               switch binop.Op {
+                               case token.GTR, token.LSS, token.EQL, token.NEQ, token.LEQ, token.GEQ:
+                               default:
+                                       continue
+                               }
+
+                               xs, ok1 := consts(binop.X, nil, nil)
+                               ys, ok2 := consts(binop.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, binop.Op, y.Value) {
+                                                       trues++
+                                               }
+                                       }
+                               }
+                               b := trues != 0
+                               if trues == 0 || trues == len(xs)*len(ys) {
+                                       report.Report(pass, binop, fmt.Sprintf("binary expression is always %t for all possible values (%s %s %s)", b, xs, binop.Op, ys))
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckNilMaps(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               mu, ok := ins.(*ir.MapUpdate)
+                               if !ok {
+                                       continue
+                               }
+                               c, ok := mu.Map.(*ir.Const)
+                               if !ok {
+                                       continue
+                               }
+                               if c.Value != nil {
+                                       continue
+                               }
+                               report.Report(pass, mu, "assignment to nil map")
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckExtremeComparison(pass *analysis.Pass) (interface{}, error) {
+       isobj := func(expr ast.Expr, name string) bool {
+               sel, ok := expr.(*ast.SelectorExpr)
+               if !ok {
+                       return false
+               }
+               return code.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
+       }
+
+       fn := func(node ast.Node) {
+               expr := node.(*ast.BinaryExpr)
+               tx := pass.TypesInfo.TypeOf(expr.X)
+               basic, ok := tx.Underlying().(*types.Basic)
+               if !ok {
+                       return
+               }
+
+               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) {
+                       report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, max))
+               }
+               if expr.Op == token.LEQ && isobj(expr.Y, max) ||
+                       expr.Op == token.GEQ && isobj(expr.X, max) {
+                       report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, max))
+               }
+
+               if (basic.Info() & types.IsUnsigned) != 0 {
+                       if (expr.Op == token.LSS && code.IsIntLiteral(expr.Y, "0")) ||
+                               (expr.Op == token.GTR && code.IsIntLiteral(expr.X, "0")) {
+                               report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic))
+                       }
+                       if expr.Op == token.GEQ && code.IsIntLiteral(expr.Y, "0") ||
+                               expr.Op == token.LEQ && code.IsIntLiteral(expr.X, "0") {
+                               report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic))
+                       }
+               } 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) {
+                               report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, min))
+                       }
+                       if expr.Op == token.GEQ && isobj(expr.Y, min) ||
+                               expr.Op == token.LEQ && isobj(expr.X, min) {
+                               report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, min))
+                       }
+               }
+
+       }
+       code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
+       return nil, nil
+}
+
+func consts(val ir.Value, out []*ir.Const, visitedPhis map[string]bool) ([]*ir.Const, bool) {
+       if visitedPhis == nil {
+               visitedPhis = map[string]bool{}
+       }
+       var ok bool
+       switch val := val.(type) {
+       case *ir.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 *ir.Const:
+               out = append(out, val)
+       case *ir.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 := []*ir.Const{out[0]}
+       for _, val := range out[1:] {
+               if val.Value == uniq[len(uniq)-1].Value {
+                       continue
+               }
+               uniq = append(uniq, val)
+       }
+       return uniq, true
+}
+
+func CheckLoopCondition(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               cb := func(node ast.Node) bool {
+                       loop, ok := node.(*ast.ForStmt)
+                       if !ok {
+                               return true
+                       }
+                       if loop.Init == nil || loop.Cond == nil || loop.Post == nil {
+                               return true
+                       }
+                       init, ok := loop.Init.(*ast.AssignStmt)
+                       if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 {
+                               return true
+                       }
+                       cond, ok := loop.Cond.(*ast.BinaryExpr)
+                       if !ok {
+                               return true
+                       }
+                       x, ok := cond.X.(*ast.Ident)
+                       if !ok {
+                               return true
+                       }
+                       lhs, ok := init.Lhs[0].(*ast.Ident)
+                       if !ok {
+                               return true
+                       }
+                       if x.Obj != lhs.Obj {
+                               return true
+                       }
+                       if _, ok := loop.Post.(*ast.IncDecStmt); !ok {
+                               return true
+                       }
+
+                       v, isAddr := fn.ValueForExpr(cond.X)
+                       if v == nil || isAddr {
+                               return true
+                       }
+                       switch v := v.(type) {
+                       case *ir.Phi:
+                               ops := v.Operands(nil)
+                               if len(ops) != 2 {
+                                       return true
+                               }
+                               _, ok := (*ops[0]).(*ir.Const)
+                               if !ok {
+                                       return true
+                               }
+                               sigma, ok := (*ops[1]).(*ir.Sigma)
+                               if !ok {
+                                       return true
+                               }
+                               if sigma.X != v {
+                                       return true
+                               }
+                       case *ir.Load:
+                               return true
+                       }
+                       report.Report(pass, cond, "variable in loop condition never changes")
+
+                       return true
+               }
+               Inspect(fn.Source(), cb)
+       }
+       return nil, nil
+}
+
+func CheckArgOverwritten(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               cb := func(node ast.Node) bool {
+                       var typ *ast.FuncType
+                       var body *ast.BlockStmt
+                       switch fn := node.(type) {
+                       case *ast.FuncDecl:
+                               typ = fn.Type
+                               body = fn.Body
+                       case *ast.FuncLit:
+                               typ = fn.Type
+                               body = fn.Body
+                       }
+                       if body == nil {
+                               return true
+                       }
+                       if len(typ.Params.List) == 0 {
+                               return true
+                       }
+                       for _, field := range typ.Params.List {
+                               for _, arg := range field.Names {
+                                       obj := pass.TypesInfo.ObjectOf(arg)
+                                       var irobj *ir.Parameter
+                                       for _, param := range fn.Params {
+                                               if param.Object() == obj {
+                                                       irobj = param
+                                                       break
+                                               }
+                                       }
+                                       if irobj == nil {
+                                               continue
+                                       }
+                                       refs := irobj.Referrers()
+                                       if refs == nil {
+                                               continue
+                                       }
+                                       if len(code.FilterDebug(*refs)) != 0 {
+                                               continue
+                                       }
+
+                                       var assignment ast.Node
+                                       ast.Inspect(body, func(node ast.Node) bool {
+                                               if assignment != nil {
+                                                       return false
+                                               }
+                                               assign, ok := node.(*ast.AssignStmt)
+                                               if !ok {
+                                                       return true
+                                               }
+                                               for _, lhs := range assign.Lhs {
+                                                       ident, ok := lhs.(*ast.Ident)
+                                                       if !ok {
+                                                               continue
+                                                       }
+                                                       if pass.TypesInfo.ObjectOf(ident) == obj {
+                                                               assignment = assign
+                                                               return false
+                                                       }
+                                               }
+                                               return true
+                                       })
+                                       if assignment != nil {
+                                               report.Report(pass, arg, fmt.Sprintf("argument %s is overwritten before first use", arg),
+                                                       report.Related(assignment, fmt.Sprintf("assignment to %s", arg)))
+                                       }
+                               }
+                       }
+                       return true
+               }
+               Inspect(fn.Source(), cb)
+       }
+       return nil, nil
+}
+
+func CheckIneffectiveLoop(pass *analysis.Pass) (interface{}, error) {
+       // This check detects some, but not all unconditional loop exits.
+       // We give up in the following cases:
+       //
+       // - a goto anywhere in the loop. The goto might skip over our
+       // return, and we don't check that it doesn't.
+       //
+       // - any nested, unlabelled continue, even if it is in another
+       // loop or closure.
+       fn := func(node ast.Node) {
+               var body *ast.BlockStmt
+               switch fn := node.(type) {
+               case *ast.FuncDecl:
+                       body = fn.Body
+               case *ast.FuncLit:
+                       body = fn.Body
+               default:
+                       ExhaustiveTypeSwitch(node)
+               }
+               if body == nil {
+                       return
+               }
+               labels := map[*ast.Object]ast.Stmt{}
+               ast.Inspect(body, func(node ast.Node) bool {
+                       label, ok := node.(*ast.LabeledStmt)
+                       if !ok {
+                               return true
+                       }
+                       labels[label.Label.Obj] = label.Stmt
+                       return true
+               })
+
+               ast.Inspect(body, func(node ast.Node) bool {
+                       var loop ast.Node
+                       var body *ast.BlockStmt
+                       switch node := node.(type) {
+                       case *ast.ForStmt:
+                               body = node.Body
+                               loop = node
+                       case *ast.RangeStmt:
+                               typ := pass.TypesInfo.TypeOf(node.X)
+                               if _, ok := typ.Underlying().(*types.Map); ok {
+                                       // looping once over a map is a valid pattern for
+                                       // getting an arbitrary element.
+                                       return true
+                               }
+                               body = node.Body
+                               loop = node
+                       default:
+                               return true
+                       }
+                       if len(body.List) < 2 {
+                               // avoid flagging the somewhat common pattern of using
+                               // a range loop to get the first element in a slice,
+                               // or the first rune in a string.
+                               return true
+                       }
+                       var unconditionalExit ast.Node
+                       hasBranching := false
+                       for _, stmt := range body.List {
+                               switch stmt := stmt.(type) {
+                               case *ast.BranchStmt:
+                                       switch stmt.Tok {
+                                       case token.BREAK:
+                                               if stmt.Label == nil || labels[stmt.Label.Obj] == loop {
+                                                       unconditionalExit = stmt
+                                               }
+                                       case token.CONTINUE:
+                                               if stmt.Label == nil || labels[stmt.Label.Obj] == loop {
+                                                       unconditionalExit = nil
+                                                       return false
+                                               }
+                                       }
+                               case *ast.ReturnStmt:
+                                       unconditionalExit = stmt
+                               case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
+                                       hasBranching = true
+                               }
+                       }
+                       if unconditionalExit == nil || !hasBranching {
+                               return false
+                       }
+                       ast.Inspect(body, func(node ast.Node) bool {
+                               if branch, ok := node.(*ast.BranchStmt); ok {
+
+                                       switch branch.Tok {
+                                       case token.GOTO:
+                                               unconditionalExit = nil
+                                               return false
+                                       case token.CONTINUE:
+                                               if branch.Label != nil && labels[branch.Label.Obj] != loop {
+                                                       return true
+                                               }
+                                               unconditionalExit = nil
+                                               return false
+                                       }
+                               }
+                               return true
+                       })
+                       if unconditionalExit != nil {
+                               report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated")
+                       }
+                       return true
+               })
+       }
+       code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
+       return nil, nil
+}
+
+var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Function _) (Builtin "nil"):_)`)
+
+func CheckNilContext(pass *analysis.Pass) (interface{}, error) {
+       todo := &ast.CallExpr{
+               Fun: Selector("context", "TODO"),
+       }
+       bg := &ast.CallExpr{
+               Fun: Selector("context", "Background"),
+       }
+       fn := func(node ast.Node) {
+               m, ok := Match(pass, checkNilContextQ, node)
+               if !ok {
+                       return
+               }
+
+               call := node.(*ast.CallExpr)
+               fun, ok := m.State["fun"].(*types.Func)
+               if !ok {
+                       // it might also be a builtin
+                       return
+               }
+               sig := fun.Type().(*types.Signature)
+               if sig.Params().Len() == 0 {
+                       // Our CallExpr might've matched a method expression, like
+                       // (*T).Foo(nil) â€“ here, nil isn't the first argument of
+                       // the Foo method, but the method receiver.
+                       return
+               }
+               if !code.IsType(sig.Params().At(0).Type(), "context.Context") {
+                       return
+               }
+               report.Report(pass, call.Args[0],
+                       "do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes(
+                               edit.Fix("use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)),
+                               edit.Fix("use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg))))
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+var (
+       checkSeekerQ = pattern.MustParse(`(CallExpr fun@(SelectorExpr _ (Ident "Seek")) [arg1@(SelectorExpr (Ident "io") (Ident (Or "SeekStart" "SeekCurrent" "SeekEnd"))) arg2])`)
+       checkSeekerR = pattern.MustParse(`(CallExpr fun [arg2 arg1])`)
+)
+
+func CheckSeeker(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               if _, edits, ok := MatchAndEdit(pass, checkSeekerQ, checkSeekerR, node); ok {
+                       report.Report(pass, node, "the first argument of io.Seeker is the offset, but an io.Seek* constant is being used instead",
+                               report.Fixes(edit.Fix("swap arguments", edits...)))
+               }
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckIneffectiveAppend(pass *analysis.Pass) (interface{}, error) {
+       isAppend := func(ins ir.Value) bool {
+               call, ok := ins.(*ir.Call)
+               if !ok {
+                       return false
+               }
+               if call.Call.IsInvoke() {
+                       return false
+               }
+               if builtin, ok := call.Call.Value.(*ir.Builtin); !ok || builtin.Name() != "append" {
+                       return false
+               }
+               return true
+       }
+
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               val, ok := ins.(ir.Value)
+                               if !ok || !isAppend(val) {
+                                       continue
+                               }
+
+                               isUsed := false
+                               visited := map[ir.Instruction]bool{}
+                               var walkRefs func(refs []ir.Instruction)
+                               walkRefs = func(refs []ir.Instruction) {
+                               loop:
+                                       for _, ref := range refs {
+                                               if visited[ref] {
+                                                       continue
+                                               }
+                                               visited[ref] = true
+                                               if _, ok := ref.(*ir.DebugRef); ok {
+                                                       continue
+                                               }
+                                               switch ref := ref.(type) {
+                                               case *ir.Phi:
+                                                       walkRefs(*ref.Referrers())
+                                               case *ir.Sigma:
+                                                       walkRefs(*ref.Referrers())
+                                               case ir.Value:
+                                                       if !isAppend(ref) {
+                                                               isUsed = true
+                                                       } else {
+                                                               walkRefs(*ref.Referrers())
+                                                       }
+                                               case ir.Instruction:
+                                                       isUsed = true
+                                                       break loop
+                                               }
+                                       }
+                               }
+
+                               refs := val.Referrers()
+                               if refs == nil {
+                                       continue
+                               }
+                               walkRefs(*refs)
+
+                               if !isUsed {
+                                       report.Report(pass, ins, "this result of append is never used, except maybe in other appends")
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckConcurrentTesting(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               gostmt, ok := ins.(*ir.Go)
+                               if !ok {
+                                       continue
+                               }
+                               var fn *ir.Function
+                               switch val := gostmt.Call.Value.(type) {
+                               case *ir.Function:
+                                       fn = val
+                               case *ir.MakeClosure:
+                                       fn = val.Fn.(*ir.Function)
+                               default:
+                                       continue
+                               }
+                               if fn.Blocks == nil {
+                                       continue
+                               }
+                               for _, block := range fn.Blocks {
+                                       for _, ins := range block.Instrs {
+                                               call, ok := ins.(*ir.Call)
+                                               if !ok {
+                                                       continue
+                                               }
+                                               if call.Call.IsInvoke() {
+                                                       continue
+                                               }
+                                               callee := call.Call.StaticCallee()
+                                               if callee == nil {
+                                                       continue
+                                               }
+                                               recv := callee.Signature.Recv()
+                                               if recv == nil {
+                                                       continue
+                                               }
+                                               if !code.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
+                                               }
+                                               // TODO(dh): don't report multiple diagnostics
+                                               // for multiple calls to T.Fatal, but do
+                                               // collect all of them as related information
+                                               report.Report(pass, gostmt, fmt.Sprintf("the goroutine calls T.%s, which must be called in the same goroutine as the test", name),
+                                                       report.Related(call, fmt.Sprintf("call to T.%s", name)))
+                                       }
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
+       for _, b := range fn.Blocks {
+               for _, instr := range b.Instrs {
+                       if site, ok := instr.(ir.CallInstruction); ok {
+                               if g := site.Common().StaticCallee(); g != nil {
+                                       cb(fn, site, g)
+                               }
+                       }
+               }
+       }
+}
+
+func CheckCyclicFinalizer(pass *analysis.Pass) (interface{}, error) {
+       cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
+               if callee.RelString(nil) != "runtime.SetFinalizer" {
+                       return
+               }
+               arg0 := site.Common().Args[Arg("runtime.SetFinalizer.obj")]
+               if iface, ok := arg0.(*ir.MakeInterface); ok {
+                       arg0 = iface.X
+               }
+               load, ok := arg0.(*ir.Load)
+               if !ok {
+                       return
+               }
+               v, ok := load.X.(*ir.Alloc)
+               if !ok {
+                       return
+               }
+               arg1 := site.Common().Args[Arg("runtime.SetFinalizer.finalizer")]
+               if iface, ok := arg1.(*ir.MakeInterface); ok {
+                       arg1 = iface.X
+               }
+               mc, ok := arg1.(*ir.MakeClosure)
+               if !ok {
+                       return
+               }
+               for _, b := range mc.Bindings {
+                       if b == v {
+                               pos := lint.DisplayPosition(pass.Fset, mc.Fn.Pos())
+                               report.Report(pass, site, fmt.Sprintf("the finalizer closes over the object, preventing the finalizer from ever running (at %s)", pos))
+                       }
+               }
+       }
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               eachCall(fn, cb)
+       }
+       return nil, nil
+}
+
+/*
+func CheckSliceOutOfBounds(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               ia, ok := ins.(*ir.IndexAddr)
+                               if !ok {
+                                       continue
+                               }
+                               if _, ok := ia.X.Type().Underlying().(*types.Slice); !ok {
+                                       continue
+                               }
+                               sr, ok1 := c.funcDescs.Get(fn).Ranges[ia.X].(vrp.SliceInterval)
+                               idxr, ok2 := c.funcDescs.Get(fn).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 {
+                                       report.Nodef(pass, ia, "index out of bounds")
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+*/
+
+func CheckDeferLock(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       instrs := code.FilterDebug(block.Instrs)
+                       if len(instrs) < 2 {
+                               continue
+                       }
+                       for i, ins := range instrs[:len(instrs)-1] {
+                               call, ok := ins.(*ir.Call)
+                               if !ok {
+                                       continue
+                               }
+                               if !code.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
+                                       continue
+                               }
+                               nins, ok := instrs[i+1].(*ir.Defer)
+                               if !ok {
+                                       continue
+                               }
+                               if !code.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
+                                       continue
+                               }
+                               if call.Common().Args[0] != nins.Call.Args[0] {
+                                       continue
+                               }
+                               name := shortCallName(call.Common())
+                               alt := ""
+                               switch name {
+                               case "Lock":
+                                       alt = "Unlock"
+                               case "RLock":
+                                       alt = "RUnlock"
+                               }
+                               report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt))
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckNaNComparison(pass *analysis.Pass) (interface{}, error) {
+       isNaN := func(v ir.Value) bool {
+               call, ok := v.(*ir.Call)
+               if !ok {
+                       return false
+               }
+               return code.IsCallTo(call.Common(), "math.NaN")
+       }
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               ins, ok := ins.(*ir.BinOp)
+                               if !ok {
+                                       continue
+                               }
+                               if isNaN(ins.X) || isNaN(ins.Y) {
+                                       report.Report(pass, ins, "no value is equal to NaN, not even NaN itself")
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckInfiniteRecursion(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               eachCall(fn, func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
+                       if callee != fn {
+                               return
+                       }
+                       if _, ok := site.(*ir.Go); ok {
+                               // Recursively spawning goroutines doesn't consume
+                               // stack space infinitely, so don't flag it.
+                               return
+                       }
+
+                       block := site.Block()
+                       canReturn := false
+                       for _, b := range fn.Blocks {
+                               if block.Dominates(b) {
+                                       continue
+                               }
+                               if len(b.Instrs) == 0 {
+                                       continue
+                               }
+                               if _, ok := b.Control().(*ir.Return); ok {
+                                       canReturn = true
+                                       break
+                               }
+                       }
+                       if canReturn {
+                               return
+                       }
+                       report.Report(pass, site, "infinite recursive call")
+               })
+       }
+       return nil, nil
+}
+
+func objectName(obj types.Object) string {
+       if obj == nil {
+               return "<nil>"
+       }
+       var name string
+       if obj.Pkg() != nil && obj.Pkg().Scope().Lookup(obj.Name()) == obj {
+               s := obj.Pkg().Path()
+               if s != "" {
+                       name += s + "."
+               }
+       }
+       name += obj.Name()
+       return name
+}
+
+func isName(pass *analysis.Pass, expr ast.Expr, name string) bool {
+       var obj types.Object
+       switch expr := expr.(type) {
+       case *ast.Ident:
+               obj = pass.TypesInfo.ObjectOf(expr)
+       case *ast.SelectorExpr:
+               obj = pass.TypesInfo.ObjectOf(expr.Sel)
+       }
+       return objectName(obj) == name
+}
+
+func CheckLeakyTimeTick(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               if code.IsMainLike(pass) || code.IsInTest(pass, fn) {
+                       continue
+               }
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               call, ok := ins.(*ir.Call)
+                               if !ok || !code.IsCallTo(call.Common(), "time.Tick") {
+                                       continue
+                               }
+                               if !functions.Terminates(call.Parent()) {
+                                       continue
+                               }
+                               report.Report(pass, call, "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here")
+                       }
+               }
+       }
+       return nil, nil
+}
+
+var checkDoubleNegationQ = pattern.MustParse(`(UnaryExpr "!" single@(UnaryExpr "!" x))`)
+
+func CheckDoubleNegation(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               if m, ok := Match(pass, checkDoubleNegationQ, node); ok {
+                       report.Report(pass, node, "negating a boolean twice has no effect; is this a typo?", report.Fixes(
+                               edit.Fix("turn into single negation", edit.ReplaceWithNode(pass.Fset, node, m.State["single"].(ast.Node))),
+                               edit.Fix("remove double negation", edit.ReplaceWithNode(pass.Fset, node, m.State["x"].(ast.Node)))))
+               }
+       }
+       code.Preorder(pass, fn, (*ast.UnaryExpr)(nil))
+       return nil, nil
+}
+
+func CheckRepeatedIfElse(pass *analysis.Pass) (interface{}, error) {
+       seen := map[ast.Node]bool{}
+
+       var collectConds func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool)
+       collectConds = func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) {
+               seen[ifstmt] = true
+               // Bail if any if-statement has an Init statement or side effects in its condition
+               if ifstmt.Init != nil {
+                       return nil, false
+               }
+               if code.MayHaveSideEffects(pass, ifstmt.Cond, nil) {
+                       return nil, false
+               }
+
+               conds = append(conds, ifstmt.Cond)
+               if elsestmt, ok := ifstmt.Else.(*ast.IfStmt); ok {
+                       return collectConds(elsestmt, conds)
+               }
+               return conds, true
+       }
+       fn := func(node ast.Node) {
+               ifstmt := node.(*ast.IfStmt)
+               if seen[ifstmt] {
+                       // this if-statement is part of an if/else-if chain that we've already processed
+                       return
+               }
+               if ifstmt.Else == nil {
+                       // there can be at most one condition
+                       return
+               }
+               conds, ok := collectConds(ifstmt, nil)
+               if !ok {
+                       return
+               }
+               if len(conds) < 2 {
+                       return
+               }
+               counts := map[string]int{}
+               for _, cond := range conds {
+                       s := report.Render(pass, cond)
+                       counts[s]++
+                       if counts[s] == 2 {
+                               report.Report(pass, cond, "this condition occurs multiple times in this if/else if chain")
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.IfStmt)(nil))
+       return nil, nil
+}
+
+func CheckSillyBitwiseOps(pass *analysis.Pass) (interface{}, error) {
+       // FIXME(dh): what happened here?
+       if false {
+               for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+                       for _, block := range fn.Blocks {
+                               for _, ins := range block.Instrs {
+                                       ins, ok := ins.(*ir.BinOp)
+                                       if !ok {
+                                               continue
+                                       }
+
+                                       if c, ok := ins.Y.(*ir.Const); !ok || c.Value == nil || c.Value.Kind() != constant.Int || c.Uint64() != 0 {
+                                               continue
+                                       }
+                                       switch ins.Op {
+                                       case token.AND, token.OR, token.XOR:
+                                       default:
+                                               // we do not flag shifts because too often, x<<0 is part
+                                               // of a pattern, x<<0, x<<8, x<<16, ...
+                                               continue
+                                       }
+                                       path, _ := astutil.PathEnclosingInterval(code.File(pass, ins), ins.Pos(), ins.Pos())
+                                       if len(path) == 0 {
+                                               continue
+                                       }
+
+                                       if node, ok := path[0].(*ast.BinaryExpr); !ok || !code.IsIntLiteral(node.Y, "0") {
+                                               continue
+                                       }
+
+                                       switch ins.Op {
+                                       case token.AND:
+                                               report.Report(pass, ins, "x & 0 always equals 0")
+                                       case token.OR, token.XOR:
+                                               report.Report(pass, ins, fmt.Sprintf("x %s 0 always equals x", ins.Op))
+                                       }
+                               }
+                       }
+               }
+       }
+       fn := func(node ast.Node) {
+               binop := node.(*ast.BinaryExpr)
+               b, ok := pass.TypesInfo.TypeOf(binop).Underlying().(*types.Basic)
+               if !ok {
+                       return
+               }
+               if (b.Info() & types.IsInteger) == 0 {
+                       return
+               }
+               switch binop.Op {
+               case token.AND, token.OR, token.XOR:
+               default:
+                       // we do not flag shifts because too often, x<<0 is part
+                       // of a pattern, x<<0, x<<8, x<<16, ...
+                       return
+               }
+               switch y := binop.Y.(type) {
+               case *ast.Ident:
+                       obj, ok := pass.TypesInfo.ObjectOf(y).(*types.Const)
+                       if !ok {
+                               return
+                       }
+                       if v, _ := constant.Int64Val(obj.Val()); v != 0 {
+                               return
+                       }
+                       path, _ := astutil.PathEnclosingInterval(code.File(pass, obj), obj.Pos(), obj.Pos())
+                       if len(path) < 2 {
+                               return
+                       }
+                       spec, ok := path[1].(*ast.ValueSpec)
+                       if !ok {
+                               return
+                       }
+                       if len(spec.Names) != 1 || len(spec.Values) != 1 {
+                               // TODO(dh): we could support this
+                               return
+                       }
+                       ident, ok := spec.Values[0].(*ast.Ident)
+                       if !ok {
+                               return
+                       }
+                       if !isIota(pass.TypesInfo.ObjectOf(ident)) {
+                               return
+                       }
+                       switch binop.Op {
+                       case token.AND:
+                               report.Report(pass, node,
+                                       fmt.Sprintf("%s always equals 0; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
+                       case token.OR, token.XOR:
+                               report.Report(pass, node,
+                                       fmt.Sprintf("%s always equals %s; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.X), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
+                       }
+               case *ast.BasicLit:
+                       if !code.IsIntLiteral(binop.Y, "0") {
+                               return
+                       }
+                       switch binop.Op {
+                       case token.AND:
+                               report.Report(pass, node, fmt.Sprintf("%s always equals 0", report.Render(pass, binop)))
+                       case token.OR, token.XOR:
+                               report.Report(pass, node, fmt.Sprintf("%s always equals %s", report.Render(pass, binop), report.Render(pass, binop.X)))
+                       }
+               default:
+                       return
+               }
+       }
+       code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
+       return nil, nil
+}
+
+func isIota(obj types.Object) bool {
+       if obj.Name() != "iota" {
+               return false
+       }
+       c, ok := obj.(*types.Const)
+       if !ok {
+               return false
+       }
+       return c.Pkg() == nil
+}
+
+func CheckNonOctalFileMode(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               call := node.(*ast.CallExpr)
+               sig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)
+               if !ok {
+                       return
+               }
+               n := sig.Params().Len()
+               for i := 0; i < n; i++ {
+                       typ := sig.Params().At(i).Type()
+                       if !code.IsType(typ, "os.FileMode") {
+                               continue
+                       }
+
+                       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
+                               }
+                               report.Report(pass, call.Args[i], fmt.Sprintf("file mode '%s' evaluates to %#o; did you mean '0%s'?", lit.Value, v, lit.Value),
+                                       report.Fixes(edit.Fix("fix octal literal", edit.ReplaceWithString(pass.Fset, call.Args[i], "0"+lit.Value))))
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckPureFunctions(pass *analysis.Pass) (interface{}, error) {
+       pure := pass.ResultOf[facts.Purity].(facts.PurityResult)
+
+fnLoop:
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               if code.IsInTest(pass, fn) {
+                       params := fn.Signature.Params()
+                       for i := 0; i < params.Len(); i++ {
+                               param := params.At(i)
+                               if code.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 fn.Blocks {
+                       for _, ins := range b.Instrs {
+                               ins, ok := ins.(*ir.Call)
+                               if !ok {
+                                       continue
+                               }
+                               refs := ins.Referrers()
+                               if refs == nil || len(code.FilterDebug(*refs)) > 0 {
+                                       continue
+                               }
+
+                               callee := ins.Common().StaticCallee()
+                               if callee == nil {
+                                       continue
+                               }
+                               if callee.Object() == nil {
+                                       // TODO(dh): support anonymous functions
+                                       continue
+                               }
+                               if _, ok := pure[callee.Object().(*types.Func)]; ok {
+                                       if pass.Pkg.Path() == "fmt_test" && callee.Object().(*types.Func).FullName() == "fmt.Sprintf" {
+                                               // special case for benchmarks in the fmt package
+                                               continue
+                                       }
+                                       report.Report(pass, ins, fmt.Sprintf("%s is a pure function but its return value is ignored", callee.Name()))
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckDeprecated(pass *analysis.Pass) (interface{}, error) {
+       deprs := pass.ResultOf[facts.Deprecated].(facts.DeprecatedResult)
+
+       // Selectors can appear outside of function literals, e.g. when
+       // declaring package level variables.
+
+       var tfn types.Object
+       stack := 0
+       fn := func(node ast.Node, push bool) bool {
+               if !push {
+                       stack--
+                       return false
+               }
+               stack++
+               if stack == 1 {
+                       tfn = nil
+               }
+               if fn, ok := node.(*ast.FuncDecl); ok {
+                       tfn = pass.TypesInfo.ObjectOf(fn.Name)
+               }
+               sel, ok := node.(*ast.SelectorExpr)
+               if !ok {
+                       return true
+               }
+
+               obj := pass.TypesInfo.ObjectOf(sel.Sel)
+               if obj.Pkg() == nil {
+                       return true
+               }
+               if pass.Pkg == obj.Pkg() || obj.Pkg().Path()+"_test" == pass.Pkg.Path() {
+                       // Don't flag stuff in our own package
+                       return true
+               }
+               if depr, ok := deprs.Objects[obj]; ok {
+                       // Look for the first available alternative, not the first
+                       // version something was deprecated in. If a function was
+                       // deprecated in Go 1.6, an alternative has been available
+                       // already in 1.0, and we're targeting 1.2, it still
+                       // makes sense to use the alternative from 1.0, to be
+                       // future-proof.
+                       minVersion := deprecated.Stdlib[code.SelectorName(pass, sel)].AlternativeAvailableSince
+                       if !code.IsGoVersion(pass, minVersion) {
+                               return true
+                       }
+
+                       if tfn != nil {
+                               if _, ok := deprs.Objects[tfn]; ok {
+                                       // functions that are deprecated may use deprecated
+                                       // symbols
+                                       return true
+                               }
+                       }
+                       report.Report(pass, sel, fmt.Sprintf("%s is deprecated: %s", report.Render(pass, sel), depr.Msg))
+                       return true
+               }
+               return true
+       }
+
+       fn2 := func(node ast.Node) {
+               spec := node.(*ast.ImportSpec)
+               var imp *types.Package
+               if spec.Name != nil {
+                       imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported()
+               } else {
+                       imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported()
+               }
+
+               p := spec.Path.Value
+               path := p[1 : len(p)-1]
+               if depr, ok := deprs.Packages[imp]; ok {
+                       if path == "github.com/golang/protobuf/proto" {
+                               gen, ok := code.Generator(pass, spec.Path.Pos())
+                               if ok && gen == facts.ProtocGenGo {
+                                       return
+                               }
+                       }
+                       report.Report(pass, spec, fmt.Sprintf("package %s is deprecated: %s", path, depr.Msg))
+               }
+       }
+       pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn)
+       code.Preorder(pass, fn2, (*ast.ImportSpec)(nil))
+       return nil, nil
+}
+
+func callChecker(rules map[string]CallCheck) func(pass *analysis.Pass) (interface{}, error) {
+       return func(pass *analysis.Pass) (interface{}, error) {
+               return checkCalls(pass, rules)
+       }
+}
+
+func checkCalls(pass *analysis.Pass, rules map[string]CallCheck) (interface{}, error) {
+       cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
+               obj, ok := callee.Object().(*types.Func)
+               if !ok {
+                       return
+               }
+
+               r, ok := rules[lint.FuncName(obj)]
+               if !ok {
+                       return
+               }
+               var args []*Argument
+               irargs := site.Common().Args
+               if callee.Signature.Recv() != nil {
+                       irargs = irargs[1:]
+               }
+               for _, arg := range irargs {
+                       if iarg, ok := arg.(*ir.MakeInterface); ok {
+                               arg = iarg.X
+                       }
+                       args = append(args, &Argument{Value: Value{arg}})
+               }
+               call := &Call{
+                       Pass:   pass,
+                       Instr:  site,
+                       Args:   args,
+                       Parent: site.Parent(),
+               }
+               r(call)
+               path, _ := astutil.PathEnclosingInterval(code.File(pass, site), site.Pos(), site.Pos())
+               var astcall *ast.CallExpr
+               for _, el := range path {
+                       if expr, ok := el.(*ast.CallExpr); ok {
+                               astcall = expr
+                               break
+                       }
+               }
+               for idx, arg := range call.Args {
+                       for _, e := range arg.invalids {
+                               if astcall != nil {
+                                       report.Report(pass, astcall.Args[idx], e)
+                               } else {
+                                       report.Report(pass, site, e)
+                               }
+                       }
+               }
+               for _, e := range call.invalids {
+                       report.Report(pass, call.Instr, e)
+               }
+       }
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               eachCall(fn, cb)
+       }
+       return nil, nil
+}
+
+func shortCallName(call *ir.CallCommon) string {
+       if call.IsInvoke() {
+               return ""
+       }
+       switch v := call.Value.(type) {
+       case *ir.Function:
+               fn, ok := v.Object().(*types.Func)
+               if !ok {
+                       return ""
+               }
+               return fn.Name()
+       case *ir.Builtin:
+               return v.Name()
+       }
+       return ""
+}
+
+func CheckWriterBufferModified(pass *analysis.Pass) (interface{}, error) {
+       // TODO(dh): this might be a good candidate for taint analysis.
+       // Taint the argument as MUST_NOT_MODIFY, then propagate that
+       // through functions like bytes.Split
+
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               sig := fn.Signature
+               if fn.Name() != "Write" || sig.Recv() == nil || sig.Params().Len() != 1 || sig.Results().Len() != 2 {
+                       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 || !code.IsType(named, "error") {
+                       continue
+               }
+
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               switch ins := ins.(type) {
+                               case *ir.Store:
+                                       addr, ok := ins.Addr.(*ir.IndexAddr)
+                                       if !ok {
+                                               continue
+                                       }
+                                       if addr.X != fn.Params[1] {
+                                               continue
+                                       }
+                                       report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
+                               case *ir.Call:
+                                       if !code.IsCallTo(ins.Common(), "append") {
+                                               continue
+                                       }
+                                       if ins.Common().Args[0] != fn.Params[1] {
+                                               continue
+                                       }
+                                       report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func loopedRegexp(name string) CallCheck {
+       return func(call *Call) {
+               if len(extractConsts(call.Args[0].Value.Value)) == 0 {
+                       return
+               }
+               if !isInLoop(call.Instr.Block()) {
+                       return
+               }
+               call.Invalid(fmt.Sprintf("calling %s in a loop has poor performance, consider using regexp.Compile", name))
+       }
+}
+
+func CheckEmptyBranch(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               if fn.Source() == nil {
+                       continue
+               }
+               if code.IsExample(fn) {
+                       continue
+               }
+               cb := func(node ast.Node) bool {
+                       ifstmt, ok := node.(*ast.IfStmt)
+                       if !ok {
+                               return true
+                       }
+                       if ifstmt.Else != nil {
+                               b, ok := ifstmt.Else.(*ast.BlockStmt)
+                               if !ok || len(b.List) != 0 {
+                                       return true
+                               }
+                               report.Report(pass, ifstmt.Else, "empty branch", report.FilterGenerated(), report.ShortRange())
+                       }
+                       if len(ifstmt.Body.List) != 0 {
+                               return true
+                       }
+                       report.Report(pass, ifstmt, "empty branch", report.FilterGenerated(), report.ShortRange())
+                       return true
+               }
+               Inspect(fn.Source(), cb)
+       }
+       return nil, nil
+}
+
+func CheckMapBytesKey(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, b := range fn.Blocks {
+               insLoop:
+                       for _, ins := range b.Instrs {
+                               // find []byte -> string conversions
+                               conv, ok := ins.(*ir.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 *ir.DebugRef:
+                                               if _, ok := ref.Expr.(*ast.Ident); !ok {
+                                                       // the string seems to be used somewhere
+                                                       // unexpected; the default branch should
+                                                       // catch this already, but be safe
+                                                       continue insLoop
+                                               } else {
+                                                       ident = true
+                                               }
+                                       case *ir.MapLookup:
+                                       default:
+                                               // the string is used somewhere else than a
+                                               // map lookup
+                                               continue insLoop
+                                       }
+                               }
+
+                               // the result of the conversion wasn't assigned to an
+                               // identifier
+                               if !ident {
+                                       continue
+                               }
+                               report.Report(pass, conv, "m[string(key)] would be more efficient than k := string(key); m[k]")
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
+       return sharedcheck.CheckRangeStringRunes(pass)
+}
+
+func CheckSelfAssignment(pass *analysis.Pass) (interface{}, error) {
+       pure := pass.ResultOf[facts.Purity].(facts.PurityResult)
+
+       fn := func(node ast.Node) {
+               assign := node.(*ast.AssignStmt)
+               if assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
+                       return
+               }
+               for i, lhs := range assign.Lhs {
+                       rhs := assign.Rhs[i]
+                       if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
+                               continue
+                       }
+                       if code.MayHaveSideEffects(pass, lhs, pure) || code.MayHaveSideEffects(pass, rhs, pure) {
+                               continue
+                       }
+
+                       rlh := report.Render(pass, lhs)
+                       rrh := report.Render(pass, rhs)
+                       if rlh == rrh {
+                               report.Report(pass, assign, fmt.Sprintf("self-assignment of %s to %s", rrh, rlh), report.FilterGenerated())
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
+       return nil, nil
+}
+
+func buildTagsIdentical(s1, s2 []string) bool {
+       if len(s1) != len(s2) {
+               return false
+       }
+       s1s := make([]string, len(s1))
+       copy(s1s, s1)
+       sort.Strings(s1s)
+       s2s := make([]string, len(s2))
+       copy(s2s, s2)
+       sort.Strings(s2s)
+       for i, s := range s1s {
+               if s != s2s[i] {
+                       return false
+               }
+       }
+       return true
+}
+
+func CheckDuplicateBuildConstraints(pass *analysis.Pass) (interface{}, error) {
+       for _, f := range pass.Files {
+               constraints := buildTags(f)
+               for i, constraint1 := range constraints {
+                       for j, constraint2 := range constraints {
+                               if i >= j {
+                                       continue
+                               }
+                               if buildTagsIdentical(constraint1, constraint2) {
+                                       msg := fmt.Sprintf("identical build constraints %q and %q",
+                                               strings.Join(constraint1, " "),
+                                               strings.Join(constraint2, " "))
+                                       report.Report(pass, f, msg, report.FilterGenerated(), report.ShortRange())
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckSillyRegexp(pass *analysis.Pass) (interface{}, error) {
+       // We could use the rule checking engine for this, but the
+       // arguments aren't really invalid.
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, b := range fn.Blocks {
+                       for _, ins := range b.Instrs {
+                               call, ok := ins.(*ir.Call)
+                               if !ok {
+                                       continue
+                               }
+                               if !code.IsCallToAny(call.Common(), "regexp.MustCompile", "regexp.Compile", "regexp.Match", "regexp.MatchReader", "regexp.MatchString") {
+                                       continue
+                               }
+                               c, ok := call.Common().Args[0].(*ir.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
+                               }
+                               report.Report(pass, call, "regular expression does not contain any meta characters")
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func CheckMissingEnumTypesInDeclaration(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               decl := node.(*ast.GenDecl)
+               if !decl.Lparen.IsValid() {
+                       return
+               }
+               if decl.Tok != token.CONST {
+                       return
+               }
+
+               groups := code.GroupSpecs(pass.Fset, decl.Specs)
+       groupLoop:
+               for _, group := range groups {
+                       if len(group) < 2 {
+                               continue
+                       }
+                       if group[0].(*ast.ValueSpec).Type == nil {
+                               // first constant doesn't have a type
+                               continue groupLoop
+                       }
+                       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
+                               }
+                       }
+                       var edits []analysis.TextEdit
+                       typ := group[0].(*ast.ValueSpec).Type
+                       for _, spec := range group[1:] {
+                               nspec := *spec.(*ast.ValueSpec)
+                               nspec.Type = typ
+                               edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec))
+                       }
+                       report.Report(pass, group[0], "only the first constant in this group has an explicit type", report.Fixes(edit.Fix("add type to all constants in group", edits...)))
+               }
+       }
+       code.Preorder(pass, fn, (*ast.GenDecl)(nil))
+       return nil, nil
+}
+
+func CheckTimerResetReturnValue(pass *analysis.Pass) (interface{}, error) {
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, block := range fn.Blocks {
+                       for _, ins := range block.Instrs {
+                               call, ok := ins.(*ir.Call)
+                               if !ok {
+                                       continue
+                               }
+                               if !code.IsCallTo(call.Common(), "(*time.Timer).Reset") {
+                                       continue
+                               }
+                               refs := call.Referrers()
+                               if refs == nil {
+                                       continue
+                               }
+                               for _, ref := range code.FilterDebug(*refs) {
+                                       ifstmt, ok := ref.(*ir.If)
+                                       if !ok {
+                                               continue
+                                       }
+
+                                       found := false
+                                       for _, succ := range ifstmt.Block().Succs {
+                                               if len(succ.Preds) != 1 {
+                                                       // Merge point, not a branch in the
+                                                       // syntactical sense.
+
+                                                       // FIXME(dh): this is broken for if
+                                                       // statements a la "if x || y"
+                                                       continue
+                                               }
+                                               irutil.Walk(succ, func(b *ir.BasicBlock) bool {
+                                                       if !succ.Dominates(b) {
+                                                               // We've reached the end of the branch
+                                                               return false
+                                                       }
+                                                       for _, ins := range b.Instrs {
+                                                               // TODO(dh): we should check that
+                                                               // we're receiving from the channel of
+                                                               // a time.Timer to further reduce
+                                                               // false positives. Not a key
+                                                               // priority, considering the rarity of
+                                                               // Reset and the tiny likeliness of a
+                                                               // false positive
+                                                               if ins, ok := ins.(*ir.Recv); ok && code.IsType(ins.Chan.Type(), "<-chan time.Time") {
+                                                                       found = true
+                                                                       return false
+                                                               }
+                                                       }
+                                                       return true
+                                               })
+                                       }
+
+                                       if found {
+                                               report.Report(pass, call, "it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring")
+                                       }
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+var (
+       checkToLowerToUpperComparisonQ = pattern.MustParse(`
+       (BinaryExpr
+               (CallExpr fun@(Function (Or "strings.ToLower" "strings.ToUpper")) [a])
+               tok@(Or "==" "!=")
+               (CallExpr fun [b]))`)
+       checkToLowerToUpperComparisonR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b])`)
+)
+
+func CheckToLowerToUpperComparison(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               m, ok := Match(pass, checkToLowerToUpperComparisonQ, node)
+               if !ok {
+                       return
+               }
+               rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr)
+               if m.State["tok"].(token.Token) == token.NEQ {
+                       rn = &ast.UnaryExpr{
+                               Op: token.NOT,
+                               X:  rn,
+                       }
+               }
+
+               report.Report(pass, node, "should use strings.EqualFold instead", report.Fixes(edit.Fix("replace with strings.EqualFold", edit.ReplaceWithNode(pass.Fset, node, rn))))
+       }
+
+       code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
+       return nil, nil
+}
+
+func CheckUnreachableTypeCases(pass *analysis.Pass) (interface{}, error) {
+       // Check if T subsumes V in a type switch. T subsumes V if T is an interface and T's method set is a subset of V's method set.
+       subsumes := func(T, V types.Type) bool {
+               tIface, ok := T.Underlying().(*types.Interface)
+               if !ok {
+                       return false
+               }
+
+               return types.Implements(V, tIface)
+       }
+
+       subsumesAny := func(Ts, Vs []types.Type) (types.Type, types.Type, bool) {
+               for _, T := range Ts {
+                       for _, V := range Vs {
+                               if subsumes(T, V) {
+                                       return T, V, true
+                               }
+                       }
+               }
+
+               return nil, nil, false
+       }
+
+       fn := func(node ast.Node) {
+               tsStmt := node.(*ast.TypeSwitchStmt)
+
+               type ccAndTypes struct {
+                       cc    *ast.CaseClause
+                       types []types.Type
+               }
+
+               // All asserted types in the order of case clauses.
+               ccs := make([]ccAndTypes, 0, len(tsStmt.Body.List))
+               for _, stmt := range tsStmt.Body.List {
+                       cc, _ := stmt.(*ast.CaseClause)
+
+                       // Exclude the 'default' case.
+                       if len(cc.List) == 0 {
+                               continue
+                       }
+
+                       Ts := make([]types.Type, len(cc.List))
+                       for i, expr := range cc.List {
+                               Ts[i] = pass.TypesInfo.TypeOf(expr)
+                       }
+
+                       ccs = append(ccs, ccAndTypes{cc: cc, types: Ts})
+               }
+
+               if len(ccs) <= 1 {
+                       // Zero or one case clauses, nothing to check.
+                       return
+               }
+
+               // Check if case clauses following cc have types that are subsumed by cc.
+               for i, cc := range ccs[:len(ccs)-1] {
+                       for _, next := range ccs[i+1:] {
+                               if T, V, yes := subsumesAny(cc.types, next.types); yes {
+                                       report.Report(pass, next.cc, fmt.Sprintf("unreachable case clause: %s will always match before %s", T.String(), V.String()),
+                                               report.ShortRange())
+                               }
+                       }
+               }
+       }
+
+       code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
+       return nil, nil
+}
+
+var checkSingleArgAppendQ = pattern.MustParse(`(CallExpr (Builtin "append") [_])`)
+
+func CheckSingleArgAppend(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               _, ok := Match(pass, checkSingleArgAppendQ, node)
+               if !ok {
+                       return
+               }
+               report.Report(pass, node, "x = append(y) is equivalent to x = y", report.FilterGenerated())
+       }
+       code.Preorder(pass, fn, (*ast.CallExpr)(nil))
+       return nil, nil
+}
+
+func CheckStructTags(pass *analysis.Pass) (interface{}, error) {
+       importsGoFlags := false
+
+       // we use the AST instead of (*types.Package).Imports to work
+       // around vendored packages in GOPATH mode. A vendored package's
+       // path will include the vendoring subtree as a prefix.
+       for _, f := range pass.Files {
+               for _, imp := range f.Imports {
+                       v := imp.Path.Value
+                       if v[1:len(v)-1] == "github.com/jessevdk/go-flags" {
+                               importsGoFlags = true
+                               break
+                       }
+               }
+       }
+
+       fn := func(node ast.Node) {
+               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 {
+                               report.Report(pass, field.Tag, fmt.Sprintf("unparseable struct tag: %s", err))
+                               continue
+                       }
+                       for k, v := range tags {
+                               if len(v) > 1 {
+                                       isGoFlagsTag := importsGoFlags &&
+                                               (k == "choice" || k == "optional-value" || k == "default")
+                                       if !isGoFlagsTag {
+                                               report.Report(pass, field.Tag, fmt.Sprintf("duplicate struct tag %q", k))
+                                       }
+                               }
+
+                               switch k {
+                               case "json":
+                                       checkJSONTag(pass, field, v[0])
+                               case "xml":
+                                       checkXMLTag(pass, field, v[0])
+                               }
+                       }
+               }
+       }
+       code.Preorder(pass, fn, (*ast.StructType)(nil))
+       return nil, nil
+}
+
+func checkJSONTag(pass *analysis.Pass, field *ast.Field, tag string) {
+       if pass.Pkg.Path() == "encoding/json" || pass.Pkg.Path() == "encoding/json_test" {
+               // don't flag malformed JSON tags in the encoding/json
+               // package; it knows what it is doing, and it is testing
+               // itself.
+               return
+       }
+       //lint:ignore SA9003 TODO(dh): should we flag empty tags?
+       if len(tag) == 0 {
+       }
+       fields := strings.Split(tag, ",")
+       for _, r := range fields[0] {
+               if !unicode.IsLetter(r) && !unicode.IsDigit(r) && !strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", r) {
+                       report.Report(pass, field.Tag, fmt.Sprintf("invalid JSON field name %q", fields[0]))
+               }
+       }
+       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
+                       T := code.Dereference(pass.TypesInfo.TypeOf(field.Type).Underlying()).Underlying()
+                       basic, ok := T.(*types.Basic)
+                       if !ok || (basic.Info()&(types.IsBoolean|types.IsInteger|types.IsFloat|types.IsString)) == 0 {
+                               report.Report(pass, field.Tag, "the JSON string option only applies to fields of type string, floating point, integer or bool, or pointers to those")
+                       }
+               case "inline":
+                       ci++
+               default:
+                       report.Report(pass, field.Tag, fmt.Sprintf("unknown JSON option %q", s))
+               }
+       }
+       if co > 1 {
+               report.Report(pass, field.Tag, `duplicate JSON option "omitempty"`)
+       }
+       if cs > 1 {
+               report.Report(pass, field.Tag, `duplicate JSON option "string"`)
+       }
+       if ci > 1 {
+               report.Report(pass, field.Tag, `duplicate JSON option "inline"`)
+       }
+}
+
+func checkXMLTag(pass *analysis.Pass, field *ast.Field, tag string) {
+       //lint:ignore SA9003 TODO(dh): should we flag empty tags?
+       if len(tag) == 0 {
+       }
+       fields := strings.Split(tag, ",")
+       counts := map[string]int{}
+       var exclusives []string
+       for _, s := range fields[1:] {
+               switch s {
+               case "attr", "chardata", "cdata", "innerxml", "comment":
+                       counts[s]++
+                       if counts[s] == 1 {
+                               exclusives = append(exclusives, s)
+                       }
+               case "omitempty", "any":
+                       counts[s]++
+               case "":
+               default:
+                       report.Report(pass, field.Tag, fmt.Sprintf("unknown XML option %q", s))
+               }
+       }
+       for k, v := range counts {
+               if v > 1 {
+                       report.Report(pass, field.Tag, fmt.Sprintf("duplicate XML option %q", k))
+               }
+       }
+       if len(exclusives) > 1 {
+               report.Report(pass, field.Tag, fmt.Sprintf("XML options %s are mutually exclusive", strings.Join(exclusives, " and ")))
+       }
+}
+
+func CheckImpossibleTypeAssertion(pass *analysis.Pass) (interface{}, error) {
+       type entry struct {
+               l, r *types.Func
+       }
+
+       msc := &pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg.Prog.MethodSets
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               for _, b := range fn.Blocks {
+                       for _, instr := range b.Instrs {
+                               assert, ok := instr.(*ir.TypeAssert)
+                               if !ok {
+                                       continue
+                               }
+                               var wrong []entry
+                               left := assert.X.Type()
+                               right := assert.AssertedType
+                               righti, ok := right.Underlying().(*types.Interface)
+
+                               if !ok {
+                                       // We only care about interface->interface
+                                       // assertions. The Go compiler already catches
+                                       // impossible interface->concrete assertions.
+                                       continue
+                               }
+
+                               ms := msc.MethodSet(left)
+                               for i := 0; i < righti.NumMethods(); i++ {
+                                       mr := righti.Method(i)
+                                       sel := ms.Lookup(mr.Pkg(), mr.Name())
+                                       if sel == nil {
+                                               continue
+                                       }
+                                       ml := sel.Obj().(*types.Func)
+                                       if types.AssignableTo(ml.Type(), mr.Type()) {
+                                               continue
+                                       }
+
+                                       wrong = append(wrong, entry{ml, mr})
+                               }
+
+                               if len(wrong) != 0 {
+                                       s := fmt.Sprintf("impossible type assertion; %s and %s contradict each other:",
+                                               types.TypeString(left, types.RelativeTo(pass.Pkg)),
+                                               types.TypeString(right, types.RelativeTo(pass.Pkg)))
+                                       for _, e := range wrong {
+                                               s += fmt.Sprintf("\n\twrong type for %s method", e.l.Name())
+                                               s += fmt.Sprintf("\n\t\thave %s", e.l.Type())
+                                               s += fmt.Sprintf("\n\t\twant %s", e.r.Type())
+                                       }
+                                       report.Report(pass, assert, s)
+                               }
+                       }
+               }
+       }
+       return nil, nil
+}
+
+func checkWithValueKey(call *Call) {
+       arg := call.Args[1]
+       T := arg.Value.Value.Type()
+       if T, ok := T.(*types.Basic); ok {
+               arg.Invalid(
+                       fmt.Sprintf("should not use built-in type %s as key for value; define your own type to avoid collisions", T))
+       }
+       if !types.Comparable(T) {
+               arg.Invalid(fmt.Sprintf("keys used with context.WithValue must be comparable, but type %s is not comparable", T))
+       }
+}
+
+func CheckMaybeNil(pass *analysis.Pass) (interface{}, error) {
+       // This is an extremely trivial check that doesn't try to reason
+       // about control flow. That is, phis and sigmas do not propagate
+       // any information. As such, we can flag this:
+       //
+       //      _ = *x
+       //      if x == nil { return }
+       //
+       // but we cannot flag this:
+       //
+       //      if x == nil { println(x) }
+       //      _ = *x
+       //
+       // nor many other variations of conditional uses of or assignments to x.
+       //
+       // However, even this trivial implementation finds plenty of
+       // real-world bugs, such as dereference before nil pointer check,
+       // or using t.Error instead of t.Fatal when encountering nil
+       // pointers.
+       //
+       // On the flip side, our naive implementation avoids false positives in branches, such as
+       //
+       //      if x != nil { _ = *x }
+       //
+       // due to the same lack of propagating information through sigma
+       // nodes. x inside the branch will be independent of the x in the
+       // nil pointer check.
+       //
+       //
+       // We could implement a more powerful check, but then we'd be
+       // getting false positives instead of false negatives because
+       // we're incapable of deducing relationships between variables.
+       // For example, a function might return a pointer and an error,
+       // and the error being nil guarantees that the pointer is not nil.
+       // Depending on the surrounding code, the pointer may still end up
+       // being checked against nil in one place, and guarded by a check
+       // on the error in another, which would lead to us marking some
+       // loads as unsafe.
+       //
+       // Unfortunately, simply hard-coding the relationship between
+       // return values wouldn't eliminate all false positives, either.
+       // Many other more subtle relationships exist. An abridged example
+       // from real code:
+       //
+       // if a == nil && b == nil { return }
+       // c := fn(a)
+       // if c != "" { _ = *a }
+       //
+       // where `fn` is guaranteed to return a non-empty string if a
+       // isn't nil.
+       //
+       // We choose to err on the side of false negatives.
+
+       isNilConst := func(v ir.Value) bool {
+               if code.IsPointerLike(v.Type()) {
+                       if k, ok := v.(*ir.Const); ok {
+                               return k.IsNil()
+                       }
+               }
+               return false
+       }
+
+       for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
+               maybeNil := map[ir.Value]ir.Instruction{}
+               for _, b := range fn.Blocks {
+                       for _, instr := range b.Instrs {
+                               if instr, ok := instr.(*ir.BinOp); ok {
+                                       var ptr ir.Value
+                                       if isNilConst(instr.X) {
+                                               ptr = instr.Y
+                                       } else if isNilConst(instr.Y) {
+                                               ptr = instr.X
+                                       }
+                                       maybeNil[ptr] = instr
+                               }
+                       }
+               }
+
+               for _, b := range fn.Blocks {
+                       for _, instr := range b.Instrs {
+                               var ptr ir.Value
+                               switch instr := instr.(type) {
+                               case *ir.Load:
+                                       ptr = instr.X
+                               case *ir.Store:
+                                       ptr = instr.Addr
+                               case *ir.IndexAddr:
+                                       ptr = instr.X
+                               case *ir.FieldAddr:
+                                       ptr = instr.X
+                               }
+                               if ptr != nil {
+                                       switch ptr.(type) {
+                                       case *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr:
+                                               // these cannot be nil
+                                               continue
+                                       }
+                                       if r, ok := maybeNil[ptr]; ok {
+                                               report.Report(pass, instr, "possible nil pointer dereference",
+                                                       report.Related(r, "this check suggests that the pointer can be nil"))
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return nil, nil
+}
+
+var checkAddressIsNilQ = pattern.MustParse(
+       `(BinaryExpr
+               (UnaryExpr "&" _)
+               (Or "==" "!=")
+               (Builtin "nil"))`)
+
+func CheckAddressIsNil(pass *analysis.Pass) (interface{}, error) {
+       fn := func(node ast.Node) {
+               _, ok := Match(pass, checkAddressIsNilQ, node)
+               if !ok {
+                       return
+               }
+               report.Report(pass, node, "the address of a variable cannot be nil")
+       }
+       code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
+       return nil, nil
+}