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

     1  package sa4009
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  
     7  	"github.com/amarpal/go-tools/analysis/lint"
     8  	"github.com/amarpal/go-tools/analysis/report"
     9  	"github.com/amarpal/go-tools/go/ir"
    10  	"github.com/amarpal/go-tools/go/ir/irutil"
    11  	"github.com/amarpal/go-tools/internal/passes/buildir"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  )
    15  
    16  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    17  	Analyzer: &analysis.Analyzer{
    18  		Name:     "SA4009",
    19  		Run:      run,
    20  		Requires: []*analysis.Analyzer{buildir.Analyzer},
    21  	},
    22  	Doc: &lint.Documentation{
    23  		Title:    `A function argument is overwritten before its first use`,
    24  		Since:    "2017.1",
    25  		Severity: lint.SeverityWarning,
    26  		MergeIf:  lint.MergeIfAny,
    27  	},
    28  })
    29  
    30  var Analyzer = SCAnalyzer.Analyzer
    31  
    32  func run(pass *analysis.Pass) (interface{}, error) {
    33  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
    34  		cb := func(node ast.Node) bool {
    35  			var typ *ast.FuncType
    36  			var body *ast.BlockStmt
    37  			switch fn := node.(type) {
    38  			case *ast.FuncDecl:
    39  				typ = fn.Type
    40  				body = fn.Body
    41  			case *ast.FuncLit:
    42  				typ = fn.Type
    43  				body = fn.Body
    44  			}
    45  			if body == nil {
    46  				return true
    47  			}
    48  			if len(typ.Params.List) == 0 {
    49  				return true
    50  			}
    51  			for _, field := range typ.Params.List {
    52  				for _, arg := range field.Names {
    53  					obj := pass.TypesInfo.ObjectOf(arg)
    54  					var irobj *ir.Parameter
    55  					for _, param := range fn.Params {
    56  						if param.Object() == obj {
    57  							irobj = param
    58  							break
    59  						}
    60  					}
    61  					if irobj == nil {
    62  						continue
    63  					}
    64  					refs := irobj.Referrers()
    65  					if refs == nil {
    66  						continue
    67  					}
    68  					if len(irutil.FilterDebug(*refs)) != 0 {
    69  						continue
    70  					}
    71  
    72  					var assignment ast.Node
    73  					ast.Inspect(body, func(node ast.Node) bool {
    74  						if assignment != nil {
    75  							return false
    76  						}
    77  						assign, ok := node.(*ast.AssignStmt)
    78  						if !ok {
    79  							return true
    80  						}
    81  						for _, lhs := range assign.Lhs {
    82  							ident, ok := lhs.(*ast.Ident)
    83  							if !ok {
    84  								continue
    85  							}
    86  							if pass.TypesInfo.ObjectOf(ident) == obj {
    87  								assignment = assign
    88  								return false
    89  							}
    90  						}
    91  						return true
    92  					})
    93  					if assignment != nil {
    94  						report.Report(pass, arg, fmt.Sprintf("argument %s is overwritten before first use", arg),
    95  							report.Related(assignment, fmt.Sprintf("assignment to %s", arg)))
    96  					}
    97  				}
    98  			}
    99  			return true
   100  		}
   101  		if source := fn.Source(); source != nil {
   102  			ast.Inspect(source, cb)
   103  		}
   104  	}
   105  	return nil, nil
   106  }