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 }