github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/unusedresult/unusedresult.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package unusedresult defines an analyzer that checks for unused 6 // results of calls to certain pure functions. 7 package unusedresult 8 9 import ( 10 "go/ast" 11 "go/token" 12 "go/types" 13 "sort" 14 "strings" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 19 "golang.org/x/tools/go/ast/inspector" 20 "golang.org/x/tools/internal/typeparams" 21 ) 22 23 // TODO(adonovan): make this analysis modular: export a mustUseResult 24 // fact for each function that tail-calls one of the functions that we 25 // check, and check those functions too. 26 27 const Doc = `check for unused results of calls to some functions 28 29 Some functions like fmt.Errorf return a result and have no side effects, 30 so it is always a mistake to discard the result. This analyzer reports 31 calls to certain functions in which the result of the call is ignored. 32 33 The set of functions may be controlled using flags.` 34 35 var Analyzer = &analysis.Analyzer{ 36 Name: "unusedresult", 37 Doc: Doc, 38 Requires: []*analysis.Analyzer{inspect.Analyzer}, 39 Run: run, 40 } 41 42 // flags 43 var funcs, stringMethods stringSetFlag 44 45 func init() { 46 // TODO(adonovan): provide a comment syntax to allow users to 47 // add their functions to this set using facts. 48 funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout") 49 Analyzer.Flags.Var(&funcs, "funcs", 50 "comma-separated list of functions whose results must be used") 51 52 stringMethods.Set("Error,String") 53 Analyzer.Flags.Var(&stringMethods, "stringmethods", 54 "comma-separated list of names of methods of type func() string whose results must be used") 55 } 56 57 func run(pass *analysis.Pass) (interface{}, error) { 58 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 59 60 nodeFilter := []ast.Node{ 61 (*ast.ExprStmt)(nil), 62 } 63 inspect.Preorder(nodeFilter, func(n ast.Node) { 64 call, ok := analysisutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) 65 if !ok { 66 return // not a call statement 67 } 68 fun := analysisutil.Unparen(call.Fun) 69 70 if pass.TypesInfo.Types[fun].IsType() { 71 return // a conversion, not a call 72 } 73 74 x, _, _, _ := typeparams.UnpackIndexExpr(fun) 75 if x != nil { 76 fun = x // If this is generic function or method call, skip the instantiation arguments 77 } 78 79 selector, ok := fun.(*ast.SelectorExpr) 80 if !ok { 81 return // neither a method call nor a qualified ident 82 } 83 84 sel, ok := pass.TypesInfo.Selections[selector] 85 if ok && sel.Kind() == types.MethodVal { 86 // method (e.g. foo.String()) 87 obj := sel.Obj().(*types.Func) 88 sig := sel.Type().(*types.Signature) 89 if types.Identical(sig, sigNoArgsStringResult) { 90 if stringMethods[obj.Name()] { 91 pass.Reportf(call.Lparen, "result of (%s).%s call not used", 92 sig.Recv().Type(), obj.Name()) 93 } 94 } 95 } else if !ok { 96 // package-qualified function (e.g. fmt.Errorf) 97 obj := pass.TypesInfo.Uses[selector.Sel] 98 if obj, ok := obj.(*types.Func); ok { 99 qname := obj.Pkg().Path() + "." + obj.Name() 100 if funcs[qname] { 101 pass.Reportf(call.Lparen, "result of %v call not used", qname) 102 } 103 } 104 } 105 }) 106 return nil, nil 107 } 108 109 // func() string 110 var sigNoArgsStringResult = types.NewSignature(nil, nil, 111 types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), 112 false) 113 114 type stringSetFlag map[string]bool 115 116 func (ss *stringSetFlag) String() string { 117 var items []string 118 for item := range *ss { 119 items = append(items, item) 120 } 121 sort.Strings(items) 122 return strings.Join(items, ",") 123 } 124 125 func (ss *stringSetFlag) Set(s string) error { 126 m := make(map[string]bool) // clobber previous value 127 if s != "" { 128 for _, name := range strings.Split(s, ",") { 129 if name == "" { 130 continue // TODO: report error? proceed? 131 } 132 m[name] = true 133 } 134 } 135 *ss = m 136 return nil 137 }