diff --git a/go/callgraph/vta/graph.go b/go/callgraph/vta/graph.go index 6ca765b062..91f3a3b97f 100644 --- a/go/callgraph/vta/graph.go +++ b/go/callgraph/vta/graph.go @@ -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 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 +} diff --git a/go/callgraph/vta/graph_test.go b/go/callgraph/vta/graph_test.go index 0b5ec7e63a..ee66d4ab9f 100644 --- a/go/callgraph/vta/graph_test.go +++ b/go/callgraph/vta/graph_test.go @@ -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) + } + }) + } +} diff --git a/go/callgraph/vta/testdata/fields.go b/go/callgraph/vta/testdata/fields.go new file mode 100644 index 0000000000..e53932758e --- /dev/null +++ b/go/callgraph/vta/testdata/fields.go @@ -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) diff --git a/go/callgraph/vta/testdata/node_uniqueness.go b/go/callgraph/vta/testdata/node_uniqueness.go new file mode 100644 index 0000000000..0c1dc074f3 --- /dev/null +++ b/go/callgraph/vta/testdata/node_uniqueness.go @@ -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) diff --git a/go/callgraph/vta/testdata/phi.go b/go/callgraph/vta/testdata/phi.go new file mode 100644 index 0000000000..2144a2cbcd --- /dev/null +++ b/go/callgraph/vta/testdata/phi.go @@ -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) diff --git a/go/callgraph/vta/testdata/phi_alias.go b/go/callgraph/vta/testdata/phi_alias.go new file mode 100644 index 0000000000..d4c414d54e --- /dev/null +++ b/go/callgraph/vta/testdata/phi_alias.go @@ -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) diff --git a/go/callgraph/vta/testdata/simple.go b/go/callgraph/vta/testdata/simple.go index 95920219d3..d3bfbe7928 100644 --- a/go/callgraph/vta/testdata/simple.go +++ b/go/callgraph/vta/testdata/simple.go @@ -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 diff --git a/go/callgraph/vta/testdata/store.go b/go/callgraph/vta/testdata/store.go new file mode 100644 index 0000000000..8a36d4e949 --- /dev/null +++ b/go/callgraph/vta/testdata/store.go @@ -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) diff --git a/go/callgraph/vta/testdata/store_load_alias.go b/go/callgraph/vta/testdata/store_load_alias.go new file mode 100644 index 0000000000..b9814f0b85 --- /dev/null +++ b/go/callgraph/vta/testdata/store_load_alias.go @@ -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) diff --git a/go/callgraph/vta/testdata/stores_arrays.go b/go/callgraph/vta/testdata/stores_arrays.go new file mode 100644 index 0000000000..80de2b098f --- /dev/null +++ b/go/callgraph/vta/testdata/stores_arrays.go @@ -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) diff --git a/go/callgraph/vta/testdata/type_assertions.go b/go/callgraph/vta/testdata/type_assertions.go new file mode 100644 index 0000000000..d4e8e3327b --- /dev/null +++ b/go/callgraph/vta/testdata/type_assertions.go @@ -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) diff --git a/go/callgraph/vta/testdata/type_conversions.go b/go/callgraph/vta/testdata/type_conversions.go new file mode 100644 index 0000000000..e18077f444 --- /dev/null +++ b/go/callgraph/vta/testdata/type_conversions.go @@ -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) diff --git a/go/callgraph/vta/utils.go b/go/callgraph/vta/utils.go new file mode 100644 index 0000000000..e3c0f952f1 --- /dev/null +++ b/go/callgraph/vta/utils.go @@ -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()) +}