woodpecker/vendor/github.com/gostaticanalysis/analysisutil/call.go

406 lines
8.2 KiB
Go
Raw Normal View History

package analysisutil
import (
"go/types"
"golang.org/x/tools/go/ssa"
)
// CalledChecker checks a function is called.
// See From and Func.
type CalledChecker struct {
Ignore func(instr ssa.Instruction) bool
}
// NotIn checks whether receiver's method is called in a function.
// If there is no methods calling at a path from an instruction
// which type is receiver to all return instruction, NotIn returns these instructions.
func (c *CalledChecker) NotIn(f *ssa.Function, receiver types.Type, methods ...*types.Func) []ssa.Instruction {
done := map[ssa.Value]bool{}
var instrs []ssa.Instruction
for _, b := range f.Blocks {
for i, instr := range b.Instrs {
v, _ := instr.(ssa.Value)
if v == nil || done[v] {
continue
}
if v, _ := v.(*ssa.UnOp); v != nil && done[v.X] {
continue
}
called, ok := c.From(b, i, receiver, methods...)
if ok && !called {
instrs = append(instrs, instr)
done[v] = true
if v, _ := v.(*ssa.UnOp); v != nil {
done[v.X] = true
}
}
}
}
return instrs
}
// Func returns true when f is called in the instr.
// If recv is not nil, Func also checks the receiver.
func (c *CalledChecker) Func(instr ssa.Instruction, recv ssa.Value, f *types.Func) bool {
if c.Ignore != nil && c.Ignore(instr) {
return false
}
call, ok := instr.(ssa.CallInstruction)
if !ok {
return false
}
common := call.Common()
if common == nil {
return false
}
callee := common.StaticCallee()
if callee == nil {
return false
}
fn, ok := callee.Object().(*types.Func)
if !ok {
return false
}
if recv != nil &&
common.Signature().Recv() != nil &&
(len(common.Args) == 0 && recv != nil || common.Args[0] != recv &&
!referrer(recv, common.Args[0])) {
return false
}
return fn == f
}
func referrer(a, b ssa.Value) bool {
return isReferrerOf(a, b) || isReferrerOf(b, a)
}
func isReferrerOf(a, b ssa.Value) bool {
if a == nil || b == nil {
return false
}
if b.Referrers() != nil {
brs := *b.Referrers()
for _, br := range brs {
brv, ok := br.(ssa.Value)
if !ok {
continue
}
if brv == a {
return true
}
}
}
return false
}
// From checks whether receiver's method is called in an instruction
// which belogns to after i-th instructions, or in succsor blocks of b.
// The first result is above value.
// The second result is whether type of i-th instruction does not much receiver
// or matches with ignore cases.
func (c *CalledChecker) From(b *ssa.BasicBlock, i int, receiver types.Type, methods ...*types.Func) (called, ok bool) {
if b == nil || i < 0 || i >= len(b.Instrs) ||
receiver == nil || len(methods) == 0 {
return false, false
}
v, ok := b.Instrs[i].(ssa.Value)
if !ok {
return false, false
}
from := &calledFrom{recv: v, fs: methods, ignore: c.Ignore}
if !from.isRecv(receiver, v.Type()) {
return false, false
}
if from.ignored() {
return false, false
}
if from.instrs(b.Instrs[i+1:]) ||
from.succs(b) {
return true, true
}
from.done = nil
if from.storedInInstrs(b.Instrs[i+1:]) ||
from.storedInSuccs(b) {
return false, false
}
return false, true
}
type calledFrom struct {
recv ssa.Value
fs []*types.Func
done map[*ssa.BasicBlock]bool
ignore func(ssa.Instruction) bool
}
func (c *calledFrom) ignored() bool {
switch v := c.recv.(type) {
case *ssa.UnOp:
switch v.X.(type) {
case *ssa.FreeVar, *ssa.Global:
return true
}
}
refs := c.recv.Referrers()
if refs == nil {
return false
}
for _, ref := range *refs {
done := map[ssa.Instruction]bool{}
if !c.isOwn(ref) &&
((c.ignore != nil && c.ignore(ref)) ||
c.isRet(ref, done) || c.isArg(ref)) {
return true
}
}
return false
}
func (c *calledFrom) isOwn(instr ssa.Instruction) bool {
v, ok := instr.(ssa.Value)
if !ok {
return false
}
return v == c.recv
}
func (c *calledFrom) isRet(instr ssa.Instruction, done map[ssa.Instruction]bool) bool {
if done[instr] {
return false
}
done[instr] = true
switch instr := instr.(type) {
case *ssa.Return:
return true
case *ssa.MapUpdate:
return c.isRetInRefs(instr.Map, done)
case *ssa.Store:
if instr, _ := instr.Addr.(ssa.Instruction); instr != nil {
return c.isRet(instr, done)
}
return c.isRetInRefs(instr.Addr, done)
case *ssa.FieldAddr:
return c.isRetInRefs(instr.X, done)
case ssa.Value:
return c.isRetInRefs(instr, done)
default:
return false
}
}
func (c *calledFrom) isRetInRefs(v ssa.Value, done map[ssa.Instruction]bool) bool {
refs := v.Referrers()
if refs == nil {
return false
}
for _, ref := range *refs {
if c.isRet(ref, done) {
return true
}
}
return false
}
func (c *calledFrom) isArg(instr ssa.Instruction) bool {
call, ok := instr.(ssa.CallInstruction)
if !ok {
return false
}
common := call.Common()
if common == nil {
return false
}
args := common.Args
if common.Signature().Recv() != nil {
args = args[1:]
}
for i := range args {
if args[i] == c.recv {
return true
}
}
return false
}
func (c *calledFrom) instrs(instrs []ssa.Instruction) bool {
for _, instr := range instrs {
for _, f := range c.fs {
if Called(instr, c.recv, f) {
return true
}
}
}
return false
}
func (c *calledFrom) succs(b *ssa.BasicBlock) bool {
if c.done == nil {
c.done = map[*ssa.BasicBlock]bool{}
}
if c.done[b] {
return true
}
c.done[b] = true
if len(b.Succs) == 0 {
return false
}
for _, s := range b.Succs {
if !c.instrs(s.Instrs) && !c.succs(s) {
return false
}
}
return true
}
func (c *calledFrom) storedInInstrs(instrs []ssa.Instruction) bool {
for _, instr := range instrs {
switch instr := instr.(type) {
case *ssa.Store:
if instr.Val == c.recv {
return true
}
}
}
return false
}
func (c *calledFrom) storedInSuccs(b *ssa.BasicBlock) bool {
if c.done == nil {
c.done = map[*ssa.BasicBlock]bool{}
}
if c.done[b] {
return true
}
c.done[b] = true
if len(b.Succs) == 0 {
return false
}
for _, s := range b.Succs {
if !c.storedInInstrs(s.Instrs) && !c.succs(s) {
return false
}
}
return true
}
func (c *calledFrom) isRecv(recv, typ types.Type) bool {
return recv == typ || identical(recv, typ) ||
c.isRecvInTuple(recv, typ) || c.isRecvInEmbedded(recv, typ)
}
func (c *calledFrom) isRecvInTuple(recv, typ types.Type) bool {
tuple, _ := typ.(*types.Tuple)
if tuple == nil {
return false
}
for i := 0; i < tuple.Len(); i++ {
if c.isRecv(recv, tuple.At(i).Type()) {
return true
}
}
return false
}
func (c *calledFrom) isRecvInEmbedded(recv, typ types.Type) bool {
var st *types.Struct
switch typ := typ.(type) {
case *types.Struct:
st = typ
case *types.Pointer:
return c.isRecvInEmbedded(recv, typ.Elem())
case *types.Named:
return c.isRecvInEmbedded(recv, typ.Underlying())
default:
return false
}
for i := 0; i < st.NumFields(); i++ {
field := st.Field(i)
if !field.Embedded() {
continue
}
ft := field.Type()
if c.isRecv(recv, ft) {
return true
}
var ptrOrUnptr types.Type
switch ft := ft.(type) {
case *types.Pointer:
// struct { *T } -> T
ptrOrUnptr = ft.Elem()
default:
// struct { T } -> *T
ptrOrUnptr = types.NewPointer(ft)
}
if c.isRecv(recv, ptrOrUnptr) {
return true
}
}
return false
}
// NotCalledIn checks whether receiver's method is called in a function.
// If there is no methods calling at a path from an instruction
// which type is receiver to all return instruction, NotCalledIn returns these instructions.
func NotCalledIn(f *ssa.Function, receiver types.Type, methods ...*types.Func) []ssa.Instruction {
return new(CalledChecker).NotIn(f, receiver, methods...)
}
// CalledFrom checks whether receiver's method is called in an instruction
// which belogns to after i-th instructions, or in succsor blocks of b.
// The first result is above value.
// The second result is whether type of i-th instruction does not much receiver
// or matches with ignore cases.
func CalledFrom(b *ssa.BasicBlock, i int, receiver types.Type, methods ...*types.Func) (called, ok bool) {
return new(CalledChecker).From(b, i, receiver, methods...)
}
// Called returns true when f is called in the instr.
// If recv is not nil, Called also checks the receiver.
func Called(instr ssa.Instruction, recv ssa.Value, f *types.Func) bool {
return new(CalledChecker).Func(instr, recv, f)
}