govet: add more analyzers

This commit is contained in:
Aleksandr Razumov 2019-09-29 00:56:21 +03:00 committed by Trevor Pounds
parent 6cc10f0615
commit 7e09842e2b
7 changed files with 737 additions and 0 deletions

View File

@ -13,18 +13,26 @@ import (
"golang.org/x/tools/go/analysis/passes/atomic"
"golang.org/x/tools/go/analysis/passes/atomicalign"
"golang.org/x/tools/go/analysis/passes/bools"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/analysis/passes/buildtag"
"golang.org/x/tools/go/analysis/passes/cgocall"
"golang.org/x/tools/go/analysis/passes/composite"
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
"golang.org/x/tools/go/analysis/passes/deepequalerrors"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/loopclosure"
"golang.org/x/tools/go/analysis/passes/lostcancel"
"golang.org/x/tools/go/analysis/passes/nilfunc"
"golang.org/x/tools/go/analysis/passes/nilness"
"golang.org/x/tools/go/analysis/passes/pkgfact"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/passes/shadow"
"golang.org/x/tools/go/analysis/passes/shift"
"golang.org/x/tools/go/analysis/passes/sortslice"
"golang.org/x/tools/go/analysis/passes/stdmethods"
"golang.org/x/tools/go/analysis/passes/structtag"
"golang.org/x/tools/go/analysis/passes/tests"
@ -41,18 +49,26 @@ func getAllAnalyzers() []*analysis.Analyzer {
atomic.Analyzer,
atomicalign.Analyzer,
bools.Analyzer,
buildssa.Analyzer,
buildtag.Analyzer,
cgocall.Analyzer,
composite.Analyzer,
copylock.Analyzer,
ctrlflow.Analyzer,
deepequalerrors.Analyzer,
errorsas.Analyzer,
findcall.Analyzer,
httpresponse.Analyzer,
inspect.Analyzer,
loopclosure.Analyzer,
lostcancel.Analyzer,
nilfunc.Analyzer,
nilness.Analyzer,
pkgfact.Analyzer,
printf.Analyzer,
shadow.Analyzer,
shift.Analyzer,
sortslice.Analyzer,
stdmethods.Analyzer,
structtag.Analyzer,
tests.Analyzer,

View File

@ -0,0 +1,115 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package deepequalerrors defines an Analyzer that checks for the use
// of reflect.DeepEqual with error values.
package deepequalerrors
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
const Doc = `check for calls of reflect.DeepEqual on error values
The deepequalerrors checker looks for calls of the form:
reflect.DeepEqual(err1, err2)
where err1 and err2 are errors. Using reflect.DeepEqual to compare
errors is discouraged.`
var Analyzer = &analysis.Analyzer{
Name: "deepequalerrors",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if !ok {
return
}
if fn.FullName() == "reflect.DeepEqual" && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
pass.Reportf(call.Pos(), "avoid using reflect.DeepEqual with errors")
}
})
return nil, nil
}
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
// hasError reports whether the type of e contains the type error.
// See containsError, below, for the meaning of "contains".
func hasError(pass *analysis.Pass, e ast.Expr) bool {
tv, ok := pass.TypesInfo.Types[e]
if !ok { // no type info, assume good
return false
}
return containsError(tv.Type)
}
// Report whether any type that typ could store and that could be compared is the
// error type. This includes typ itself, as well as the types of struct field, slice
// and array elements, map keys and elements, and pointers. It does not include
// channel types (incomparable), arg and result types of a Signature (not stored), or
// methods of a named or interface type (not stored).
func containsError(typ types.Type) bool {
// Track types being processed, to avoid infinite recursion.
// Using types as keys here is OK because we are checking for the identical pointer, not
// type identity. See analysis/passes/printf/types.go.
inProgress := make(map[types.Type]bool)
var check func(t types.Type) bool
check = func(t types.Type) bool {
if t == errorType {
return true
}
if inProgress[t] {
return false
}
inProgress[t] = true
switch t := t.(type) {
case *types.Pointer:
return check(t.Elem())
case *types.Slice:
return check(t.Elem())
case *types.Array:
return check(t.Elem())
case *types.Map:
return check(t.Key()) || check(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if check(t.Field(i).Type()) {
return true
}
}
case *types.Named:
return check(t.Underlying())
// We list the remaining valid type kinds for completeness.
case *types.Basic:
case *types.Chan: // channels store values, but they are not comparable
case *types.Signature:
case *types.Tuple: // tuples are only part of signatures
case *types.Interface:
}
return false
}
return check(typ)
}

View File

@ -0,0 +1,80 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The findcall package defines an Analyzer that serves as a trivial
// example and test of the Analysis API. It reports a diagnostic for
// every call to a function or method of the name specified by its
// -name flag.
package findcall
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
)
const Doc = `find calls to a particular function
The findcall analysis reports calls to functions or methods
of a particular name.`
var Analyzer = &analysis.Analyzer{
Name: "findcall",
Doc: Doc,
Run: run,
RunDespiteErrors: true,
FactTypes: []analysis.Fact{new(foundFact)},
}
var name string // -name flag
func init() {
Analyzer.Flags.StringVar(&name, "name", name, "name of the function to find")
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
ast.Inspect(f, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
var id *ast.Ident
switch fun := call.Fun.(type) {
case *ast.Ident:
id = fun
case *ast.SelectorExpr:
id = fun.Sel
}
if id != nil && !pass.TypesInfo.Types[id].IsType() && id.Name == name {
pass.Reportf(call.Lparen, "call of %s(...)", id.Name)
}
}
return true
})
}
// Export a fact for each matching function.
//
// These facts are produced only to test the testing
// infrastructure in the analysistest package.
// They are not consumed by the findcall Analyzer
// itself, as would happen in a more realistic example.
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name.Name == name {
if obj, ok := pass.TypesInfo.Defs[decl.Name].(*types.Func); ok {
pass.ExportObjectFact(obj, new(foundFact))
}
}
}
}
return nil, nil
}
// foundFact is a fact associated with functions that match -name.
// We use it to exercise the fact machinery in tests.
type foundFact struct{}
func (*foundFact) String() string { return "found" }
func (*foundFact) AFact() {}

View File

@ -0,0 +1,271 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package nilness inspects the control-flow graph of an SSA function
// and reports errors such as nil pointer dereferences and degenerate
// nil pointer comparisons.
package nilness
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
)
const Doc = `check for redundant or impossible nil comparisons
The nilness checker inspects the control-flow graph of each function in
a package and reports nil pointer dereferences and degenerate nil
pointers. A degenerate comparison is of the form x==nil or x!=nil where x
is statically known to be nil or non-nil. These are often a mistake,
especially in control flow related to errors.
This check reports conditions such as:
if f == nil { // impossible condition (f is a function)
}
and:
p := &v
...
if p != nil { // tautological condition
}
and:
if p == nil {
print(*p) // nil dereference
}
`
var Analyzer = &analysis.Analyzer{
Name: "nilness",
Doc: Doc,
Run: run,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}
func run(pass *analysis.Pass) (interface{}, error) {
ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
for _, fn := range ssainput.SrcFuncs {
runFunc(pass, fn)
}
return nil, nil
}
func runFunc(pass *analysis.Pass, fn *ssa.Function) {
reportf := func(category string, pos token.Pos, format string, args ...interface{}) {
pass.Report(analysis.Diagnostic{
Pos: pos,
Category: category,
Message: fmt.Sprintf(format, args...),
})
}
// notNil reports an error if v is provably nil.
notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
if nilnessOf(stack, v) == isnil {
reportf("nilderef", instr.Pos(), "nil dereference in "+descr)
}
}
// visit visits reachable blocks of the CFG in dominance order,
// maintaining a stack of dominating nilness facts.
//
// By traversing the dom tree, we can pop facts off the stack as
// soon as we've visited a subtree. Had we traversed the CFG,
// we would need to retain the set of facts for each block.
seen := make([]bool, len(fn.Blocks)) // seen[i] means visit should ignore block i
var visit func(b *ssa.BasicBlock, stack []fact)
visit = func(b *ssa.BasicBlock, stack []fact) {
if seen[b.Index] {
return
}
seen[b.Index] = true
// Report nil dereferences.
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case ssa.CallInstruction:
notNil(stack, instr, instr.Common().Value,
instr.Common().Description())
case *ssa.FieldAddr:
notNil(stack, instr, instr.X, "field selection")
case *ssa.IndexAddr:
notNil(stack, instr, instr.X, "index operation")
case *ssa.MapUpdate:
notNil(stack, instr, instr.Map, "map update")
case *ssa.Slice:
// A nilcheck occurs in ptr[:] iff ptr is a pointer to an array.
if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
notNil(stack, instr, instr.X, "slice operation")
}
case *ssa.Store:
notNil(stack, instr, instr.Addr, "store")
case *ssa.TypeAssert:
notNil(stack, instr, instr.X, "type assertion")
case *ssa.UnOp:
if instr.Op == token.MUL { // *X
notNil(stack, instr, instr.X, "load")
}
}
}
// For nil comparison blocks, report an error if the condition
// is degenerate, and push a nilness fact on the stack when
// visiting its true and false successor blocks.
if binop, tsucc, fsucc := eq(b); binop != nil {
xnil := nilnessOf(stack, binop.X)
ynil := nilnessOf(stack, binop.Y)
if ynil != unknown && xnil != unknown && (xnil == isnil || ynil == isnil) {
// Degenerate condition:
// the nilness of both operands is known,
// and at least one of them is nil.
var adj string
if (xnil == ynil) == (binop.Op == token.EQL) {
adj = "tautological"
} else {
adj = "impossible"
}
reportf("cond", binop.Pos(), "%s condition: %s %s %s", adj, xnil, binop.Op, ynil)
// If tsucc's or fsucc's sole incoming edge is impossible,
// it is unreachable. Prune traversal of it and
// all the blocks it dominates.
// (We could be more precise with full dataflow
// analysis of control-flow joins.)
var skip *ssa.BasicBlock
if xnil == ynil {
skip = fsucc
} else {
skip = tsucc
}
for _, d := range b.Dominees() {
if d == skip && len(d.Preds) == 1 {
continue
}
visit(d, stack)
}
return
}
// "if x == nil" or "if nil == y" condition; x, y are unknown.
if xnil == isnil || ynil == isnil {
var f fact
if xnil == isnil {
// x is nil, y is unknown:
// t successor learns y is nil.
f = fact{binop.Y, isnil}
} else {
// x is nil, y is unknown:
// t successor learns x is nil.
f = fact{binop.X, isnil}
}
for _, d := range b.Dominees() {
// Successor blocks learn a fact
// only at non-critical edges.
// (We could do be more precise with full dataflow
// analysis of control-flow joins.)
s := stack
if len(d.Preds) == 1 {
if d == tsucc {
s = append(s, f)
} else if d == fsucc {
s = append(s, f.negate())
}
}
visit(d, s)
}
return
}
}
for _, d := range b.Dominees() {
visit(d, stack)
}
}
// Visit the entry block. No need to visit fn.Recover.
if fn.Blocks != nil {
visit(fn.Blocks[0], make([]fact, 0, 20)) // 20 is plenty
}
}
// A fact records that a block is dominated
// by the condition v == nil or v != nil.
type fact struct {
value ssa.Value
nilness nilness
}
func (f fact) negate() fact { return fact{f.value, -f.nilness} }
type nilness int
const (
isnonnil = -1
unknown nilness = 0
isnil = 1
)
var nilnessStrings = []string{"non-nil", "unknown", "nil"}
func (n nilness) String() string { return nilnessStrings[n+1] }
// nilnessOf reports whether v is definitely nil, definitely not nil,
// or unknown given the dominating stack of facts.
func nilnessOf(stack []fact, v ssa.Value) nilness {
// Is value intrinsically nil or non-nil?
switch v := v.(type) {
case *ssa.Alloc,
*ssa.FieldAddr,
*ssa.FreeVar,
*ssa.Function,
*ssa.Global,
*ssa.IndexAddr,
*ssa.MakeChan,
*ssa.MakeClosure,
*ssa.MakeInterface,
*ssa.MakeMap,
*ssa.MakeSlice:
return isnonnil
case *ssa.Const:
if v.IsNil() {
return isnil
} else {
return isnonnil
}
}
// Search dominating control-flow facts.
for _, f := range stack {
if f.value == v {
return f.nilness
}
}
return unknown
}
// If b ends with an equality comparison, eq returns the operation and
// its true (equal) and false (not equal) successors.
func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {
if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
if binop, ok := If.Cond.(*ssa.BinOp); ok {
switch binop.Op {
case token.EQL:
return binop, b.Succs[0], b.Succs[1]
case token.NEQ:
return binop, b.Succs[1], b.Succs[0]
}
}
}
return nil, nil, nil
}

View File

@ -0,0 +1,127 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The pkgfact package is a demonstration and test of the package fact
// mechanism.
//
// The output of the pkgfact analysis is a set of key/values pairs
// gathered from the analyzed package and its imported dependencies.
// Each key/value pair comes from a top-level constant declaration
// whose name starts and ends with "_". For example:
//
// package p
//
// const _greeting_ = "hello"
// const _audience_ = "world"
//
// the pkgfact analysis output for package p would be:
//
// {"greeting": "hello", "audience": "world"}.
//
// In addition, the analysis reports a diagnostic at each import
// showing which key/value pairs it contributes.
package pkgfact
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "pkgfact",
Doc: "gather name/value pairs from constant declarations",
Run: run,
FactTypes: []analysis.Fact{new(pairsFact)},
ResultType: reflect.TypeOf(map[string]string{}),
}
// A pairsFact is a package-level fact that records
// an set of key=value strings accumulated from constant
// declarations in this package and its dependencies.
// Elements are ordered by keys, which are unique.
type pairsFact []string
func (f *pairsFact) AFact() {}
func (f *pairsFact) String() string { return "pairs(" + strings.Join(*f, ", ") + ")" }
func run(pass *analysis.Pass) (interface{}, error) {
result := make(map[string]string)
// At each import, print the fact from the imported
// package and accumulate its information into the result.
// (Warning: accumulation leads to quadratic growth of work.)
doImport := func(spec *ast.ImportSpec) {
pkg := imported(pass.TypesInfo, spec)
var fact pairsFact
if pass.ImportPackageFact(pkg, &fact) {
for _, pair := range fact {
eq := strings.IndexByte(pair, '=')
result[pair[:eq]] = pair[1+eq:]
}
pass.Reportf(spec.Pos(), "%s", strings.Join(fact, " "))
}
}
// At each "const _name_ = value", add a fact into env.
doConst := func(spec *ast.ValueSpec) {
if len(spec.Names) == len(spec.Values) {
for i := range spec.Names {
name := spec.Names[i].Name
if strings.HasPrefix(name, "_") && strings.HasSuffix(name, "_") {
if key := strings.Trim(name, "_"); key != "" {
value := pass.TypesInfo.Types[spec.Values[i]].Value.String()
result[key] = value
}
}
}
}
}
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.GenDecl); ok {
for _, spec := range decl.Specs {
switch decl.Tok {
case token.IMPORT:
doImport(spec.(*ast.ImportSpec))
case token.CONST:
doConst(spec.(*ast.ValueSpec))
}
}
}
}
}
// Sort/deduplicate the result and save it as a package fact.
keys := make([]string, 0, len(result))
for key := range result {
keys = append(keys, key)
}
sort.Strings(keys)
var fact pairsFact
for _, key := range keys {
fact = append(fact, fmt.Sprintf("%s=%s", key, result[key]))
}
if len(fact) > 0 {
pass.ExportPackageFact(&fact)
}
return result, nil
}
func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
obj, ok := info.Implicits[spec]
if !ok {
obj = info.Defs[spec.Name] // renaming import
}
return obj.(*types.PkgName).Imported()
}

View File

@ -0,0 +1,123 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sortslice defines an Analyzer that checks for calls
// to sort.Slice that do not use a slice type as first argument.
package sortslice
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
const Doc = `check the argument type of sort.Slice
sort.Slice requires an argument of a slice type. Check that
the interface{} value passed to sort.Slice is actually a slice.`
var Analyzer = &analysis.Analyzer{
Name: "sortslice",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if fn == nil {
return
}
if fn.FullName() != "sort.Slice" {
return
}
arg := call.Args[0]
typ := pass.TypesInfo.Types[arg].Type
switch typ.Underlying().(type) {
case *types.Slice, *types.Interface:
return
}
var fixes []analysis.SuggestedFix
switch v := typ.Underlying().(type) {
case *types.Array:
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.SliceExpr{
X: arg,
Slice3: false,
Lbrack: arg.End() + 1,
Rbrack: arg.End() + 3,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Get a slice of the full array",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
case *types.Pointer:
_, ok := v.Elem().Underlying().(*types.Slice)
if !ok {
break
}
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.StarExpr{
X: arg,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Dereference the pointer to the slice",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
case *types.Signature:
if v.Params().Len() != 0 || v.Results().Len() != 1 {
break
}
if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
break
}
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.CallExpr{
Fun: arg,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Call the function",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
}
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf("sort.Slice's argument must be a slice; is called with %s", typ.String()),
SuggestedFixes: fixes,
})
})
return nil, nil
}

5
vendor/modules.txt vendored
View File

@ -194,16 +194,21 @@ golang.org/x/tools/go/analysis/passes/cgocall
golang.org/x/tools/go/analysis/passes/composite
golang.org/x/tools/go/analysis/passes/copylock
golang.org/x/tools/go/analysis/passes/ctrlflow
golang.org/x/tools/go/analysis/passes/deepequalerrors
golang.org/x/tools/go/analysis/passes/errorsas
golang.org/x/tools/go/analysis/passes/findcall
golang.org/x/tools/go/analysis/passes/httpresponse
golang.org/x/tools/go/analysis/passes/inspect
golang.org/x/tools/go/analysis/passes/internal/analysisutil
golang.org/x/tools/go/analysis/passes/loopclosure
golang.org/x/tools/go/analysis/passes/lostcancel
golang.org/x/tools/go/analysis/passes/nilfunc
golang.org/x/tools/go/analysis/passes/nilness
golang.org/x/tools/go/analysis/passes/pkgfact
golang.org/x/tools/go/analysis/passes/printf
golang.org/x/tools/go/analysis/passes/shadow
golang.org/x/tools/go/analysis/passes/shift
golang.org/x/tools/go/analysis/passes/sortslice
golang.org/x/tools/go/analysis/passes/stdmethods
golang.org/x/tools/go/analysis/passes/structtag
golang.org/x/tools/go/analysis/passes/tests