vta: adds VTA graph construction for basic program statements

Basic SSA program statements include storing and loading from a
register, storing and loading from a field, unary operations, make
instructions, tuple extractions, and type conversion/assertions.

Support for collections, including channels, and function calls will
happen in future CLs.

Change-Id: I2ab988dbdc5cfe67ba7f3946910a03416045ec6c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/322950
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Zvonimir Pavlinovic 2021-05-26 11:13:28 -07:00
parent 8f2cf6ccfc
commit bf132055df
13 changed files with 942 additions and 0 deletions

View File

@ -6,9 +6,12 @@ package vta
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
)
// node interface for VTA nodes.
@ -232,3 +235,250 @@ func (g vtaGraph) successors(n node) []node {
}
return succs
}
// typePropGraph builds a VTA graph for a set of `funcs` and initial
// `callgraph` needed to establish interprocedural edges. Returns the
// graph and a map for unique type representatives.
func typePropGraph(funcs map[*ssa.Function]bool, callgraph *callgraph.Graph) (vtaGraph, *typeutil.Map) {
b := builder{graph: make(vtaGraph), callGraph: callgraph}
b.visit(funcs)
return b.graph, &b.canon
}
// Data structure responsible for linearly traversing the
// code and building a VTA graph.
type builder struct {
graph vtaGraph
callGraph *callgraph.Graph // initial call graph for creating flows at unresolved call sites.
// Specialized type map for canonicalization of types.Type.
// Semantically equivalent types can have different implementations,
// i.e., they are different pointer values. The map allows us to
// have one unique representative. The keys are fixed and from the
// client perspective they are types. The values in our case are
// types too, in particular type representatives. Each value is a
// pointer so this map is not expected to take much memory.
canon typeutil.Map
}
func (b *builder) visit(funcs map[*ssa.Function]bool) {
for f, in := range funcs {
if in {
b.fun(f)
}
}
}
func (b *builder) fun(f *ssa.Function) {
for _, bl := range f.Blocks {
for _, instr := range bl.Instrs {
b.instr(instr)
}
}
}
func (b *builder) instr(instr ssa.Instruction) {
switch i := instr.(type) {
case *ssa.Store:
b.addInFlowAliasEdges(b.nodeFromVal(i.Addr), b.nodeFromVal(i.Val))
case *ssa.MakeInterface:
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
case *ssa.UnOp:
b.unop(i)
case *ssa.Phi:
b.phi(i)
case *ssa.ChangeInterface:
// Although in change interface a := A(b) command a and b are
// the same object, the only interesting flow happens when A
// is an interface. We create flow b -> a, but omit a -> b.
// The latter flow is not needed: if a gets assigned concrete
// type later on, that cannot be propagated back to b as b
// is a separate variable. The a -> b flow can happen when
// A is a pointer to interface, but then the command is of
// type ChangeType, handled below.
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
case *ssa.ChangeType:
// change type command a := A(b) results in a and b being the
// same value. For concrete type A, there is no interesting flow.
//
// Note: When A is an interface, most interface casts are handled
// by the ChangeInterface instruction. The relevant case here is
// when converting a pointer to an interface type. This can happen
// when the underlying interfaces have the same method set.
// type I interface{ foo() }
// type J interface{ foo() }
// var b *I
// a := (*J)(b)
// When this happens we add flows between a <--> b.
b.addInFlowAliasEdges(b.nodeFromVal(i), b.nodeFromVal(i.X))
case *ssa.TypeAssert:
b.tassert(i)
case *ssa.Extract:
b.extract(i)
case *ssa.Field:
b.field(i)
case *ssa.FieldAddr:
b.fieldAddr(i)
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
*ssa.Slice, *ssa.Range, *ssa.RunDefers:
// No interesting flow here.
return
default:
// TODO(zpavlinovic): make into a panic once all instructions are supported.
fmt.Printf("unsupported instruction %v\n", instr)
}
}
func (b *builder) unop(u *ssa.UnOp) {
switch u.Op {
case token.MUL:
// Multiplication operator * is used here as a dereference operator.
b.addInFlowAliasEdges(b.nodeFromVal(u), b.nodeFromVal(u.X))
case token.ARROW:
// TODO(zpavlinovic): add support for channels.
default:
// There is no interesting type flow otherwise.
}
}
func (b *builder) phi(p *ssa.Phi) {
for _, edge := range p.Edges {
b.addInFlowAliasEdges(b.nodeFromVal(p), b.nodeFromVal(edge))
}
}
func (b *builder) tassert(a *ssa.TypeAssert) {
if !a.CommaOk {
b.addInFlowEdge(b.nodeFromVal(a.X), b.nodeFromVal(a))
return
}
// The case where a is <a.AssertedType, bool> register so there
// is a flow from a.X to a[0]. Here, a[0] is represented as an
// indexedLocal: an entry into local tuple register a at index 0.
tup := a.Type().Underlying().(*types.Tuple)
t := tup.At(0).Type()
local := indexedLocal{val: a, typ: t, index: 0}
b.addInFlowEdge(b.nodeFromVal(a.X), local)
}
// extract instruction t1 := t2[i] generates flows between t2[i]
// and t1 where the source is indexed local representing a value
// from tuple register t2 at index i and the target is t1.
func (b *builder) extract(e *ssa.Extract) {
tup := e.Tuple.Type().Underlying().(*types.Tuple)
t := tup.At(e.Index).Type()
local := indexedLocal{val: e.Tuple, typ: t, index: e.Index}
b.addInFlowAliasEdges(b.nodeFromVal(e), local)
}
func (b *builder) field(f *ssa.Field) {
fnode := field{StructType: f.X.Type(), index: f.Field}
b.addInFlowEdge(fnode, b.nodeFromVal(f))
}
func (b *builder) fieldAddr(f *ssa.FieldAddr) {
t := f.X.Type().Underlying().(*types.Pointer).Elem()
// Since we are getting pointer to a field, make a bidirectional edge.
fnode := field{StructType: t, index: f.Field}
b.addInFlowEdge(fnode, b.nodeFromVal(f))
b.addInFlowEdge(b.nodeFromVal(f), fnode)
}
// addInFlowAliasEdges adds an edge r -> l to b.graph if l is a node that can
// have an inflow, i.e., a node that represents an interface or an unresolved
// function value. Similarly for the edge l -> r with an additional condition
// of that l and r can potentially alias.
func (b *builder) addInFlowAliasEdges(l, r node) {
b.addInFlowEdge(r, l)
if canAlias(l, r) {
b.addInFlowEdge(l, r)
}
}
// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
// that represents an interface or an unresolved function value. Otherwise, there
// is no interesting type flow so the edge is ommited.
func (b *builder) addInFlowEdge(s, d node) {
if hasInFlow(d) {
b.graph.addEdge(b.representative(s), b.representative(d))
}
}
// Creates const, pointer, global, func, and local nodes based on register instructions.
func (b *builder) nodeFromVal(val ssa.Value) node {
if p, ok := val.Type().(*types.Pointer); ok && !isInterface(p.Elem()) {
// Nested pointer to interfaces are modeled as a special
// nestedPtrInterface node.
if i := interfaceUnderPtr(p.Elem()); i != nil {
return nestedPtrInterface{typ: i}
}
return pointer{typ: p}
}
switch v := val.(type) {
case *ssa.Const:
return constant{typ: val.Type()}
case *ssa.Global:
return global{val: v}
case *ssa.Function:
return function{f: v}
case *ssa.Parameter, *ssa.FreeVar, ssa.Instruction:
// ssa.Param, ssa.FreeVar, and a specific set of "register" instructions,
// satisifying the ssa.Value interface, can serve as local variables.
return local{val: v}
default:
panic(fmt.Errorf("unsupported value %v in node creation", val))
}
return nil
}
// representative returns a unique representative for node `n`. Since
// semantically equivalent types can have different implementations,
// this method guarantees the same implementation is always used.
func (b *builder) representative(n node) node {
if !hasInitialTypes(n) {
return n
}
t := canonicalize(n.Type(), &b.canon)
switch i := n.(type) {
case constant:
return constant{typ: t}
case pointer:
return pointer{typ: t.(*types.Pointer)}
case sliceElem:
return sliceElem{typ: t}
case mapKey:
return mapKey{typ: t}
case mapValue:
return mapValue{typ: t}
case channelElem:
return channelElem{typ: t}
case nestedPtrInterface:
return nestedPtrInterface{typ: t}
case field:
return field{StructType: canonicalize(i.StructType, &b.canon), index: i.index}
case indexedLocal:
return indexedLocal{typ: t, val: i.val, index: i.index}
case local, global, panicArg, recoverReturn, function:
return n
default:
panic(fmt.Errorf("canonicalizing unrecognized node %v", n))
}
}
// canonicalize returns a type representative of `t` unique subject
// to type map `canon`.
func canonicalize(t types.Type, canon *typeutil.Map) types.Type {
rep := canon.At(t)
if rep != nil {
return rep.(types.Type)
}
canon.Set(t, t)
return t
}

View File

@ -5,14 +5,17 @@
package vta
import (
"fmt"
"go/ast"
"go/parser"
"go/types"
"io/ioutil"
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
@ -187,3 +190,66 @@ func TestVtaGraph(t *testing.T) {
}
}
}
// vtaGraphStr stringifies vtaGraph into a list of strings
// where each string represents an edge set of the format
// node -> succ_1, ..., succ_n. succ_1, ..., succ_n are
// sorted in alphabetical order.
func vtaGraphStr(g vtaGraph) []string {
var vgs []string
for n, succ := range g {
var succStr []string
for s := range succ {
succStr = append(succStr, s.String())
}
sort.Strings(succStr)
entry := fmt.Sprintf("%v -> %v", n.String(), strings.Join(succStr, ", "))
vgs = append(vgs, entry)
}
return vgs
}
// subGraph checks if a graph `g1` is a subgraph of graph `g2`.
// Assumes that each element in `g1` and `g2` is an edge set
// for a particular node in a fixed yet arbitrary format.
func subGraph(g1, g2 []string) bool {
m := make(map[string]bool)
for _, s := range g2 {
m[s] = true
}
for _, s := range g1 {
if _, ok := m[s]; !ok {
return false
}
}
return true
}
func TestVTAGraphConstruction(t *testing.T) {
for _, file := range []string{
"testdata/store.go",
"testdata/phi.go",
"testdata/type_conversions.go",
"testdata/type_assertions.go",
"testdata/fields.go",
"testdata/node_uniqueness.go",
"testdata/store_load_alias.go",
"testdata/phi_alias.go",
} {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(file)
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
if gs := vtaGraphStr(g); !subGraph(want, gs) {
t.Errorf("`%s`: want superset of %v;\n got %v", file, want, gs)
}
})
}
}

65
go/callgraph/vta/testdata/fields.go vendored Normal file
View File

@ -0,0 +1,65 @@
// 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.
// go:build ignore
package testdata
type I interface {
Foo()
}
type J interface {
I
Bar()
}
type A struct{}
func (a A) Foo() {}
func (a A) Bar() {}
type B struct {
a A
i I
}
func Do() B {
b := B{}
return b
}
func Baz(b B) {
var j J
j = b.a
j.Bar()
b.i = j
Do().i.Foo()
}
// Relevant SSA:
// func Baz(b B):
// t0 = local B (b)
// *t0 = b
// t1 = &t0.a [#0] // no flow here since a is of concrete type
// t2 = *t1
// t3 = make J <- A (t2)
// t4 = invoke t3.Bar()
// t5 = &t0.i [#1]
// t6 = change interface I <- J (t3)
// *t5 = t6
// t7 = Do()
// t8 = t7.i [#0]
// t9 = (A).Foo(t8)
// return
// WANT:
// Field(testdata.B:i) -> Local(t5), Local(t8)
// Local(t5) -> Field(testdata.B:i)
// Local(t2) -> Local(t3)
// Local(t3) -> Local(t6)
// Local(t6) -> Local(t5)

View File

@ -0,0 +1,58 @@
// 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.
// go:build ignore
package testdata
// TestNodeTypeUniqueness checks if semantically equivalent types
// are being represented using the same pointer value in vta nodes.
// If not, some edges become missing in the string representation
// of the graph.
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
func Baz(a *A) (I, I, interface{}, interface{}) {
var i I
i = a
var ii I
aa := &A{}
ii = aa
m := make(map[int]int)
var iii interface{}
iii = m
var iiii interface{}
iiii = m
return i, ii, iii, iiii
}
// Relevant SSA:
// func Baz(a *A) (I, I, interface{}, interface{}):
// t0 = make I <- *A (a)
// t1 = new A (complit)
// t2 = make I <- *A (t1)
// t3 = make map[int]int
// t4 = make interface{} <- map[int]int (t3)
// t5 = make interface{} <- map[int]int (t3)
// return t0, t2, t4, t5
// Without canon approach, one of Pointer(*A) -> Local(t0) and Pointer(*A) -> Local(t2) edges is
// missing in the graph string representation. The original graph has both of the edges but the
// source node Pointer(*A) is not the same; two occurences of Pointer(*A) are considered separate
// nodes. Since they have the same string representation, one edge gets overriden by the other
// during the graph stringification, instead of being joined together as in below.
// WANT:
// Pointer(*testdata.A) -> Local(t0), Local(t2)
// Local(t3) -> Local(t4), Local(t5)

55
go/callgraph/vta/testdata/phi.go vendored Normal file
View File

@ -0,0 +1,55 @@
// 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.
// go:build ignore
package testdata
type A struct{}
type B struct{}
type I interface{ foo() }
func (a A) foo() {}
func (b B) foo() {}
func Baz(b B, c bool) {
var i I
if c {
i = b
} else {
a := A{}
i = a
}
i.foo()
}
// Relevant SSA:
// func Baz(b B, c bool):
// 0:
// t0 = local B (b)
// *t0 = b
// if c goto 1 else 3
//
// 1:
// t1 = *t0
// t2 = make I <- B (t1)
// jump 2
//
// 2:
// t3 = phi [1: t2, 3: t7] #i
// t4 = invoke t3.foo()
// return
//
// 3:
// t5 = local A (a)
// t6 = *t5
// t7 = make I <- A (t6)
// jump 2
// WANT:
// Local(t1) -> Local(t2)
// Local(t2) -> Local(t3)
// Local(t7) -> Local(t3)
// Local(t6) -> Local(t7)

66
go/callgraph/vta/testdata/phi_alias.go vendored Normal file
View File

@ -0,0 +1,66 @@
// 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.
// go:build ignore
package testdata
type I interface {
Foo()
}
type B struct {
p int
}
func (b B) Foo() {}
func Baz(i, j *I, b, c bool) {
if b {
i = j
}
*i = B{9}
if c {
(*i).Foo()
} else {
(*j).Foo()
}
}
// Relevant SSA:
// func Baz(i *I, j *I, b bool, c bool):
// if b goto 1 else 2
// 1:
// jump 2
// 2:
// t0 = phi [0: i, 1: j] #i
// t1 = local B (complit)
// t2 = &t1.p [#0]
// *t2 = 9:int
// t3 = *t1
// t4 = make I <- B (t3)
// *t0 = t4
// if c goto 3 else 5
// 3:
// t5 = *t0
// t6 = invoke t5.Foo()
// jump 4
// 4:
// return
// 5:
// t7 = *j
// t8 = invoke t7.Foo()
// jump 4
// Flow chain showing that B reaches (*i).foo():
// t3 (B) -> t4 -> t0 -> t5
// Flow chain showing that B reaches (*j).foo():
// t3 (B) -> t4 -> t0 <--> j -> t7
// WANT:
// Local(t0) -> Local(i), Local(j), Local(t5)
// Local(i) -> Local(t0)
// Local(j) -> Local(t0), Local(t7)
// Local(t3) -> Local(t4)
// Local(t4) -> Local(t0)

View File

@ -1,3 +1,9 @@
// 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.
// go:build ignore
package testdata
var gl int

41
go/callgraph/vta/testdata/store.go vendored Normal file
View File

@ -0,0 +1,41 @@
// 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.
// go:build ignore
package testdata
// Tests graph creation for store/load and make instructions.
// Note that ssa package does not have a load instruction per
// se. Yet, one is encoded as a unary instruction with the
// * operator.
type A struct{}
type I interface{ foo() }
func (a A) foo() {}
func main() {
a := A{}
var i I
i = a
ii := &i
(*ii).foo()
}
// Relevant SSA:
// t0 = local A (a)
// t1 = new I (i)
// t2 = *t0 no interesting flow: concrete types
// t3 = make I <- A (t2) t2 -> t3
// *t1 = t3 t3 -> t1
// t4 = *t1 t1 -> t4
// t5 = invoke t4.foo()
// return
// WANT:
// Local(t2) -> Local(t3)
// Local(t3) -> Local(t1)
// Local(t1) -> Local(t4)

View File

@ -0,0 +1,52 @@
// 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.
// go:build ignore
package testdata
type A struct{}
func (a A) foo() {}
type I interface{ foo() }
func Baz(i I) {
j := &i
k := &j
**k = A{}
i.foo()
(**k).foo()
}
// Relevant SSA:
// func Baz(i I):
// t0 = new I (i)
// *t0 = i
// t1 = new *I (j)
// *t1 = t0
// t2 = *t1
// t3 = local A (complit)
// t4 = *t3
// t5 = make I <- A (t4)
// *t2 = t5
// t6 = *t0
// t7 = invoke t6.foo()
// t8 = *t1
// t9 = *t8
// t10 = invoke t9.foo()
// Flow chain showing that A reaches i.foo():
// t4 (A) -> t5 -> t2 <-> PtrInterface(I) <-> t0 -> t6
// Flow chain showing that A reaches (**k).foo():
// t4 (A) -> t5 -> t2 <-> PtrInterface(I) <-> t8 -> t9
// WANT:
// Local(i) -> Local(t0)
// Local(t0) -> Local(t6), PtrInterface(testdata.I)
// PtrInterface(testdata.I) -> Local(t0), Local(t2), Local(t8)
// Local(t2) -> PtrInterface(testdata.I)
// Local(t4) -> Local(t5)
// Local(t5) -> Local(t2)
// Local(t8) -> Local(t9), PtrInterface(testdata.I)

View File

@ -0,0 +1,56 @@
// 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.
// go:build ignore
package testdata
type I interface {
Foo()
}
type J interface {
Foo()
Bar()
}
type B struct {
p int
}
func (b B) Foo() {}
func (b B) Bar() {}
func Baz(b *B, S []*I, s []J) {
var x [3]I
x[1] = b
a := &s[2]
(*a).Bar()
print([3]*I{nil, nil, nil}[2])
}
// Relevant SSA:
// func Baz(b *B, S []*I, s []J):
// t0 = local [3]I (x)
// t1 = &t0[1:int]
// ...
// t3 = &s[2:int]
// t4 = *t3
// ...
// t6 = local [3]*I (complit)
// t7 = &t6[0:int]
// ...
// t11 = t10[2:int]
// ...
// WANT:
// Slice([]testdata.I) -> Local(t1)
// Local(t1) -> Slice([]testdata.I)
// Slice([]testdata.J) -> Local(t3)
// Local(t3) -> Local(t4), Slice([]testdata.J)
// Local(t11) -> Slice([]*testdata.I)
// Slice([]*testdata.I) -> Local(t11), PtrInterface(testdata.I)
// Constant(*testdata.I) -> PtrInterface(testdata.I)

View File

@ -0,0 +1,56 @@
// 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.
// go:build ignore
package testdata
// Test program for testing type assertions and extract instructions.
// The latter are tested here too since extract instruction comes
// naturally in type assertions.
type I interface {
Foo()
}
type J interface {
Foo()
Bar()
}
type A struct {
c int
}
func (a A) Foo() {}
func (a A) Bar() {}
func Baz(i I) {
j, ok := i.(J)
if ok {
j.Foo()
}
a := i.(*A)
a.Bar()
}
// Relevant SSA:
// func Baz(i I):
// t0 = typeassert,ok i.(J)
// t1 = extract t0 #0
// t2 = extract t0 #1
// if t2 goto 1 else 2
// 1:
// t3 = invoke t1.Foo()
// jump 2
// 2:
// t4 = typeassert i.(*A) // no flow since t4 is of concrete type
// t5 = *t4
// t6 = (A).Bar(t5)
// return
// WANT:
// Local(i) -> Local(t0[0])
// Local(t0[0]) -> Local(t1)

View File

@ -0,0 +1,85 @@
// 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.
// go:build ignore
package testdata
type Y interface {
Foo()
Bar(float64)
}
type Z Y
type W interface {
Y
}
type A struct{}
func (a A) Foo() { print("A:Foo") }
func (a A) Bar(f float64) { print(uint(f)) }
type B struct{}
func (b B) Foo() { print("B:Foo") }
func (b B) Bar(f float64) { print(uint(f) + 1) }
type X interface {
Foo()
}
func Baz(y Y) {
z := Z(y)
z.Foo()
x := X(y)
x.Foo()
y = A{}
var y_p *Y = &y
w_p := (*W)(y_p)
*w_p = B{}
(*y_p).Foo() // prints B:Foo
(*w_p).Foo() // prints B:Foo
}
// Relevant SSA:
// func Baz(y Y):
// t0 = new Y (y)
// *t0 = y
// t1 = *t0
// t2 = changetype Z <- Y (t1)
// t3 = invoke t2.Foo()
//
// t4 = *t0
// t5 = change interface X <- Y (t4)
// t6 = invoke t5.Foo()
//
// t7 = local A (complit)
// t8 = *t7
// t9 = make Y <- A (t8)
// *t0 = t9
// t10 = changetype *W <- *Y (t0)
// t11 = local B (complit)
// t12 = *t11
// t13 = make W <- B (t12)
// *t10 = t13
// t14 = *t0
// t15 = invoke t14.Foo()
// t16 = *t10
// t17 = invoke t16.Foo()
// return
// WANT:
// Local(t1) -> Local(t2)
// Local(t4) -> Local(t5)
// Local(t0) -> Local(t1), Local(t10), Local(t14), Local(t4)
// Local(y) -> Local(t0)
// Local(t8) -> Local(t9)
// Local(t9) -> Local(t0)
// Local(t13) -> Local(t10)

86
go/callgraph/vta/utils.go Normal file
View File

@ -0,0 +1,86 @@
// 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"
)
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.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 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 _, ok := t.Underlying().(*types.Signature); ok {
return true
}
if i := interfaceUnderPtr(t); i != nil {
return true
}
return isInterface(t)
}
// hasInitialTypes check if a node can have initial types.
// Returns true iff `n` is not a panic or recover node as
// those are artifical.
func hasInitialTypes(n node) bool {
switch n.(type) {
case panicArg, recoverReturn:
return false
default:
return true
}
}
func isInterface(t types.Type) bool {
_, ok := t.Underlying().(*types.Interface)
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 {
p, ok := t.Underlying().(*types.Pointer)
if !ok {
return nil
}
if isInterface(p.Elem()) {
return p.Elem()
}
return interfaceUnderPtr(p.Elem())
}