sa4017.go raw

   1  package sa4017
   2  
   3  import (
   4  	"fmt"
   5  	"go/types"
   6  
   7  	"honnef.co/go/tools/analysis/code"
   8  	"honnef.co/go/tools/analysis/facts/purity"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  	"honnef.co/go/tools/go/ir"
  12  	"honnef.co/go/tools/go/ir/irutil"
  13  	"honnef.co/go/tools/go/types/typeutil"
  14  	"honnef.co/go/tools/internal/passes/buildir"
  15  
  16  	"golang.org/x/tools/go/analysis"
  17  )
  18  
  19  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  20  	Analyzer: &analysis.Analyzer{
  21  		Name:     "SA4017",
  22  		Run:      run,
  23  		Requires: []*analysis.Analyzer{buildir.Analyzer, purity.Analyzer},
  24  	},
  25  	Doc: &lint.RawDocumentation{
  26  		Title:    `Discarding the return values of a function without side effects, making the call pointless`,
  27  		Since:    "2017.1",
  28  		Severity: lint.SeverityWarning,
  29  		MergeIf:  lint.MergeIfAll,
  30  	},
  31  })
  32  
  33  var Analyzer = SCAnalyzer.Analyzer
  34  
  35  func run(pass *analysis.Pass) (interface{}, error) {
  36  	pure := pass.ResultOf[purity.Analyzer].(purity.Result)
  37  
  38  fnLoop:
  39  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  40  		if code.IsInTest(pass, fn) {
  41  			params := fn.Signature.Params()
  42  			for i := 0; i < params.Len(); i++ {
  43  				param := params.At(i)
  44  				if typeutil.IsPointerToTypeWithName(param.Type(), "testing.B") {
  45  					// Ignore discarded pure functions in code related
  46  					// to benchmarks. Instead of matching BenchmarkFoo
  47  					// functions, we match any function accepting a
  48  					// *testing.B. Benchmarks sometimes call generic
  49  					// functions for doing the actual work, and
  50  					// checking for the parameter is a lot easier and
  51  					// faster than analyzing call trees.
  52  					continue fnLoop
  53  				}
  54  			}
  55  		}
  56  
  57  		for _, b := range fn.Blocks {
  58  			for _, ins := range b.Instrs {
  59  				ins, ok := ins.(*ir.Call)
  60  				if !ok {
  61  					continue
  62  				}
  63  				refs := ins.Referrers()
  64  				if refs == nil || len(irutil.FilterDebug(*refs)) > 0 {
  65  					continue
  66  				}
  67  
  68  				callee := ins.Common().StaticCallee()
  69  				if callee == nil {
  70  					continue
  71  				}
  72  				if callee.Object() == nil {
  73  					// TODO(dh): support anonymous functions
  74  					continue
  75  				}
  76  				if _, ok := pure[callee.Object().(*types.Func)]; ok {
  77  					if pass.Pkg.Path() == "fmt_test" && callee.Object().(*types.Func).FullName() == "fmt.Sprintf" {
  78  						// special case for benchmarks in the fmt package
  79  						continue
  80  					}
  81  					report.Report(pass, ins, fmt.Sprintf("%s doesn't have side effects and its return value is ignored", callee.Object().Name()))
  82  				}
  83  			}
  84  		}
  85  	}
  86  	return nil, nil
  87  }
  88