sa2002.go raw
1 package sa2002
2
3 import (
4 "fmt"
5 "go/types"
6
7 "honnef.co/go/tools/analysis/lint"
8 "honnef.co/go/tools/analysis/report"
9 "honnef.co/go/tools/go/ir"
10 "honnef.co/go/tools/go/types/typeutil"
11 "honnef.co/go/tools/internal/passes/buildir"
12
13 "golang.org/x/tools/go/analysis"
14 )
15
16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
17 Analyzer: &analysis.Analyzer{
18 Name: "SA2002",
19 Run: run,
20 Requires: []*analysis.Analyzer{buildir.Analyzer},
21 },
22 Doc: &lint.RawDocumentation{
23 Title: `Called \'testing.T.FailNow\' or \'SkipNow\' in a goroutine, which isn't allowed`,
24 Since: "2017.1",
25 Severity: lint.SeverityError,
26 MergeIf: lint.MergeIfAny,
27 },
28 })
29
30 var Analyzer = SCAnalyzer.Analyzer
31
32 func run(pass *analysis.Pass) (interface{}, error) {
33 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
34 for _, block := range fn.Blocks {
35 for _, ins := range block.Instrs {
36 gostmt, ok := ins.(*ir.Go)
37 if !ok {
38 continue
39 }
40 var fn *ir.Function
41 switch val := gostmt.Call.Value.(type) {
42 case *ir.Function:
43 fn = val
44 case *ir.MakeClosure:
45 fn = val.Fn.(*ir.Function)
46 default:
47 continue
48 }
49 if fn.Blocks == nil {
50 continue
51 }
52 for _, block := range fn.Blocks {
53 for _, ins := range block.Instrs {
54 call, ok := ins.(*ir.Call)
55 if !ok {
56 continue
57 }
58 if call.Call.IsInvoke() {
59 continue
60 }
61 callee := call.Call.StaticCallee()
62 if callee == nil {
63 continue
64 }
65 recv := callee.Signature.Recv()
66 if recv == nil {
67 continue
68 }
69 if !typeutil.IsPointerToTypeWithName(recv.Type(), "testing.common") {
70 continue
71 }
72 fn, ok := call.Call.StaticCallee().Object().(*types.Func)
73 if !ok {
74 continue
75 }
76 name := fn.Name()
77 switch name {
78 case "FailNow", "Fatal", "Fatalf", "SkipNow", "Skip", "Skipf":
79 default:
80 continue
81 }
82 // TODO(dh): don't report multiple diagnostics
83 // for multiple calls to T.Fatal, but do
84 // collect all of them as related information
85 report.Report(pass, gostmt, fmt.Sprintf("the goroutine calls T.%s, which must be called in the same goroutine as the test", name),
86 report.Related(call, fmt.Sprintf("call to T.%s", name)))
87 }
88 }
89 }
90 }
91 }
92 return nil, nil
93 }
94