Skip to content

Commit 234f954

Browse files
committed
vta: extends VTA graph construction to handle collections
Collections include channels, slices, ranges, maps, and their related SSA statements. Statements involving functions and function calls will be addressed in a future CL. Change-Id: I552cbf3ee9d65e125270db69d1fc3c3f6491b121 Reviewed-on: https://go-review.googlesource.com/c/tools/+/322951 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>
1 parent 126df1d commit 234f954

File tree

7 files changed

+325
-1
lines changed

7 files changed

+325
-1
lines changed

go/callgraph/vta/graph.go

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,20 @@ func (b *builder) instr(instr ssa.Instruction) {
319319
b.field(i)
320320
case *ssa.FieldAddr:
321321
b.fieldAddr(i)
322+
case *ssa.Send:
323+
b.send(i)
324+
case *ssa.Select:
325+
b.selekt(i)
326+
case *ssa.Index:
327+
b.index(i)
328+
case *ssa.IndexAddr:
329+
b.indexAddr(i)
330+
case *ssa.Lookup:
331+
b.lookup(i)
332+
case *ssa.MapUpdate:
333+
b.mapUpdate(i)
334+
case *ssa.Next:
335+
b.next(i)
322336
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
323337
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
324338
*ssa.Slice, *ssa.Range, *ssa.RunDefers:
@@ -336,7 +350,8 @@ func (b *builder) unop(u *ssa.UnOp) {
336350
// Multiplication operator * is used here as a dereference operator.
337351
b.addInFlowAliasEdges(b.nodeFromVal(u), b.nodeFromVal(u.X))
338352
case token.ARROW:
339-
// TODO(zpavlinovic): add support for channels.
353+
t := u.X.Type().Underlying().(*types.Chan).Elem()
354+
b.addInFlowAliasEdges(b.nodeFromVal(u), channelElem{typ: t})
340355
default:
341356
// There is no interesting type flow otherwise.
342357
}
@@ -388,6 +403,91 @@ func (b *builder) fieldAddr(f *ssa.FieldAddr) {
388403
b.addInFlowEdge(b.nodeFromVal(f), fnode)
389404
}
390405

406+
func (b *builder) send(s *ssa.Send) {
407+
t := s.Chan.Type().Underlying().(*types.Chan).Elem()
408+
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(s.X))
409+
}
410+
411+
// selekt generates flows for select statement
412+
// a = select blocking/nonblocking [c_1 <- t_1, c_2 <- t_2, ..., <- o_1, <- o_2, ...]
413+
// between receiving channel registers c_i and corresponding input register t_i. Further,
414+
// flows are generated between o_i and a[2 + i]. Note that a is a tuple register of type
415+
// <int, bool, r_1, r_2, ...> where the type of r_i is the element type of channel o_i.
416+
func (b *builder) selekt(s *ssa.Select) {
417+
recvIndex := 0
418+
for _, state := range s.States {
419+
t := state.Chan.Type().Underlying().(*types.Chan).Elem()
420+
421+
if state.Dir == types.SendOnly {
422+
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(state.Send))
423+
} else {
424+
// state.Dir == RecvOnly by definition of select instructions.
425+
tupEntry := indexedLocal{val: s, typ: t, index: 2 + recvIndex}
426+
b.addInFlowAliasEdges(tupEntry, channelElem{typ: t})
427+
recvIndex++
428+
}
429+
}
430+
}
431+
432+
// index instruction a := b[c] on slices creates flows between a and
433+
// SliceElem(t) flow where t is an interface type of c. Arrays and
434+
// slice elements are both modeled as SliceElem.
435+
func (b *builder) index(i *ssa.Index) {
436+
et := sliceArrayElem(i.X.Type())
437+
b.addInFlowAliasEdges(b.nodeFromVal(i), sliceElem{typ: et})
438+
}
439+
440+
// indexAddr instruction a := &b[c] fetches address of a index
441+
// into the field so we create bidirectional flow a <-> SliceElem(t)
442+
// where t is an interface type of c. Arrays and slice elements are
443+
// both modeled as SliceElem.
444+
func (b *builder) indexAddr(i *ssa.IndexAddr) {
445+
et := sliceArrayElem(i.X.Type())
446+
b.addInFlowEdge(sliceElem{typ: et}, b.nodeFromVal(i))
447+
b.addInFlowEdge(b.nodeFromVal(i), sliceElem{typ: et})
448+
}
449+
450+
// lookup handles map query commands a := m[b] where m is of type
451+
// map[...]V and V is an interface. It creates flows between `a`
452+
// and MapValue(V).
453+
func (b *builder) lookup(l *ssa.Lookup) {
454+
t, ok := l.X.Type().Underlying().(*types.Map)
455+
if !ok {
456+
// No interesting flows for string lookups.
457+
return
458+
}
459+
b.addInFlowAliasEdges(b.nodeFromVal(l), mapValue{typ: t.Elem()})
460+
}
461+
462+
// mapUpdate handles map update commands m[b] = a where m is of type
463+
// map[K]V and K and V are interfaces. It creates flows between `a`
464+
// and MapValue(V) as well as between MapKey(K) and `b`.
465+
func (b *builder) mapUpdate(u *ssa.MapUpdate) {
466+
t, ok := u.Map.Type().Underlying().(*types.Map)
467+
if !ok {
468+
// No interesting flows for string updates.
469+
return
470+
}
471+
472+
b.addInFlowAliasEdges(mapKey{typ: t.Key()}, b.nodeFromVal(u.Key))
473+
b.addInFlowAliasEdges(mapValue{typ: t.Elem()}, b.nodeFromVal(u.Value))
474+
}
475+
476+
// next instruction <ok, key, value> := next r, where r
477+
// is a range over map or string generates flow between
478+
// key and MapKey as well value and MapValue nodes.
479+
func (b *builder) next(n *ssa.Next) {
480+
if n.IsString {
481+
return
482+
}
483+
tup := n.Type().Underlying().(*types.Tuple)
484+
kt := tup.At(1).Type()
485+
vt := tup.At(2).Type()
486+
487+
b.addInFlowAliasEdges(indexedLocal{val: n, typ: kt, index: 1}, mapKey{typ: kt})
488+
b.addInFlowAliasEdges(indexedLocal{val: n, typ: vt, index: 2}, mapValue{typ: vt})
489+
}
490+
391491
// addInFlowAliasEdges adds an edge r -> l to b.graph if l is a node that can
392492
// have an inflow, i.e., a node that represents an interface or an unresolved
393493
// function value. Similarly for the edge l -> r with an additional condition

go/callgraph/vta/graph_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ func TestVTAGraphConstruction(t *testing.T) {
236236
"testdata/node_uniqueness.go",
237237
"testdata/store_load_alias.go",
238238
"testdata/phi_alias.go",
239+
"testdata/channels.go",
240+
"testdata/select.go",
241+
"testdata/stores_arrays.go",
242+
"testdata/maps.go",
243+
"testdata/ranges.go",
239244
} {
240245
t.Run(file, func(t *testing.T) {
241246
prog, want, err := testProg(file)

go/callgraph/vta/testdata/channels.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// go:build ignore
6+
7+
package testdata
8+
9+
func foo(c chan interface{}, j int) {
10+
c <- j + 1
11+
}
12+
13+
func Baz(i int) {
14+
c := make(chan interface{})
15+
go foo(c, i)
16+
x := <-c
17+
print(x)
18+
}
19+
20+
// Relevant SSA:
21+
// func foo(c chan interface{}, j int):
22+
// t0 = j + 1:int
23+
// t1 = make interface{} <- int (t0)
24+
// send c <- t1 // t1 -> chan {}interface
25+
// return
26+
//
27+
// func Baz(i int):
28+
// t0 = make chan interface{} 0:int
29+
// go foo(t0, i)
30+
// t1 = <-t0 // chan {}interface -> t1
31+
// t2 = print(t1)
32+
// return
33+
34+
// WANT:
35+
// Channel(chan interface{}) -> Local(t1)
36+
// Local(t1) -> Channel(chan interface{})

go/callgraph/vta/testdata/maps.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// go:build ignore
6+
7+
package testdata
8+
9+
type I interface {
10+
Foo() string
11+
}
12+
13+
type J interface {
14+
Foo() string
15+
Bar()
16+
}
17+
18+
type B struct {
19+
p string
20+
}
21+
22+
func (b B) Foo() string { return b.p }
23+
func (b B) Bar() {}
24+
25+
func Baz(m map[I]I, b1, b2 B, n map[string]*J) *J {
26+
m[b1] = b2
27+
28+
return n[b1.Foo()]
29+
}
30+
31+
// Relevant SSA:
32+
// func Baz(m map[I]I, b1 B, b2 B, n map[string]*J) *J:
33+
// t0 = local B (b1)
34+
// *t0 = b1
35+
// t1 = local B (b2)
36+
// *t1 = b2
37+
// t2 = *t0
38+
// t3 = make I <- B (t2)
39+
// t4 = *t1
40+
// t5 = make I <- B (t4)
41+
// m[t3] = t5
42+
// t6 = *t0
43+
// t7 = (B).Foo(t6)
44+
// t8 = n[t7]
45+
// return t8
46+
47+
// WANT:
48+
// Local(t4) -> Local(t5)
49+
// Local(t5) -> MapValue(testdata.I)
50+
// Local(t3) -> MapKey(testdata.I)
51+
// Local(t8) -> MapValue(*testdata.J)
52+
// MapValue(*testdata.J) -> Local(t8)

go/callgraph/vta/testdata/ranges.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// go:build ignore
6+
7+
package testdata
8+
9+
type I interface {
10+
Foo() string
11+
}
12+
13+
type B struct {
14+
p string
15+
}
16+
17+
func (b B) Foo() string { return b.p }
18+
19+
func Baz(m map[I]*I) {
20+
for i, v := range m {
21+
*v = B{p: i.Foo()}
22+
}
23+
}
24+
25+
// Relevant SSA:
26+
// func Baz(m map[I]*I):
27+
// 0:
28+
// t0 = range m
29+
// jump 1
30+
// 1:
31+
// t1 = next t0
32+
// t2 = extract t1 #0
33+
// if t2 goto 2 else 3
34+
// 2:
35+
// t3 = extract t1 #1
36+
// t4 = extract t1 #2
37+
// t5 = local B (complit)
38+
// t6 = &t5.p [#0]
39+
// t7 = invoke t3.Foo()
40+
// *t6 = t7
41+
// t8 = *t5
42+
// t9 = make I <- B (t8)
43+
// *t4 = t9
44+
// jump 1
45+
// 3:
46+
// return
47+
48+
// WANT:
49+
// MapKey(testdata.I) -> Local(t1[1])
50+
// Local(t1[1]) -> Local(t3)
51+
// MapValue(*testdata.I) -> Local(t1[2])
52+
// Local(t1[2]) -> Local(t4), MapValue(*testdata.I)
53+
// Local(t8) -> Local(t9)
54+
// Local(t9) -> Local(t4)
55+
// Local(t4) -> Local(t1[2])

go/callgraph/vta/testdata/select.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// go:build ignore
6+
7+
package testdata
8+
9+
type I interface {
10+
Foo() string
11+
}
12+
13+
type J interface {
14+
I
15+
}
16+
17+
type B struct {
18+
p string
19+
}
20+
21+
func (b B) Foo() string { return b.p }
22+
23+
func Baz(b1, b2 B, c1 chan I, c2 chan J) {
24+
for {
25+
select {
26+
case c1 <- b1:
27+
print("b1")
28+
case c2 <- b2:
29+
print("b2")
30+
case <-c1:
31+
print("c1")
32+
case k := <-c2:
33+
print(k.Foo())
34+
return
35+
}
36+
}
37+
}
38+
39+
// Relevant SSA:
40+
// func Baz(b1 B, b2 B, c1 chan I, c2 chan J):
41+
// ...
42+
// t2 = *t0
43+
// t3 = make I <- B (t2)
44+
// t4 = *t1
45+
// t5 = make J <- B (t4)
46+
// t6 = select blocking [c1<-t3, c2<-t5, <-c1, <-c2] (index int, ok bool, I, J)
47+
// t7 = extract t6 #0
48+
// t8 = t7 == 0:int
49+
// if t8 goto 2 else 3
50+
// ...
51+
// 8:
52+
// t15 = extract t6 #3
53+
// t16 = invoke t15.Foo()
54+
// t17 = print(t18)
55+
56+
// WANT:
57+
// Local(t3) -> Channel(chan testdata.I)
58+
// Local(t5) -> Channel(chan testdata.J)
59+
// Channel(chan testdata.I) -> Local(t6[2])
60+
// Channel(chan testdata.J) -> Local(t6[3])

go/callgraph/vta/utils.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,19 @@ func interfaceUnderPtr(t types.Type) types.Type {
8484

8585
return interfaceUnderPtr(p.Elem())
8686
}
87+
88+
// sliceArrayElem returns the element type of type `t` that is
89+
// expected to be a (pointer to) array or slice, consistent with
90+
// the ssa.Index and ssa.IndexAddr instructions. Panics otherwise.
91+
func sliceArrayElem(t types.Type) types.Type {
92+
u := t.Underlying()
93+
94+
if p, ok := u.(*types.Pointer); ok {
95+
u = p.Elem().Underlying()
96+
}
97+
98+
if a, ok := u.(*types.Array); ok {
99+
return a.Elem()
100+
}
101+
return u.(*types.Slice).Elem()
102+
}

0 commit comments

Comments
 (0)