1package irutil
2
3import (
4	"go/types"
5	"strings"
6
7	"honnef.co/go/tools/go/ir"
8	"honnef.co/go/tools/go/types/typeutil"
9)
10
11func Reachable(from, to *ir.BasicBlock) bool {
12	if from == to {
13		return true
14	}
15	if from.Dominates(to) {
16		return true
17	}
18
19	found := false
20	Walk(from, func(b *ir.BasicBlock) bool {
21		if b == to {
22			found = true
23			return false
24		}
25		return true
26	})
27	return found
28}
29
30func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) {
31	seen := map[*ir.BasicBlock]bool{}
32	wl := []*ir.BasicBlock{b}
33	for len(wl) > 0 {
34		b := wl[len(wl)-1]
35		wl = wl[:len(wl)-1]
36		if seen[b] {
37			continue
38		}
39		seen[b] = true
40		if !fn(b) {
41			continue
42		}
43		wl = append(wl, b.Succs...)
44	}
45}
46
47func Vararg(x *ir.Slice) ([]ir.Value, bool) {
48	var out []ir.Value
49	slice, ok := x.X.(*ir.Alloc)
50	if !ok {
51		return nil, false
52	}
53	for _, ref := range *slice.Referrers() {
54		if ref == x {
55			continue
56		}
57		if ref.Block() != x.Block() {
58			return nil, false
59		}
60		idx, ok := ref.(*ir.IndexAddr)
61		if !ok {
62			return nil, false
63		}
64		if len(*idx.Referrers()) != 1 {
65			return nil, false
66		}
67		store, ok := (*idx.Referrers())[0].(*ir.Store)
68		if !ok {
69			return nil, false
70		}
71		out = append(out, store.Val)
72	}
73	return out, true
74}
75
76func CallName(call *ir.CallCommon) string {
77	if call.IsInvoke() {
78		return ""
79	}
80	switch v := call.Value.(type) {
81	case *ir.Function:
82		fn, ok := v.Object().(*types.Func)
83		if !ok {
84			return ""
85		}
86		return typeutil.FuncName(fn)
87	case *ir.Builtin:
88		return v.Name()
89	}
90	return ""
91}
92
93func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
94
95func IsCallToAny(call *ir.CallCommon, names ...string) bool {
96	q := CallName(call)
97	for _, name := range names {
98		if q == name {
99			return true
100		}
101	}
102	return false
103}
104
105func FilterDebug(instr []ir.Instruction) []ir.Instruction {
106	var out []ir.Instruction
107	for _, ins := range instr {
108		if _, ok := ins.(*ir.DebugRef); !ok {
109			out = append(out, ins)
110		}
111	}
112	return out
113}
114
115func IsExample(fn *ir.Function) bool {
116	if !strings.HasPrefix(fn.Name(), "Example") {
117		return false
118	}
119	f := fn.Prog.Fset.File(fn.Pos())
120	if f == nil {
121		return false
122	}
123	return strings.HasSuffix(f.Name(), "_test.go")
124}
125
126// Flatten recursively returns the underlying value of an ir.Sigma or
127// ir.Phi node. If all edges in an ir.Phi node are the same (after
128// flattening), the flattened edge will get returned. If flattening is
129// not possible, nil is returned.
130func Flatten(v ir.Value) ir.Value {
131	failed := false
132	seen := map[ir.Value]struct{}{}
133	var out ir.Value
134	var dfs func(v ir.Value)
135	dfs = func(v ir.Value) {
136		if failed {
137			return
138		}
139		if _, ok := seen[v]; ok {
140			return
141		}
142		seen[v] = struct{}{}
143
144		switch v := v.(type) {
145		case *ir.Sigma:
146			dfs(v.X)
147		case *ir.Phi:
148			for _, e := range v.Edges {
149				dfs(e)
150			}
151		default:
152			if out == nil {
153				out = v
154			} else if out != v {
155				failed = true
156			}
157		}
158	}
159	dfs(v)
160
161	if failed {
162		return nil
163	}
164	return out
165}
166