.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / go / analysis / passes / sigchanyzer / sigchanyzer.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package sigchanyzer defines an Analyzer that detects
6 // misuse of unbuffered signal as argument to signal.Notify.
7 package sigchanyzer
8
9 import (
10         "bytes"
11         "go/ast"
12         "go/format"
13         "go/token"
14         "go/types"
15
16         "golang.org/x/tools/go/analysis"
17         "golang.org/x/tools/go/analysis/passes/inspect"
18         "golang.org/x/tools/go/ast/inspector"
19 )
20
21 const Doc = `check for unbuffered channel of os.Signal
22
23 This checker reports call expression of the form signal.Notify(c <-chan os.Signal, sig ...os.Signal),
24 where c is an unbuffered channel, which can be at risk of missing the signal.`
25
26 // Analyzer describes sigchanyzer analysis function detector.
27 var Analyzer = &analysis.Analyzer{
28         Name:     "sigchanyzer",
29         Doc:      Doc,
30         Requires: []*analysis.Analyzer{inspect.Analyzer},
31         Run:      run,
32 }
33
34 func run(pass *analysis.Pass) (interface{}, error) {
35         inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36
37         nodeFilter := []ast.Node{
38                 (*ast.CallExpr)(nil),
39         }
40         inspect.Preorder(nodeFilter, func(n ast.Node) {
41                 call := n.(*ast.CallExpr)
42                 if !isSignalNotify(pass.TypesInfo, call) {
43                         return
44                 }
45                 var chanDecl *ast.CallExpr
46                 switch arg := call.Args[0].(type) {
47                 case *ast.Ident:
48                         if decl, ok := findDecl(arg).(*ast.CallExpr); ok {
49                                 chanDecl = decl
50                         }
51                 case *ast.CallExpr:
52                         chanDecl = arg
53                 }
54                 if chanDecl == nil || len(chanDecl.Args) != 1 {
55                         return
56                 }
57                 chanDecl.Args = append(chanDecl.Args, &ast.BasicLit{
58                         Kind:  token.INT,
59                         Value: "1",
60                 })
61                 var buf bytes.Buffer
62                 if err := format.Node(&buf, token.NewFileSet(), chanDecl); err != nil {
63                         return
64                 }
65                 pass.Report(analysis.Diagnostic{
66                         Pos:     call.Pos(),
67                         End:     call.End(),
68                         Message: "misuse of unbuffered os.Signal channel as argument to signal.Notify",
69                         SuggestedFixes: []analysis.SuggestedFix{{
70                                 Message: "Change to buffer channel",
71                                 TextEdits: []analysis.TextEdit{{
72                                         Pos:     chanDecl.Pos(),
73                                         End:     chanDecl.End(),
74                                         NewText: buf.Bytes(),
75                                 }},
76                         }},
77                 })
78         })
79         return nil, nil
80 }
81
82 func isSignalNotify(info *types.Info, call *ast.CallExpr) bool {
83         check := func(id *ast.Ident) bool {
84                 obj := info.ObjectOf(id)
85                 return obj.Name() == "Notify" && obj.Pkg().Path() == "os/signal"
86         }
87         switch fun := call.Fun.(type) {
88         case *ast.SelectorExpr:
89                 return check(fun.Sel)
90         case *ast.Ident:
91                 if fun, ok := findDecl(fun).(*ast.SelectorExpr); ok {
92                         return check(fun.Sel)
93                 }
94                 return false
95         default:
96                 return false
97         }
98 }
99
100 func findDecl(arg *ast.Ident) ast.Node {
101         if arg.Obj == nil {
102                 return nil
103         }
104         switch as := arg.Obj.Decl.(type) {
105         case *ast.AssignStmt:
106                 if len(as.Lhs) != len(as.Rhs) {
107                         return nil
108                 }
109                 for i, lhs := range as.Lhs {
110                         lid, ok := lhs.(*ast.Ident)
111                         if !ok {
112                                 continue
113                         }
114                         if lid.Obj == arg.Obj {
115                                 return as.Rhs[i]
116                         }
117                 }
118         case *ast.ValueSpec:
119                 if len(as.Names) != len(as.Values) {
120                         return nil
121                 }
122                 for i, name := range as.Names {
123                         if name.Obj == arg.Obj {
124                                 return as.Values[i]
125                         }
126                 }
127         }
128         return nil
129 }