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.
5 // Package sigchanyzer defines an Analyzer that detects
6 // misuse of unbuffered signal as argument to signal.Notify.
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/passes/inspect"
18 "golang.org/x/tools/go/ast/inspector"
21 const Doc = `check for unbuffered channel of os.Signal
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.`
26 // Analyzer describes sigchanyzer analysis function detector.
27 var Analyzer = &analysis.Analyzer{
30 Requires: []*analysis.Analyzer{inspect.Analyzer},
34 func run(pass *analysis.Pass) (interface{}, error) {
35 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
37 nodeFilter := []ast.Node{
40 inspect.Preorder(nodeFilter, func(n ast.Node) {
41 call := n.(*ast.CallExpr)
42 if !isSignalNotify(pass.TypesInfo, call) {
45 var chanDecl *ast.CallExpr
46 switch arg := call.Args[0].(type) {
48 if decl, ok := findDecl(arg).(*ast.CallExpr); ok {
54 if chanDecl == nil || len(chanDecl.Args) != 1 {
57 chanDecl.Args = append(chanDecl.Args, &ast.BasicLit{
62 if err := format.Node(&buf, token.NewFileSet(), chanDecl); err != nil {
65 pass.Report(analysis.Diagnostic{
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{{
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"
87 switch fun := call.Fun.(type) {
88 case *ast.SelectorExpr:
91 if fun, ok := findDecl(fun).(*ast.SelectorExpr); ok {
100 func findDecl(arg *ast.Ident) ast.Node {
104 switch as := arg.Obj.Decl.(type) {
105 case *ast.AssignStmt:
106 if len(as.Lhs) != len(as.Rhs) {
109 for i, lhs := range as.Lhs {
110 lid, ok := lhs.(*ast.Ident)
114 if lid.Obj == arg.Obj {
119 if len(as.Names) != len(as.Values) {
122 for i, name := range as.Names {
123 if name.Obj == arg.Obj {