sa1016.go raw
1 package sa1016
2
3 import (
4 "fmt"
5 "go/ast"
6
7 "honnef.co/go/tools/analysis/code"
8 "honnef.co/go/tools/analysis/edit"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11
12 "golang.org/x/tools/go/analysis"
13 "golang.org/x/tools/go/analysis/passes/inspect"
14 )
15
16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
17 Analyzer: &analysis.Analyzer{
18 Name: "SA1016",
19 Run: run,
20 Requires: []*analysis.Analyzer{inspect.Analyzer},
21 },
22 Doc: &lint.RawDocumentation{
23 Title: `Trapping a signal that cannot be trapped`,
24 Text: `Not all signals can be intercepted by a process. Specifically, on
25 UNIX-like systems, the \'syscall.SIGKILL\' and \'syscall.SIGSTOP\' signals are
26 never passed to the process, but instead handled directly by the
27 kernel. It is therefore pointless to try and handle these signals.`,
28 Since: "2017.1",
29 Severity: lint.SeverityWarning,
30 MergeIf: lint.MergeIfAny,
31 },
32 })
33
34 var Analyzer = SCAnalyzer.Analyzer
35
36 func run(pass *analysis.Pass) (interface{}, error) {
37 isSignal := func(pass *analysis.Pass, expr ast.Expr, name string) bool {
38 if expr, ok := expr.(*ast.SelectorExpr); ok {
39 return code.SelectorName(pass, expr) == name
40 } else {
41 return false
42 }
43 }
44
45 fn := func(node ast.Node) {
46 call := node.(*ast.CallExpr)
47 if !code.IsCallToAny(pass, call,
48 "os/signal.Ignore", "os/signal.Notify", "os/signal.Reset") {
49 return
50 }
51
52 hasSigterm := false
53 for _, arg := range call.Args {
54 if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
55 arg = conv.Args[0]
56 }
57
58 if isSignal(pass, arg, "syscall.SIGTERM") {
59 hasSigterm = true
60 break
61 }
62
63 }
64 for i, arg := range call.Args {
65 if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
66 arg = conv.Args[0]
67 }
68
69 if isSignal(pass, arg, "os.Kill") || isSignal(pass, arg, "syscall.SIGKILL") {
70 var fixes []analysis.SuggestedFix
71 if !hasSigterm {
72 nargs := make([]ast.Expr, len(call.Args))
73 for j, a := range call.Args {
74 if i == j {
75 nargs[j] = edit.Selector("syscall", "SIGTERM")
76 } else {
77 nargs[j] = a
78 }
79 }
80 ncall := *call
81 ncall.Args = nargs
82 fixes = append(fixes, edit.Fix(fmt.Sprintf("use syscall.SIGTERM instead of %s", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
83 }
84 nargs := make([]ast.Expr, 0, len(call.Args))
85 for j, a := range call.Args {
86 if i == j {
87 continue
88 }
89 nargs = append(nargs, a)
90 }
91 ncall := *call
92 ncall.Args = nargs
93 fixes = append(fixes, edit.Fix(fmt.Sprintf("remove %s from list of arguments", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
94 report.Report(pass, arg, fmt.Sprintf("%s cannot be trapped (did you mean syscall.SIGTERM?)", report.Render(pass, arg)), report.Fixes(fixes...))
95 }
96 if isSignal(pass, arg, "syscall.SIGSTOP") {
97 nargs := make([]ast.Expr, 0, len(call.Args)-1)
98 for j, a := range call.Args {
99 if i == j {
100 continue
101 }
102 nargs = append(nargs, a)
103 }
104 ncall := *call
105 ncall.Args = nargs
106 report.Report(pass, arg, "syscall.SIGSTOP cannot be trapped", report.Fixes(edit.Fix("remove syscall.SIGSTOP from list of arguments", edit.ReplaceWithNode(pass.Fset, call, &ncall))))
107 }
108 }
109 }
110 code.Preorder(pass, fn, (*ast.CallExpr)(nil))
111 return nil, nil
112 }
113