github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4017/sa4017.go (about)

     1  package sa4017
     2  
     3  import (
     4  	"fmt"
     5  	"go/types"
     6  
     7  	"github.com/amarpal/go-tools/analysis/code"
     8  	"github.com/amarpal/go-tools/analysis/facts/purity"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/go/ir"
    12  	"github.com/amarpal/go-tools/go/ir/irutil"
    13  	"github.com/amarpal/go-tools/go/types/typeutil"
    14  	"github.com/amarpal/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.Documentation{
    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.IsType(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  }