diff --git a/README.md b/README.md index c031e16..d8fd3d2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ # analysisutil -[![godoc.org][godoc-badge]][godoc] +[![PkgGoDev](https://pkg.go.dev/badge/github.com/gostaticanalysis/analysisutil)](https://pkg.go.dev/github.com/gostaticanalysis/analysisutil) Utilities for x/tools/go/analysis package. - - -[godoc]: https://godoc.org/github.com/gostaticanalysis/analysisutil -[godoc-badge]: https://img.shields.io/badge/godoc-reference-4F73B3.svg?style=flat-square&label=%20godoc.org - diff --git a/ssa.go b/ssa.go index ce3af58..79cec17 100644 --- a/ssa.go +++ b/ssa.go @@ -72,7 +72,7 @@ func returnsInBlock(b *ssa.BasicBlock, done map[*ssa.BasicBlock]bool) (rets []*s } // BinOp returns binary operator values which are contained in the block b. -func BinOp(b *ssa.BasicBlock) ([]*ssa.BinOp) { +func BinOp(b *ssa.BasicBlock) []*ssa.BinOp { var binops []*ssa.BinOp for _, instr := range b.Instrs { if binop, ok := instr.(*ssa.BinOp); ok { @@ -81,3 +81,60 @@ func BinOp(b *ssa.BasicBlock) ([]*ssa.BinOp) { } return binops } + +// Used returns an instruction which uses the value in the instructions. +func Used(v ssa.Value, instrs []ssa.Instruction) ssa.Instruction { + if len(instrs) == 0 || v.Referrers() == nil { + return nil + } + + for _, instr := range instrs { + if used := usedInInstr(v, instr); used != nil { + return used + } + } + + return nil +} + +func usedInInstr(v ssa.Value, instr ssa.Instruction) ssa.Instruction { + switch instr := instr.(type) { + case *ssa.MakeClosure: + return usedInClosure(v, instr) + default: + operands := instr.Operands(nil) + for _, x := range operands { + if x != nil && *x == v { + return instr + } + } + } + return nil +} + +func usedInClosure(v ssa.Value, instr *ssa.MakeClosure) ssa.Instruction { + fn, _ := instr.Fn.(*ssa.Function) + if fn == nil { + return nil + } + + var fv *ssa.FreeVar + for i := range instr.Bindings { + if instr.Bindings[i] == v { + fv = fn.FreeVars[i] + break + } + } + + if fv == nil { + return nil + } + + for _, b := range fn.Blocks { + if used := Used(fv, b.Instrs); used != nil { + return used + } + } + + return nil +} diff --git a/ssa_test.go b/ssa_test.go index 4d7ccfd..7f7e98b 100644 --- a/ssa_test.go +++ b/ssa_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/gostaticanalysis/analysisutil" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/ssa" ) @@ -97,3 +100,27 @@ func Test_BinOp(t *testing.T) { }) } } + +func TestUsed(t *testing.T) { + a := &analysis.Analyzer{ + Name: "used", + Requires: []*analysis.Analyzer{buildssa.Analyzer}, + Run: func(pass *analysis.Pass) (interface{}, error) { + srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + for _, f := range srcFuncs { + if len(f.Params) != 1 { + continue + } + v := f.Params[0] + for _, b := range f.Blocks { + if analysisutil.Used(v, b.Instrs) != nil { + pass.Reportf(v.Pos(), "used") + } + } + } + return nil, nil + }, + } + testdata := analysistest.TestData() + analysistest.Run(t, testdata, a, "used") +} diff --git a/testdata/src/used/used.go b/testdata/src/used/used.go new file mode 100644 index 0000000..90e60e8 --- /dev/null +++ b/testdata/src/used/used.go @@ -0,0 +1,48 @@ +package used + +var ( + N int + V interface{} +) + +func f1(v interface{}) { // want "used" + println(v) +} + +func f2(v interface{}) {} // unsed + +func f3(v interface{}) { // unsed + { + v := 100 + println(v) + } +} + +func f4(v interface{}) { // want "used" + V = v +} + +func f5(v interface{}) { // want "used" + if N == 0 { + return + } + V = v +} + +func f6(v interface{}) { // want "used" + func() { + println(v) + }() +} + +func f7(v interface{}) { // want "used" + go func() { + println(v) + }() +} + +func f8(v interface{}) { // want "used" + defer func() { + println(v) + }() +}