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