github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1016/sa1016.go (about)

     1  package sa1016
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/amarpal/go-tools/analysis/code"
     8  	"github.com/amarpal/go-tools/analysis/edit"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/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.Documentation{
    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  }