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