mirror of https://github.com/golang/go.git
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:
parent
8f2cf6ccfc
commit
bf132055df
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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())
|
||||
}
|
||||
Loading…
Reference in New Issue