GoPLS Viewer

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