Skip to content

Commit 716319f

Browse files
committed
vta: finalizes VTA graph construction by adding support for function calls
These include call and return statements as well as closure creations and panics/recovers. Change-Id: Iee4a4e48e1b9c304959fbce4f3eb43eecd8cb851 Reviewed-on: https://go-review.googlesource.com/c/tools/+/323049 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 234f954 commit 716319f

File tree

8 files changed

+404
-2
lines changed

8 files changed

+404
-2
lines changed

go/callgraph/vta/graph.go

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ type builder struct {
262262
}
263263

264264
func (b *builder) visit(funcs map[*ssa.Function]bool) {
265+
// Add the fixed edge Panic -> Recover
266+
b.graph.addEdge(panicArg{}, recoverReturn{})
267+
265268
for f, in := range funcs {
266269
if in {
267270
b.fun(f)
@@ -283,6 +286,8 @@ func (b *builder) instr(instr ssa.Instruction) {
283286
b.addInFlowAliasEdges(b.nodeFromVal(i.Addr), b.nodeFromVal(i.Val))
284287
case *ssa.MakeInterface:
285288
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
289+
case *ssa.MakeClosure:
290+
b.closure(i)
286291
case *ssa.UnOp:
287292
b.unop(i)
288293
case *ssa.Phi:
@@ -333,14 +338,19 @@ func (b *builder) instr(instr ssa.Instruction) {
333338
b.mapUpdate(i)
334339
case *ssa.Next:
335340
b.next(i)
341+
case ssa.CallInstruction:
342+
b.call(i)
343+
case *ssa.Panic:
344+
b.panic(i)
345+
case *ssa.Return:
346+
b.rtrn(i)
336347
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
337348
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
338349
*ssa.Slice, *ssa.Range, *ssa.RunDefers:
339350
// No interesting flow here.
340351
return
341352
default:
342-
// TODO(zpavlinovic): make into a panic once all instructions are supported.
343-
fmt.Printf("unsupported instruction %v\n", instr)
353+
panic(fmt.Sprintf("unsupported instruction %v\n", instr))
344354
}
345355
}
346356

@@ -500,6 +510,97 @@ func (b *builder) addInFlowAliasEdges(l, r node) {
500510
}
501511
}
502512

513+
func (b *builder) closure(c *ssa.MakeClosure) {
514+
f := c.Fn.(*ssa.Function)
515+
b.addInFlowEdge(function{f: f}, b.nodeFromVal(c))
516+
517+
for i, fv := range f.FreeVars {
518+
b.addInFlowAliasEdges(b.nodeFromVal(fv), b.nodeFromVal(c.Bindings[i]))
519+
}
520+
}
521+
522+
// panic creates a flow from arguments to panic instructions to return
523+
// registers of all recover statements in the program. Introduces a
524+
// global panic node Panic and
525+
// 1) for every panic statement p: add p -> Panic
526+
// 2) for every recover statement r: add Panic -> r (handled in call)
527+
// TODO(zpavlinovic): improve precision by explicitly modeling how panic
528+
// values flow from callees to callers and into deferred recover instructions.
529+
func (b *builder) panic(p *ssa.Panic) {
530+
// Panics often have, for instance, strings as arguments which do
531+
// not create interesting flows.
532+
if !canHaveMethods(p.X.Type()) {
533+
return
534+
}
535+
536+
b.addInFlowEdge(b.nodeFromVal(p.X), panicArg{})
537+
}
538+
539+
// call adds flows between arguments/parameters and return values/registers
540+
// for both static and dynamic calls, as well as go and defer calls.
541+
func (b *builder) call(c ssa.CallInstruction) {
542+
// When c is r := recover() call register instruction, we add Recover -> r.
543+
if bf, ok := c.Common().Value.(*ssa.Builtin); ok && bf.Name() == "recover" {
544+
b.addInFlowEdge(recoverReturn{}, b.nodeFromVal(c.(*ssa.Call)))
545+
return
546+
}
547+
548+
for _, f := range siteCallees(c, b.callGraph) {
549+
addArgumentFlows(b, c, f)
550+
}
551+
}
552+
553+
func addArgumentFlows(b *builder, c ssa.CallInstruction, f *ssa.Function) {
554+
cc := c.Common()
555+
// When c is an unresolved method call (cc.Method != nil), cc.Value contains
556+
// the receiver object rather than cc.Args[0].
557+
if cc.Method != nil {
558+
b.addInFlowAliasEdges(b.nodeFromVal(f.Params[0]), b.nodeFromVal(cc.Value))
559+
}
560+
561+
offset := 0
562+
if cc.Method != nil {
563+
offset = 1
564+
}
565+
for i, v := range cc.Args {
566+
b.addInFlowAliasEdges(b.nodeFromVal(f.Params[i+offset]), b.nodeFromVal(v))
567+
}
568+
}
569+
570+
// rtrn produces flows between values of r and c where
571+
// c is a call instruction that resolves to the enclosing
572+
// function of r based on b.callGraph.
573+
func (b *builder) rtrn(r *ssa.Return) {
574+
n := b.callGraph.Nodes[r.Parent()]
575+
// n != nil when b.callgraph is sound, but the client can
576+
// pass any callgraph, including an underapproximate one.
577+
if n == nil {
578+
return
579+
}
580+
581+
for _, e := range n.In {
582+
if cv, ok := e.Site.(ssa.Value); ok {
583+
addReturnFlows(b, r, cv)
584+
}
585+
}
586+
}
587+
588+
func addReturnFlows(b *builder, r *ssa.Return, site ssa.Value) {
589+
results := r.Results
590+
if len(results) == 1 {
591+
// When there is only one return value, the destination register does not
592+
// have a tuple type.
593+
b.addInFlowEdge(b.nodeFromVal(results[0]), b.nodeFromVal(site))
594+
return
595+
}
596+
597+
tup := site.Type().Underlying().(*types.Tuple)
598+
for i, r := range results {
599+
local := indexedLocal{val: site, typ: tup.At(i).Type(), index: i}
600+
b.addInFlowEdge(b.nodeFromVal(r), local)
601+
}
602+
}
603+
503604
// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
504605
// that represents an interface or an unresolved function value. Otherwise, there
505606
// is no interesting type flow so the edge is ommited.

go/callgraph/vta/graph_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ func TestVTAGraphConstruction(t *testing.T) {
241241
"testdata/stores_arrays.go",
242242
"testdata/maps.go",
243243
"testdata/ranges.go",
244+
"testdata/closures.go",
245+
"testdata/static_calls.go",
246+
"testdata/dynamic_calls.go",
247+
"testdata/returns.go",
248+
"testdata/panic.go",
244249
} {
245250
t.Run(file, func(t *testing.T) {
246251
prog, want, err := testProg(file)

go/callgraph/vta/testdata/closures.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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()
11+
}
12+
13+
func Do(i I) { i.Foo() }
14+
15+
func Baz(b bool, h func(I)) {
16+
var i I
17+
a := func(g func(I)) {
18+
g(i)
19+
}
20+
21+
if b {
22+
h = Do
23+
}
24+
25+
a(h)
26+
}
27+
28+
// Relevant SSA:
29+
// func Baz(b bool, h func(I)):
30+
// t0 = new I (i)
31+
// t1 = make closure Baz$1 [t0]
32+
// if b goto 1 else 2
33+
// 1:
34+
// jump 2
35+
// 2:
36+
// t2 = phi [0: h, 1: Do] #h
37+
// t3 = t1(t2)
38+
// return
39+
//
40+
// func Baz$1(g func(I)):
41+
// t0 = *i
42+
// t1 = g(t0)
43+
// return
44+
45+
// In the edge set Local(i) -> Local(t0), Local(t0) below,
46+
// two occurrences of t0 come from t0 in Baz and Baz$1.
47+
48+
// WANT:
49+
// Function(Do) -> Local(t2)
50+
// Function(Baz$1) -> Local(t1)
51+
// Local(h) -> Local(t2)
52+
// Local(t0) -> Local(i)
53+
// Local(i) -> Local(t0), Local(t0)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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(I)
11+
}
12+
13+
type A struct{}
14+
15+
func (a A) foo(ai I) {}
16+
17+
type B struct{}
18+
19+
func (b B) foo(bi I) {}
20+
21+
func doWork() I { return nil }
22+
func close() I { return nil }
23+
24+
func Baz(x B, h func() I, i I) I {
25+
i.foo(x)
26+
27+
return h()
28+
}
29+
30+
// Relevant SSA:
31+
// func Baz(x B, h func() I, i I) I:
32+
// t0 = local B (x)
33+
// *t0 = x
34+
// t1 = *t0
35+
// t2 = make I <- B (t1)
36+
// t3 = invoke i.foo(t2)
37+
// t4 = h()
38+
// return t4
39+
40+
// WANT:
41+
// Local(t2) -> Local(ai), Local(bi)
42+
// Constant(testdata.I) -> Local(t4)
43+
// Local(t1) -> Local(t2)

go/callgraph/vta/testdata/panic.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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()
11+
}
12+
13+
type A struct{}
14+
15+
func (a A) foo() {}
16+
17+
func recover1() {
18+
print("only this recover should execute")
19+
if r, ok := recover().(I); ok {
20+
r.foo()
21+
}
22+
}
23+
24+
func recover2() {
25+
recover()
26+
}
27+
28+
func Baz(a A) {
29+
defer recover1()
30+
panic(a)
31+
}
32+
33+
// Relevant SSA:
34+
// func recover1():
35+
// 0:
36+
// t0 = print("only this recover...":string)
37+
// t1 = recover()
38+
// t2 = typeassert,ok t1.(I)
39+
// t3 = extract t2 #0
40+
// t4 = extract t2 #1
41+
// if t4 goto 1 else 2
42+
// 1:
43+
// t5 = invoke t3.foo()
44+
// jump 2
45+
// 2:
46+
// return
47+
//
48+
// func recover2():
49+
// t0 = recover()
50+
// return
51+
//
52+
// func Baz(i I):
53+
// t0 = local A (a)
54+
// *t0 = a
55+
// defer recover1()
56+
// t1 = *t0
57+
// t2 = make interface{} <- A (t1)
58+
// panic t2
59+
60+
// t2 argument to panic in Baz gets ultimately connected to recover
61+
// registers t1 in recover1() and t0 in recover2().
62+
63+
// WANT:
64+
// Panic -> Recover
65+
// Local(t2) -> Panic
66+
// Recover -> Local(t0), Local(t1)

go/callgraph/vta/testdata/returns.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
11+
func Bar(ii I) (I, I) {
12+
return Foo(ii)
13+
}
14+
15+
func Foo(iii I) (I, I) {
16+
return iii, iii
17+
}
18+
19+
func Do(j I) *I {
20+
return &j
21+
}
22+
23+
func Baz(i I) *I {
24+
Bar(i)
25+
return Do(i)
26+
}
27+
28+
// Relevant SSA:
29+
// func Bar(ii I) (I, I):
30+
// t0 = Foo(ii)
31+
// t1 = extract t0 #0
32+
// t2 = extract t0 #1
33+
// return t1, t2
34+
//
35+
// func Foo(iii I) (I, I):
36+
// return iii, iii
37+
//
38+
// func Do(j I) *I:
39+
// t0 = new I (j)
40+
// *t0 = j
41+
// return t0
42+
//
43+
// func Baz(i I):
44+
// t0 = Bar(i)
45+
// t1 = Do(i)
46+
// return t1
47+
48+
// t0 and t1 in the last edge correspond to the nodes
49+
// of Do and Baz. This edge is induced by Do(i).
50+
51+
// WANT:
52+
// Local(i) -> Local(ii), Local(j)
53+
// Local(ii) -> Local(iii)
54+
// Local(iii) -> Local(t0[0]), Local(t0[1])
55+
// Local(t1) -> Local(t0[0])
56+
// Local(t2) -> Local(t0[1])
57+
// Local(t0) -> Local(t1)

0 commit comments

Comments
 (0)