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.
5 // Package unusedresult defines an analyzer that checks for unused
6 // results of calls to certain pure functions.
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"
22 // TODO(adonovan): make this analysis modular: export a mustUseResult
23 // fact for each function that tail-calls one of the functions that we
24 // check, and check those functions too.
26 const Doc = `check for unused results of calls to some functions
28 Some functions like fmt.Errorf return a result and have no side effects,
29 so it is always a mistake to discard the result. This analyzer reports
30 calls to certain functions in which the result of the call is ignored.
32 The set of functions may be controlled using flags.`
34 var Analyzer = &analysis.Analyzer{
37 Requires: []*analysis.Analyzer{inspect.Analyzer},
42 var funcs, stringMethods stringSetFlag
45 // TODO(adonovan): provide a comment syntax to allow users to
46 // add their functions to this set using facts.
47 funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout")
48 Analyzer.Flags.Var(&funcs, "funcs",
49 "comma-separated list of functions whose results must be used")
51 stringMethods.Set("Error,String")
52 Analyzer.Flags.Var(&stringMethods, "stringmethods",
53 "comma-separated list of names of methods of type func() string whose results must be used")
56 func run(pass *analysis.Pass) (interface{}, error) {
57 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
59 nodeFilter := []ast.Node{
62 inspect.Preorder(nodeFilter, func(n ast.Node) {
63 call, ok := analysisutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
65 return // not a call statement
67 fun := analysisutil.Unparen(call.Fun)
69 if pass.TypesInfo.Types[fun].IsType() {
70 return // a conversion, not a call
73 selector, ok := fun.(*ast.SelectorExpr)
75 return // neither a method call nor a qualified ident
78 sel, ok := pass.TypesInfo.Selections[selector]
79 if ok && sel.Kind() == types.MethodVal {
80 // method (e.g. foo.String())
81 obj := sel.Obj().(*types.Func)
82 sig := sel.Type().(*types.Signature)
83 if types.Identical(sig, sigNoArgsStringResult) {
84 if stringMethods[obj.Name()] {
85 pass.Reportf(call.Lparen, "result of (%s).%s call not used",
86 sig.Recv().Type(), obj.Name())
90 // package-qualified function (e.g. fmt.Errorf)
91 obj := pass.TypesInfo.Uses[selector.Sel]
92 if obj, ok := obj.(*types.Func); ok {
93 qname := obj.Pkg().Path() + "." + obj.Name()
95 pass.Reportf(call.Lparen, "result of %v call not used", qname)
104 var sigNoArgsStringResult = types.NewSignature(nil, nil,
105 types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
108 type stringSetFlag map[string]bool
110 func (ss *stringSetFlag) String() string {
112 for item := range *ss {
113 items = append(items, item)
116 return strings.Join(items, ",")
119 func (ss *stringSetFlag) Set(s string) error {
120 m := make(map[string]bool) // clobber previous value
122 for _, name := range strings.Split(s, ",") {
124 continue // TODO: report error? proceed?