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 }