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

     1  package sa5005
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/amarpal/go-tools/analysis/lint"
     7  	"github.com/amarpal/go-tools/analysis/report"
     8  	"github.com/amarpal/go-tools/go/ir"
     9  	"github.com/amarpal/go-tools/internal/passes/buildir"
    10  	"github.com/amarpal/go-tools/knowledge"
    11  
    12  	"golang.org/x/tools/go/analysis"
    13  )
    14  
    15  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    16  	Analyzer: &analysis.Analyzer{
    17  		Name:     "SA5005",
    18  		Run:      run,
    19  		Requires: []*analysis.Analyzer{buildir.Analyzer},
    20  	},
    21  	Doc: &lint.Documentation{
    22  		Title: `The finalizer references the finalized object, preventing garbage collection`,
    23  		Text: `A finalizer is a function associated with an object that runs when the
    24  garbage collector is ready to collect said object, that is when the
    25  object is no longer referenced by anything.
    26  
    27  If the finalizer references the object, however, it will always remain
    28  as the final reference to that object, preventing the garbage
    29  collector from collecting the object. The finalizer will never run,
    30  and the object will never be collected, leading to a memory leak. That
    31  is why the finalizer should instead use its first argument to operate
    32  on the object. That way, the number of references can temporarily go
    33  to zero before the object is being passed to the finalizer.`,
    34  		Since:    "2017.1",
    35  		Severity: lint.SeverityWarning,
    36  		MergeIf:  lint.MergeIfAny,
    37  	},
    38  })
    39  
    40  var Analyzer = SCAnalyzer.Analyzer
    41  
    42  func run(pass *analysis.Pass) (interface{}, error) {
    43  	cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
    44  		if callee.RelString(nil) != "runtime.SetFinalizer" {
    45  			return
    46  		}
    47  		arg0 := site.Common().Args[knowledge.Arg("runtime.SetFinalizer.obj")]
    48  		if iface, ok := arg0.(*ir.MakeInterface); ok {
    49  			arg0 = iface.X
    50  		}
    51  		load, ok := arg0.(*ir.Load)
    52  		if !ok {
    53  			return
    54  		}
    55  		v, ok := load.X.(*ir.Alloc)
    56  		if !ok {
    57  			return
    58  		}
    59  		arg1 := site.Common().Args[knowledge.Arg("runtime.SetFinalizer.finalizer")]
    60  		if iface, ok := arg1.(*ir.MakeInterface); ok {
    61  			arg1 = iface.X
    62  		}
    63  		mc, ok := arg1.(*ir.MakeClosure)
    64  		if !ok {
    65  			return
    66  		}
    67  		for _, b := range mc.Bindings {
    68  			if b == v {
    69  				pos := report.DisplayPosition(pass.Fset, mc.Fn.Pos())
    70  				report.Report(pass, site, fmt.Sprintf("the finalizer closes over the object, preventing the finalizer from ever running (at %s)", pos))
    71  			}
    72  		}
    73  	}
    74  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
    75  		eachCall(fn, cb)
    76  	}
    77  	return nil, nil
    78  }
    79  
    80  func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
    81  	for _, b := range fn.Blocks {
    82  		for _, instr := range b.Instrs {
    83  			if site, ok := instr.(ir.CallInstruction); ok {
    84  				if g := site.Common().StaticCallee(); g != nil {
    85  					cb(fn, site, g)
    86  				}
    87  			}
    88  		}
    89  	}
    90  }