/
callers.go
194 lines (168 loc) · 5.28 KB
/
callers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Copyright 2013 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 main
import (
"fmt"
"go/token"
"go/types"
"github.com/dwahler/go-tools/cmd/guru/serial"
"github.com/dwahler/go-tools/go/callgraph"
"github.com/dwahler/go-tools/go/loader"
"github.com/dwahler/go-tools/go/ssa"
"github.com/dwahler/go-tools/go/ssa/ssautil"
)
// The callers function reports the possible callers of the function
// immediately enclosing the specified source location.
func callers(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return fmt.Errorf("this position is not inside a function")
}
// Defer SSA construction till after errors are reported.
prog.Build()
target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// If the function is never address-taken, all calls are direct
// and can be found quickly by inspecting the whole SSA program.
cg := directCallsTo(target, entryPoints(ptaConfig.Mains))
if cg == nil {
// Run the pointer analysis, recording each
// call found to originate from target.
// (Pointer analysis may return fewer results than
// directCallsTo because it ignores dead code.)
ptaConfig.BuildCallGraph = true
cg = ptrAnalysis(ptaConfig).CallGraph
}
cg.DeleteSyntheticNodes()
edges := cg.CreateNode(target).In
// TODO(adonovan): sort + dedup calls to ensure test determinism.
q.Output(lprog.Fset, &callersResult{
target: target,
callgraph: cg,
edges: edges,
})
return nil
}
// directCallsTo inspects the whole program and returns a callgraph
// containing edges for all direct calls to the target function.
// directCallsTo returns nil if the function is ever address-taken.
func directCallsTo(target *ssa.Function, entrypoints []*ssa.Function) *callgraph.Graph {
cg := callgraph.New(nil) // use nil as root *Function
targetNode := cg.CreateNode(target)
// Is the function a program entry point?
// If so, add edge from callgraph root.
for _, f := range entrypoints {
if f == target {
callgraph.AddEdge(cg.Root, nil, targetNode)
}
}
// Find receiver type (for methods).
var recvType types.Type
if recv := target.Signature.Recv(); recv != nil {
recvType = recv.Type()
}
// Find all direct calls to function,
// or a place where its address is taken.
var space [32]*ssa.Value // preallocate
for fn := range ssautil.AllFunctions(target.Prog) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
// Is this a method (T).f of a concrete type T
// whose runtime type descriptor is address-taken?
// (To be fully sound, we would have to check that
// the type doesn't make it to reflection as a
// subelement of some other address-taken type.)
if recvType != nil {
if mi, ok := instr.(*ssa.MakeInterface); ok {
if types.Identical(mi.X.Type(), recvType) {
return nil // T is address-taken
}
if ptr, ok := mi.X.Type().(*types.Pointer); ok &&
types.Identical(ptr.Elem(), recvType) {
return nil // *T is address-taken
}
}
}
// Direct call to target?
rands := instr.Operands(space[:0])
if site, ok := instr.(ssa.CallInstruction); ok &&
site.Common().Value == target {
callgraph.AddEdge(cg.CreateNode(fn), site, targetNode)
rands = rands[1:] // skip .Value (rands[0])
}
// Address-taken?
for _, rand := range rands {
if rand != nil && *rand == target {
return nil
}
}
}
}
}
return cg
}
func entryPoints(mains []*ssa.Package) []*ssa.Function {
var entrypoints []*ssa.Function
for _, pkg := range mains {
entrypoints = append(entrypoints, pkg.Func("init"))
if main := pkg.Func("main"); main != nil && pkg.Pkg.Name() == "main" {
entrypoints = append(entrypoints, main)
}
}
return entrypoints
}
type callersResult struct {
target *ssa.Function
callgraph *callgraph.Graph
edges []*callgraph.Edge
}
func (r *callersResult) PrintPlain(printf printfFunc) {
root := r.callgraph.Root
if r.edges == nil {
printf(r.target, "%s is not reachable in this program.", r.target)
} else {
printf(r.target, "%s is called from these %d sites:", r.target, len(r.edges))
for _, edge := range r.edges {
if edge.Caller == root {
printf(r.target, "the root of the call graph")
} else {
printf(edge, "\t%s from %s", edge.Description(), edge.Caller.Func)
}
}
}
}
func (r *callersResult) JSON(fset *token.FileSet) []byte {
var callers []serial.Caller
for _, edge := range r.edges {
callers = append(callers, serial.Caller{
Caller: edge.Caller.Func.String(),
Pos: fset.Position(edge.Pos()).String(),
Desc: edge.Description(),
})
}
return toJSON(callers)
}