175 lines
3.1 KiB
Go
175 lines
3.1 KiB
Go
package irutil
|
|
|
|
import (
|
|
"go/types"
|
|
"slices"
|
|
"strings"
|
|
|
|
"honnef.co/go/tools/go/ir"
|
|
"honnef.co/go/tools/go/types/typeutil"
|
|
)
|
|
|
|
func Reachable(from, to *ir.BasicBlock) bool {
|
|
if from == to {
|
|
return true
|
|
}
|
|
if from.Dominates(to) {
|
|
return true
|
|
}
|
|
|
|
found := false
|
|
Walk(from, func(b *ir.BasicBlock) bool {
|
|
if b == to {
|
|
found = true
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return found
|
|
}
|
|
|
|
func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) {
|
|
seen := map[*ir.BasicBlock]bool{}
|
|
wl := []*ir.BasicBlock{b}
|
|
for len(wl) > 0 {
|
|
b := wl[len(wl)-1]
|
|
wl = wl[:len(wl)-1]
|
|
if seen[b] {
|
|
continue
|
|
}
|
|
seen[b] = true
|
|
if !fn(b) {
|
|
continue
|
|
}
|
|
wl = append(wl, b.Succs...)
|
|
}
|
|
}
|
|
|
|
func Vararg(x *ir.Slice) ([]ir.Value, bool) {
|
|
var out []ir.Value
|
|
alloc, ok := ir.Unwrap(x.X).(*ir.Alloc)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
var checkAlloc func(alloc ir.Value) bool
|
|
checkAlloc = func(alloc ir.Value) bool {
|
|
for _, ref := range *alloc.Referrers() {
|
|
if ref == x {
|
|
continue
|
|
}
|
|
if ref.Block() != x.Block() {
|
|
return false
|
|
}
|
|
switch ref := ref.(type) {
|
|
case *ir.IndexAddr:
|
|
idx := ref
|
|
if len(*idx.Referrers()) != 1 {
|
|
return false
|
|
}
|
|
store, ok := (*idx.Referrers())[0].(*ir.Store)
|
|
if !ok {
|
|
return false
|
|
}
|
|
out = append(out, store.Val)
|
|
case *ir.Copy:
|
|
if !checkAlloc(ref) {
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
if !checkAlloc(alloc) {
|
|
return nil, false
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
func CallName(call *ir.CallCommon) string {
|
|
if call.IsInvoke() {
|
|
return ""
|
|
}
|
|
switch v := call.Value.(type) {
|
|
case *ir.Function:
|
|
fn, ok := v.Object().(*types.Func)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return typeutil.FuncName(fn)
|
|
case *ir.Builtin:
|
|
return v.Name()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
|
|
|
|
func IsCallToAny(call *ir.CallCommon, names ...string) bool {
|
|
q := CallName(call)
|
|
return slices.Contains(names, q)
|
|
}
|
|
|
|
func FilterDebug(instr []ir.Instruction) []ir.Instruction {
|
|
var out []ir.Instruction
|
|
for _, ins := range instr {
|
|
if _, ok := ins.(*ir.DebugRef); !ok {
|
|
out = append(out, ins)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func IsExample(fn *ir.Function) bool {
|
|
if !strings.HasPrefix(fn.Name(), "Example") {
|
|
return false
|
|
}
|
|
f := fn.Prog.Fset.File(fn.Pos())
|
|
if f == nil {
|
|
return false
|
|
}
|
|
return strings.HasSuffix(f.Name(), "_test.go")
|
|
}
|
|
|
|
// Flatten recursively returns the underlying value of an ir.Sigma or
|
|
// ir.Phi node. If all edges in an ir.Phi node are the same (after
|
|
// flattening), the flattened edge will get returned. If flattening is
|
|
// not possible, nil is returned.
|
|
func Flatten(v ir.Value) ir.Value {
|
|
failed := false
|
|
seen := map[ir.Value]struct{}{}
|
|
var out ir.Value
|
|
var dfs func(v ir.Value)
|
|
dfs = func(v ir.Value) {
|
|
if failed {
|
|
return
|
|
}
|
|
if _, ok := seen[v]; ok {
|
|
return
|
|
}
|
|
seen[v] = struct{}{}
|
|
|
|
switch v := v.(type) {
|
|
case *ir.Sigma:
|
|
dfs(v.X)
|
|
case *ir.Phi:
|
|
for _, e := range v.Edges {
|
|
dfs(e)
|
|
}
|
|
default:
|
|
if out == nil {
|
|
out = v
|
|
} else if out != v {
|
|
failed = true
|
|
}
|
|
}
|
|
}
|
|
dfs(v)
|
|
|
|
if failed {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|