| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package vta |
| |
| import ( |
| "go/types" |
| |
| "golang.org/x/tools/go/callgraph" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/internal/typeparams" |
| ) |
| |
| func canAlias(n1, n2 node) bool { |
| return isReferenceNode(n1) && isReferenceNode(n2) |
| } |
| |
| func isReferenceNode(n node) bool { |
| if _, ok := n.(nestedPtrInterface); ok { |
| return true |
| } |
| if _, ok := n.(nestedPtrFunction); ok { |
| return true |
| } |
| |
| if _, ok := n.Type().(*types.Pointer); ok { |
| return true |
| } |
| |
| return false |
| } |
| |
| // hasInFlow checks if a concrete type can flow to node `n`. |
| // Returns yes iff the type of `n` satisfies one the following: |
| // 1. is an interface |
| // 2. is a (nested) pointer to interface (needed for, say, |
| // slice elements of nested pointers to interface type) |
| // 3. is a function type (needed for higher-order type flow) |
| // 4. is a (nested) pointer to function (needed for, say, |
| // slice elements of nested pointers to function type) |
| // 5. is a global Recover or Panic node |
| func hasInFlow(n node) bool { |
| if _, ok := n.(panicArg); ok { |
| return true |
| } |
| if _, ok := n.(recoverReturn); ok { |
| return true |
| } |
| |
| t := n.Type() |
| |
| if i := interfaceUnderPtr(t); i != nil { |
| return true |
| } |
| if f := functionUnderPtr(t); f != nil { |
| return true |
| } |
| |
| return types.IsInterface(t) || isFunction(t) |
| } |
| |
| func isFunction(t types.Type) bool { |
| _, ok := t.Underlying().(*types.Signature) |
| return ok |
| } |
| |
| // interfaceUnderPtr checks if type `t` is a potentially nested |
| // pointer to interface and if yes, returns the interface type. |
| // Otherwise, returns nil. |
| func interfaceUnderPtr(t types.Type) types.Type { |
| seen := make(map[types.Type]bool) |
| var visit func(types.Type) types.Type |
| visit = func(t types.Type) types.Type { |
| if seen[t] { |
| return nil |
| } |
| seen[t] = true |
| |
| p, ok := t.Underlying().(*types.Pointer) |
| if !ok { |
| return nil |
| } |
| |
| if types.IsInterface(p.Elem()) { |
| return p.Elem() |
| } |
| |
| return visit(p.Elem()) |
| } |
| return visit(t) |
| } |
| |
| // functionUnderPtr checks if type `t` is a potentially nested |
| // pointer to function type and if yes, returns the function type. |
| // Otherwise, returns nil. |
| func functionUnderPtr(t types.Type) types.Type { |
| seen := make(map[types.Type]bool) |
| var visit func(types.Type) types.Type |
| visit = func(t types.Type) types.Type { |
| if seen[t] { |
| return nil |
| } |
| seen[t] = true |
| |
| p, ok := t.Underlying().(*types.Pointer) |
| if !ok { |
| return nil |
| } |
| |
| if isFunction(p.Elem()) { |
| return p.Elem() |
| } |
| |
| return visit(p.Elem()) |
| } |
| return visit(t) |
| } |
| |
| // sliceArrayElem returns the element type of type `t` that is |
| // expected to be a (pointer to) array, slice or string, consistent with |
| // the ssa.Index and ssa.IndexAddr instructions. Panics otherwise. |
| func sliceArrayElem(t types.Type) types.Type { |
| switch u := t.Underlying().(type) { |
| case *types.Pointer: |
| return u.Elem().Underlying().(*types.Array).Elem() |
| case *types.Array: |
| return u.Elem() |
| case *types.Slice: |
| return u.Elem() |
| case *types.Basic: |
| return types.Typ[types.Byte] |
| case *types.Interface: // type param. |
| terms, err := typeparams.InterfaceTermSet(u) |
| if err != nil || len(terms) == 0 { |
| panic(t) |
| } |
| return sliceArrayElem(terms[0].Type()) // Element types must match. |
| default: |
| panic(t) |
| } |
| } |
| |
| // siteCallees computes a set of callees for call site `c` given program `callgraph`. |
| func siteCallees(c ssa.CallInstruction, callgraph *callgraph.Graph) []*ssa.Function { |
| var matches []*ssa.Function |
| |
| node := callgraph.Nodes[c.Parent()] |
| if node == nil { |
| return nil |
| } |
| |
| for _, edge := range node.Out { |
| if edge.Site == c { |
| matches = append(matches, edge.Callee.Func) |
| } |
| } |
| return matches |
| } |
| |
| func canHaveMethods(t types.Type) bool { |
| if _, ok := t.(*types.Named); ok { |
| return true |
| } |
| |
| u := t.Underlying() |
| switch u.(type) { |
| case *types.Interface, *types.Signature, *types.Struct: |
| return true |
| default: |
| return false |
| } |
| } |
| |
| // calls returns the set of call instructions in `f`. |
| func calls(f *ssa.Function) []ssa.CallInstruction { |
| var calls []ssa.CallInstruction |
| for _, bl := range f.Blocks { |
| for _, instr := range bl.Instrs { |
| if c, ok := instr.(ssa.CallInstruction); ok { |
| calls = append(calls, c) |
| } |
| } |
| } |
| return calls |
| } |
| |
| // intersect produces an intersection of functions in `fs1` and `fs2`. |
| func intersect(fs1, fs2 []*ssa.Function) []*ssa.Function { |
| m := make(map[*ssa.Function]bool) |
| for _, f := range fs1 { |
| m[f] = true |
| } |
| |
| var res []*ssa.Function |
| for _, f := range fs2 { |
| if m[f] { |
| res = append(res, f) |
| } |
| } |
| return res |
| } |