// Copyright 2015 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 unusedresult defines an analyzer that checks for unused // results of calls to certain pure functions. package unusedresult import ( "go/ast" "go/token" "go/types" "sort" "strings" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/internal/typeparams" ) // TODO(adonovan): make this analysis modular: export a mustUseResult // fact for each function that tail-calls one of the functions that we // check, and check those functions too. const Doc = `check for unused results of calls to some functions Some functions like fmt.Errorf return a result and have no side effects, so it is always a mistake to discard the result. This analyzer reports calls to certain functions in which the result of the call is ignored. The set of functions may be controlled using flags.` var Analyzer = &analysis.Analyzer{ Name: "unusedresult", Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, } // flags var funcs, stringMethods stringSetFlag func init() { // TODO(adonovan): provide a comment syntax to allow users to // add their functions to this set using facts. funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout") Analyzer.Flags.Var(&funcs, "funcs", "comma-separated list of functions whose results must be used") stringMethods.Set("Error,String") Analyzer.Flags.Var(&stringMethods, "stringmethods", "comma-separated list of names of methods of type func() string whose results must be used") } func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.ExprStmt)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { call, ok := analysisutil.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) if !ok { return // not a call statement } fun := analysisutil.Unparen(call.Fun) if pass.TypesInfo.Types[fun].IsType() { return // a conversion, not a call } x, _, _, _ := typeparams.UnpackIndexExpr(fun) if x != nil { fun = x // If this is generic function or method call, skip the instantiation arguments } selector, ok := fun.(*ast.SelectorExpr) if !ok { return // neither a method call nor a qualified ident } sel, ok := pass.TypesInfo.Selections[selector] if ok && sel.Kind() == types.MethodVal { // method (e.g. foo.String()) obj := sel.Obj().(*types.Func) sig := sel.Type().(*types.Signature) if types.Identical(sig, sigNoArgsStringResult) { if stringMethods[obj.Name()] { pass.Reportf(call.Lparen, "result of (%s).%s call not used", sig.Recv().Type(), obj.Name()) } } } else if !ok { // package-qualified function (e.g. fmt.Errorf) obj := pass.TypesInfo.Uses[selector.Sel] if obj, ok := obj.(*types.Func); ok { qname := obj.Pkg().Path() + "." + obj.Name() if funcs[qname] { pass.Reportf(call.Lparen, "result of %v call not used", qname) } } } }) return nil, nil } // func() string var sigNoArgsStringResult = types.NewSignature(nil, nil, types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), false) type stringSetFlag map[string]bool func (ss *stringSetFlag) String() string { var items []string for item := range *ss { items = append(items, item) } sort.Strings(items) return strings.Join(items, ",") } func (ss *stringSetFlag) Set(s string) error { m := make(map[string]bool) // clobber previous value if s != "" { for _, name := range strings.Split(s, ",") { if name == "" { continue // TODO: report error? proceed? } m[name] = true } } *ss = m return nil }