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