go.tools/go/callgraph: simplifications to API.
1) We remove context sensitivity from API. The pointer analysis is not sufficiently context-sensitive for the context information to be worth exposing. (The actual analysis precision still benefits from being context-sensitive, though.) Since all clients would discard the context info, we now do that for them. 2) Make the graph doubly-linked. Edges are now shared by the Nodes at both ends of the edge so it's possible to navigate more easily (e.g. to the callers). 3) Graph and Node are now concrete, not interfaces. Less code in every file! LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/66460043
This commit is contained in:
parent
28104d2c91
commit
829d52f2e8
|
@ -4,11 +4,8 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Package callgraph defines the call graph abstraction and various algorithms
|
Package callgraph defines the call graph and various algorithms
|
||||||
and utilities to operate on it. It does not provide a concrete
|
and utilities to operate on it.
|
||||||
implementation but permits other analyses (such as pointer analyses or
|
|
||||||
Rapid Type Analysis) to expose their own call graphs in a
|
|
||||||
representation-independent manner.
|
|
||||||
|
|
||||||
A call graph is a labelled directed graph whose nodes represent
|
A call graph is a labelled directed graph whose nodes represent
|
||||||
functions and whose edge labels represent syntactic function call
|
functions and whose edge labels represent syntactic function call
|
||||||
|
@ -22,27 +19,6 @@ from multiple call sites. Also, it may contain multiple edges
|
||||||
(caller, site, *) that differ only by callee; this indicates a
|
(caller, site, *) that differ only by callee; this indicates a
|
||||||
polymorphic call.
|
polymorphic call.
|
||||||
|
|
||||||
A call graph is called CONTEXT INSENSITIVE if no two nodes in N
|
|
||||||
represent the same syntactic function declaration, i.e. the set of
|
|
||||||
nodes and the set of syntactic functions are in one-to-one
|
|
||||||
correspondence.
|
|
||||||
|
|
||||||
A context-sensitive call graph may have multiple nodes corresponding
|
|
||||||
to the same function; this may yield a more precise approximation to
|
|
||||||
the calling behavior of the program. Consider this program:
|
|
||||||
|
|
||||||
func Apply(fn func(V), value V) { fn(value) }
|
|
||||||
Apply(F, v1)
|
|
||||||
...
|
|
||||||
Apply(G, v2)
|
|
||||||
|
|
||||||
A context-insensitive call graph would represent all calls to Apply by
|
|
||||||
the same node, so that node would have successors F and G. A
|
|
||||||
context-sensitive call graph might represent the first and second
|
|
||||||
calls to Apply by distinct nodes, so that the first would have
|
|
||||||
successor F and the second would have successor G. This is a more
|
|
||||||
precise representation of the possible behaviors of the program.
|
|
||||||
|
|
||||||
A SOUND call graph is one that overapproximates the dynamic calling
|
A SOUND call graph is one that overapproximates the dynamic calling
|
||||||
behaviors of the program in all possible executions. One call graph
|
behaviors of the program in all possible executions. One call graph
|
||||||
is more PRECISE than another if it is a smaller overapproximation of
|
is more PRECISE than another if it is a smaller overapproximation of
|
||||||
|
@ -58,6 +34,11 @@ language.
|
||||||
*/
|
*/
|
||||||
package callgraph
|
package callgraph
|
||||||
|
|
||||||
|
// TODO(adonovan): add a function to eliminate wrappers from the
|
||||||
|
// callgraph, preserving topology.
|
||||||
|
// More generally, we could eliminate "uninteresting" nodes such as
|
||||||
|
// nodes from packages we don't care about.
|
||||||
|
|
||||||
import "code.google.com/p/go.tools/go/ssa"
|
import "code.google.com/p/go.tools/go/ssa"
|
||||||
|
|
||||||
// A Graph represents a call graph.
|
// A Graph represents a call graph.
|
||||||
|
@ -66,43 +47,49 @@ import "code.google.com/p/go.tools/go/ssa"
|
||||||
// If the call graph is sound, such nodes indicate unreachable
|
// If the call graph is sound, such nodes indicate unreachable
|
||||||
// functions.
|
// functions.
|
||||||
//
|
//
|
||||||
type Graph interface {
|
type Graph struct {
|
||||||
Root() Node // the distinguished root node
|
Root *Node // the distinguished root node
|
||||||
Nodes() []Node // new unordered set of all nodes
|
Nodes map[*ssa.Function]*Node // all nodes by function
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Graph with the specified root node.
|
||||||
|
func New(root *ssa.Function) *Graph {
|
||||||
|
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
|
||||||
|
g.Root = g.CreateNode(root)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNode returns the Node for fn, creating it if not present.
|
||||||
|
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
|
||||||
|
n, ok := g.Nodes[fn]
|
||||||
|
if !ok {
|
||||||
|
n = &Node{Func: fn, ID: len(g.Nodes)}
|
||||||
|
g.Nodes[fn] = n
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Node represents a node in a call graph.
|
// A Node represents a node in a call graph.
|
||||||
//
|
type Node struct {
|
||||||
// If the call graph is context sensitive, there may be multiple
|
Func *ssa.Function // the function this node represents
|
||||||
// Nodes with the same Func(); the identity of the graph node
|
ID int // 0-based sequence number
|
||||||
// indicates the context.
|
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
|
||||||
//
|
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||||
// Sites returns the set of syntactic call sites within this function.
|
|
||||||
//
|
|
||||||
// For nodes representing synthetic or intrinsic functions
|
|
||||||
// (e.g. reflect.Call, or the root of the call graph), Sites() returns
|
|
||||||
// a slice containing a single nil value to indicate the synthetic
|
|
||||||
// call site, and each edge in Edges() has a nil Site.
|
|
||||||
//
|
|
||||||
// All nodes "belong" to a single graph and must not be mixed with
|
|
||||||
// nodes belonging to another graph.
|
|
||||||
//
|
|
||||||
// A site may appear in Sites() but not in {e.Site | e ∈ Edges()}.
|
|
||||||
// This indicates that that caller node was unreachable, or that the
|
|
||||||
// call was dynamic yet no func or interface values flow to the call
|
|
||||||
// site.
|
|
||||||
//
|
|
||||||
// Clients should not mutate the node via the results of its methods.
|
|
||||||
//
|
|
||||||
type Node interface {
|
|
||||||
Func() *ssa.Function // the function this node represents
|
|
||||||
Sites() []ssa.CallInstruction // new unordered set of call sites within this function
|
|
||||||
Edges() []Edge // new unordered set of outgoing edges
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Edge represents an edge in the call graph.
|
// A Edge represents an edge in the call graph.
|
||||||
|
//
|
||||||
|
// Site is nil for edges originating in synthetic or intrinsic
|
||||||
|
// functions, e.g. reflect.Call or the root of the call graph.
|
||||||
type Edge struct {
|
type Edge struct {
|
||||||
Caller Node
|
Caller *Node
|
||||||
Site ssa.CallInstruction
|
Site ssa.CallInstruction
|
||||||
Callee Node
|
Callee *Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEdge adds the edge (caller, site, callee) to the call graph.
|
||||||
|
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
|
||||||
|
e := &Edge{caller, site, callee}
|
||||||
|
callee.In = append(callee.In, e)
|
||||||
|
caller.Out = append(caller.Out, e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,15 @@
|
||||||
|
|
||||||
package callgraph
|
package callgraph
|
||||||
|
|
||||||
// This file provides various representation-independent utilities
|
// This file provides various utilities over call graphs, such as
|
||||||
// over call graphs, such as visitation and path search.
|
// visitation and path search.
|
||||||
//
|
|
||||||
// TODO(adonovan):
|
|
||||||
//
|
|
||||||
// Consider adding lookup functions such as:
|
|
||||||
// FindSitesByPos(g Graph, lparen token.Pos) []Site
|
|
||||||
// FindSitesByCallExpr(g Graph, expr *ast.CallExpr) []Site
|
|
||||||
// FindSitesByInstr(g Graph, instr ssa.CallInstruction) []Site
|
|
||||||
// FindNodesByFunc(g Graph, fn *ssa.Function) []Node
|
|
||||||
// (Counterargument: they're all inefficient linear scans; if the
|
|
||||||
// caller does it explicitly there may be opportunities to optimize.
|
|
||||||
//
|
|
||||||
// Add a utility function to eliminate all context from a call graph.
|
|
||||||
|
|
||||||
// CalleesOf returns a new set containing all direct callees of the
|
// CalleesOf returns a new set containing all direct callees of the
|
||||||
// caller node.
|
// caller node.
|
||||||
//
|
//
|
||||||
func CalleesOf(caller Node) map[Node]bool {
|
func CalleesOf(caller *Node) map[*Node]bool {
|
||||||
callees := make(map[Node]bool)
|
callees := make(map[*Node]bool)
|
||||||
for _, e := range caller.Edges() {
|
for _, e := range caller.Out {
|
||||||
callees[e.Callee] = true
|
callees[e.Callee] = true
|
||||||
}
|
}
|
||||||
return callees
|
return callees
|
||||||
|
@ -35,13 +23,13 @@ func CalleesOf(caller Node) map[Node]bool {
|
||||||
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
||||||
// value.
|
// value.
|
||||||
//
|
//
|
||||||
func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
|
||||||
seen := make(map[Node]bool)
|
seen := make(map[*Node]bool)
|
||||||
var visit func(n Node) error
|
var visit func(n *Node) error
|
||||||
visit = func(n Node) error {
|
visit = func(n *Node) error {
|
||||||
if !seen[n] {
|
if !seen[n] {
|
||||||
seen[n] = true
|
seen[n] = true
|
||||||
for _, e := range n.Edges() {
|
for _, e := range n.Out {
|
||||||
if err := visit(e.Callee); err != nil {
|
if err := visit(e.Callee); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -52,7 +40,7 @@ func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, n := range g.Nodes() {
|
for _, n := range g.Nodes {
|
||||||
if err := visit(n); err != nil {
|
if err := visit(n); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -65,17 +53,17 @@ func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
||||||
// PathSearch returns the path as an ordered list of edges; on
|
// PathSearch returns the path as an ordered list of edges; on
|
||||||
// failure, it returns nil.
|
// failure, it returns nil.
|
||||||
//
|
//
|
||||||
func PathSearch(start Node, isEnd func(Node) bool) []Edge {
|
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||||
stack := make([]Edge, 0, 32)
|
stack := make([]*Edge, 0, 32)
|
||||||
seen := make(map[Node]bool)
|
seen := make(map[*Node]bool)
|
||||||
var search func(n Node) []Edge
|
var search func(n *Node) []*Edge
|
||||||
search = func(n Node) []Edge {
|
search = func(n *Node) []*Edge {
|
||||||
if !seen[n] {
|
if !seen[n] {
|
||||||
seen[n] = true
|
seen[n] = true
|
||||||
if isEnd(n) {
|
if isEnd(n) {
|
||||||
return stack
|
return stack
|
||||||
}
|
}
|
||||||
for _, e := range n.Edges() {
|
for _, e := range n.Out {
|
||||||
stack = append(stack, e) // push
|
stack = append(stack, e) // push
|
||||||
if found := search(e.Callee); found != nil {
|
if found := search(e.Callee); found != nil {
|
||||||
return found
|
return found
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/callgraph"
|
||||||
"code.google.com/p/go.tools/go/ssa"
|
"code.google.com/p/go.tools/go/ssa"
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
"code.google.com/p/go.tools/go/types/typeutil"
|
"code.google.com/p/go.tools/go/types/typeutil"
|
||||||
|
@ -346,7 +347,7 @@ func Analyze(config *Config) *Result {
|
||||||
}
|
}
|
||||||
a.computeTrackBits()
|
a.computeTrackBits()
|
||||||
|
|
||||||
root := a.generate()
|
a.generate()
|
||||||
|
|
||||||
if a.log != nil {
|
if a.log != nil {
|
||||||
// Show size of constraint system.
|
// Show size of constraint system.
|
||||||
|
@ -374,34 +375,37 @@ func Analyze(config *Config) *Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create callgraph.Nodes in deterministic order.
|
||||||
|
if cg := a.result.CallGraph; cg != nil {
|
||||||
|
for _, caller := range a.cgnodes {
|
||||||
|
cg.CreateNode(caller.fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add dynamic edges to call graph.
|
// Add dynamic edges to call graph.
|
||||||
for _, caller := range a.cgnodes {
|
for _, caller := range a.cgnodes {
|
||||||
for _, site := range caller.sites {
|
for _, site := range caller.sites {
|
||||||
for callee := range a.nodes[site.targets].pts {
|
for callee := range a.nodes[site.targets].pts {
|
||||||
a.callEdge(site, callee)
|
a.callEdge(caller, site, callee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.config.BuildCallGraph {
|
|
||||||
a.result.CallGraph = &cgraph{root, a.cgnodes}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.result
|
return a.result
|
||||||
}
|
}
|
||||||
|
|
||||||
// callEdge is called for each edge in the callgraph.
|
// callEdge is called for each edge in the callgraph.
|
||||||
// calleeid is the callee's object node (has otFunction flag).
|
// calleeid is the callee's object node (has otFunction flag).
|
||||||
//
|
//
|
||||||
func (a *analysis) callEdge(site *callsite, calleeid nodeid) {
|
func (a *analysis) callEdge(caller *cgnode, site *callsite, calleeid nodeid) {
|
||||||
obj := a.nodes[calleeid].obj
|
obj := a.nodes[calleeid].obj
|
||||||
if obj.flags&otFunction == 0 {
|
if obj.flags&otFunction == 0 {
|
||||||
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
||||||
}
|
}
|
||||||
callee := obj.cgn
|
callee := obj.cgn
|
||||||
|
|
||||||
if a.config.BuildCallGraph {
|
if cg := a.result.CallGraph; cg != nil {
|
||||||
site.callees = append(site.callees, callee)
|
callgraph.AddEdge(cg.CreateNode(caller.fn), site.instr, cg.CreateNode(callee.fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.log != nil {
|
if a.log != nil {
|
||||||
|
|
|
@ -115,7 +115,7 @@ type Warning struct {
|
||||||
// See Config for how to request the various Result components.
|
// See Config for how to request the various Result components.
|
||||||
//
|
//
|
||||||
type Result struct {
|
type Result struct {
|
||||||
CallGraph callgraph.Graph // discovered call graph
|
CallGraph *callgraph.Graph // discovered call graph
|
||||||
Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
|
Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
|
||||||
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||||
Warnings []Warning // warnings of unsoundness
|
Warnings []Warning // warnings of unsoundness
|
||||||
|
|
|
@ -4,35 +4,15 @@
|
||||||
|
|
||||||
package pointer
|
package pointer
|
||||||
|
|
||||||
// This file defines our implementation of the callgraph API.
|
// This file defines the internal (context-sensitive) call graph.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/go/callgraph"
|
|
||||||
"code.google.com/p/go.tools/go/ssa"
|
"code.google.com/p/go.tools/go/ssa"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cgraph implements callgraph.Graph.
|
|
||||||
type cgraph struct {
|
|
||||||
root *cgnode
|
|
||||||
nodes []*cgnode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *cgraph) Nodes() []callgraph.Node {
|
|
||||||
nodes := make([]callgraph.Node, len(g.nodes))
|
|
||||||
for i, node := range g.nodes {
|
|
||||||
nodes[i] = node
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *cgraph) Root() callgraph.Node {
|
|
||||||
return g.root
|
|
||||||
}
|
|
||||||
|
|
||||||
// cgnode implements callgraph.Node.
|
|
||||||
type cgnode struct {
|
type cgnode struct {
|
||||||
fn *ssa.Function
|
fn *ssa.Function
|
||||||
obj nodeid // start of this contour's object block
|
obj nodeid // start of this contour's object block
|
||||||
|
@ -40,33 +20,6 @@ type cgnode struct {
|
||||||
callersite *callsite // where called from, if known; nil for shared contours
|
callersite *callsite // where called from, if known; nil for shared contours
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *cgnode) Func() *ssa.Function {
|
|
||||||
return n.fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *cgnode) Sites() []ssa.CallInstruction {
|
|
||||||
sites := make([]ssa.CallInstruction, len(n.sites))
|
|
||||||
for i, site := range n.sites {
|
|
||||||
sites[i] = site.instr
|
|
||||||
}
|
|
||||||
return sites
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *cgnode) Edges() []callgraph.Edge {
|
|
||||||
var numEdges int
|
|
||||||
for _, site := range n.sites {
|
|
||||||
numEdges += len(site.callees)
|
|
||||||
}
|
|
||||||
edges := make([]callgraph.Edge, 0, numEdges)
|
|
||||||
|
|
||||||
for _, site := range n.sites {
|
|
||||||
for _, callee := range site.callees {
|
|
||||||
edges = append(edges, callgraph.Edge{Caller: n, Site: site.instr, Callee: callee})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return edges
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *cgnode) String() string {
|
func (n *cgnode) String() string {
|
||||||
return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
|
return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +32,6 @@ func (n *cgnode) String() string {
|
||||||
type callsite struct {
|
type callsite struct {
|
||||||
targets nodeid // pts(·) contains objects for dynamically called functions
|
targets nodeid // pts(·) contains objects for dynamically called functions
|
||||||
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
|
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
|
||||||
callees []*cgnode // unordered set of callees of this site
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callsite) String() string {
|
func (c *callsite) String() string {
|
||||||
|
|
|
@ -85,10 +85,10 @@ func main() {
|
||||||
// By converting to strings, we de-duplicate nodes
|
// By converting to strings, we de-duplicate nodes
|
||||||
// representing the same function due to context sensitivity.
|
// representing the same function due to context sensitivity.
|
||||||
var edges []string
|
var edges []string
|
||||||
callgraph.GraphVisitEdges(result.CallGraph, func(edge callgraph.Edge) error {
|
callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error {
|
||||||
caller := edge.Caller.Func()
|
caller := edge.Caller.Func
|
||||||
if caller.Pkg == mainPkg {
|
if caller.Pkg == mainPkg {
|
||||||
edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func()))
|
edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
|
"code.google.com/p/go.tools/go/callgraph"
|
||||||
"code.google.com/p/go.tools/go/ssa"
|
"code.google.com/p/go.tools/go/ssa"
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
)
|
)
|
||||||
|
@ -620,7 +621,7 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC
|
||||||
} else {
|
} else {
|
||||||
obj = a.objectNode(nil, fn) // shared contour
|
obj = a.objectNode(nil, fn) // shared contour
|
||||||
}
|
}
|
||||||
a.callEdge(site, obj)
|
a.callEdge(caller, site, obj)
|
||||||
|
|
||||||
sig := call.Signature()
|
sig := call.Signature()
|
||||||
|
|
||||||
|
@ -726,7 +727,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
||||||
fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name())
|
fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name())
|
||||||
|
|
||||||
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
||||||
a.callEdge(site, obj)
|
a.callEdge(caller, site, obj)
|
||||||
|
|
||||||
// From now on, it's essentially a static call, but little is
|
// From now on, it's essentially a static call, but little is
|
||||||
// gained by factoring together the code for both cases.
|
// gained by factoring together the code for both cases.
|
||||||
|
@ -1213,9 +1214,7 @@ func (a *analysis) genMethodsOf(T types.Type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate generates offline constraints for the entire program.
|
// generate generates offline constraints for the entire program.
|
||||||
// It returns the synthetic root of the callgraph.
|
func (a *analysis) generate() {
|
||||||
//
|
|
||||||
func (a *analysis) generate() *cgnode {
|
|
||||||
// Create a dummy node since we use the nodeid 0 for
|
// Create a dummy node since we use the nodeid 0 for
|
||||||
// non-pointerlike variables.
|
// non-pointerlike variables.
|
||||||
a.addNodes(tInvalid, "(zero)")
|
a.addNodes(tInvalid, "(zero)")
|
||||||
|
@ -1232,6 +1231,10 @@ func (a *analysis) generate() *cgnode {
|
||||||
|
|
||||||
root := a.genRootCalls()
|
root := a.genRootCalls()
|
||||||
|
|
||||||
|
if a.config.BuildCallGraph {
|
||||||
|
a.result.CallGraph = callgraph.New(root.fn)
|
||||||
|
}
|
||||||
|
|
||||||
// Create nodes and constraints for all methods of all types
|
// Create nodes and constraints for all methods of all types
|
||||||
// that are dynamically accessible via reflection or interfaces.
|
// that are dynamically accessible via reflection or interfaces.
|
||||||
for _, T := range a.prog.TypesWithMethodSets() {
|
for _, T := range a.prog.TypesWithMethodSets() {
|
||||||
|
@ -1253,6 +1256,4 @@ func (a *analysis) generate() *cgnode {
|
||||||
a.endObject(obj, nil, "<command-line args>")
|
a.endObject(obj, nil, "<command-line args>")
|
||||||
a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj)
|
a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
return root
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,7 +355,7 @@ func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
||||||
// TODO(adonovan): enable this warning when we've implemented
|
// TODO(adonovan): enable this warning when we've implemented
|
||||||
// enough that it's not unbearably annoying.
|
// enough that it's not unbearably annoying.
|
||||||
if true {
|
if true {
|
||||||
fn := cgn.Func()
|
fn := cgn.fn
|
||||||
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/go/callgraph"
|
|
||||||
"code.google.com/p/go.tools/go/ssa"
|
"code.google.com/p/go.tools/go/ssa"
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
)
|
)
|
||||||
|
@ -54,13 +53,6 @@ func (l Label) ReflectType() types.Type {
|
||||||
return rtype
|
return rtype
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context returns the analytic context in which this label's object was allocated,
|
|
||||||
// or nil for global objects: global, const, and shared contours for functions.
|
|
||||||
//
|
|
||||||
func (l Label) Context() callgraph.Node {
|
|
||||||
return l.obj.cgn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the path to the subelement of the object containing
|
// Path returns the path to the subelement of the object containing
|
||||||
// this label. For example, ".x[*].y".
|
// this label. For example, ".x[*].y".
|
||||||
//
|
//
|
||||||
|
@ -79,7 +71,7 @@ func (l Label) Pos() token.Pos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cgn := l.obj.cgn; cgn != nil {
|
if cgn := l.obj.cgn; cgn != nil {
|
||||||
return cgn.Func().Pos()
|
return cgn.fn.Pos()
|
||||||
}
|
}
|
||||||
return token.NoPos
|
return token.NoPos
|
||||||
}
|
}
|
||||||
|
@ -115,7 +107,7 @@ func (l Label) String() string {
|
||||||
case nil:
|
case nil:
|
||||||
if l.obj.cgn != nil {
|
if l.obj.cgn != nil {
|
||||||
// allocation by intrinsic or reflective operation
|
// allocation by intrinsic or reflective operation
|
||||||
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.Func())
|
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.fn)
|
||||||
} else {
|
} else {
|
||||||
s = "<unknown>" // should be unreachable
|
s = "<unknown>" // should be unreachable
|
||||||
}
|
}
|
||||||
|
|
|
@ -472,14 +472,14 @@ func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Ty
|
||||||
|
|
||||||
var errOK = errors.New("OK")
|
var errOK = errors.New("OK")
|
||||||
|
|
||||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, cg callgraph.Graph) bool {
|
func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool {
|
||||||
found := make(map[string]int)
|
found := make(map[string]int)
|
||||||
err := callgraph.GraphVisitEdges(cg, func(edge callgraph.Edge) error {
|
err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
|
||||||
// Name-based matching is inefficient but it allows us to
|
// Name-based matching is inefficient but it allows us to
|
||||||
// match functions whose names that would not appear in an
|
// match functions whose names that would not appear in an
|
||||||
// index ("<root>") or which are not unique ("func@1.2").
|
// index ("<root>") or which are not unique ("func@1.2").
|
||||||
if edge.Caller.Func().String() == e.args[0] {
|
if edge.Caller.Func.String() == e.args[0] {
|
||||||
calleeStr := edge.Callee.Func().String()
|
calleeStr := edge.Callee.Func.String()
|
||||||
if calleeStr == e.args[1] {
|
if calleeStr == e.args[1] {
|
||||||
return errOK // expectation satisified; stop the search
|
return errOK // expectation satisified; stop the search
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,26 +103,21 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||||
|
|
||||||
// Dynamic call: use pointer analysis.
|
// Dynamic call: use pointer analysis.
|
||||||
o.ptaConfig.BuildCallGraph = true
|
o.ptaConfig.BuildCallGraph = true
|
||||||
callgraph := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(o).CallGraph
|
||||||
|
|
||||||
// Find all call edges from the site.
|
// Find all call edges from the site.
|
||||||
calleesMap := make(map[*ssa.Function]bool)
|
n := cg.Nodes[site.Parent()]
|
||||||
var foundCGNode bool
|
if n == nil {
|
||||||
for _, n := range callgraph.Nodes() {
|
|
||||||
if n.Func() == site.Parent() {
|
|
||||||
foundCGNode = true
|
|
||||||
for _, edge := range n.Edges() {
|
|
||||||
if edge.Site == site {
|
|
||||||
calleesMap[edge.Callee.Func()] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundCGNode {
|
|
||||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||||
}
|
}
|
||||||
|
calleesMap := make(map[*ssa.Function]bool)
|
||||||
|
for _, edge := range n.Out {
|
||||||
|
if edge.Site == site {
|
||||||
|
calleesMap[edge.Callee.Func] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Discard context, de-duplicate and sort.
|
// De-duplicate and sort.
|
||||||
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
||||||
for f := range calleesMap {
|
for f := range calleesMap {
|
||||||
funcs = append(funcs, f)
|
funcs = append(funcs, f)
|
||||||
|
|
|
@ -38,13 +38,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
// call found to originate from target.
|
// call found to originate from target.
|
||||||
o.ptaConfig.BuildCallGraph = true
|
o.ptaConfig.BuildCallGraph = true
|
||||||
cg := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(o).CallGraph
|
||||||
var edges []callgraph.Edge
|
edges := cg.CreateNode(target).In
|
||||||
callgraph.GraphVisitEdges(cg, func(edge callgraph.Edge) error {
|
|
||||||
if edge.Callee.Func() == target {
|
|
||||||
edges = append(edges, edge)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||||
|
|
||||||
return &callersResult{
|
return &callersResult{
|
||||||
|
@ -56,12 +50,12 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
|
|
||||||
type callersResult struct {
|
type callersResult struct {
|
||||||
target *ssa.Function
|
target *ssa.Function
|
||||||
callgraph callgraph.Graph
|
callgraph *callgraph.Graph
|
||||||
edges []callgraph.Edge
|
edges []*callgraph.Edge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *callersResult) display(printf printfFunc) {
|
func (r *callersResult) display(printf printfFunc) {
|
||||||
root := r.callgraph.Root()
|
root := r.callgraph.Root
|
||||||
if r.edges == nil {
|
if r.edges == nil {
|
||||||
printf(r.target, "%s is not reachable in this program.", r.target)
|
printf(r.target, "%s is not reachable in this program.", r.target)
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,18 +64,18 @@ func (r *callersResult) display(printf printfFunc) {
|
||||||
if edge.Caller == root {
|
if edge.Caller == root {
|
||||||
printf(r.target, "the root of the call graph")
|
printf(r.target, "the root of the call graph")
|
||||||
} else {
|
} else {
|
||||||
printf(edge.Site, "\t%s from %s", edge.Site.Common().Description(), edge.Caller.Func())
|
printf(edge.Site, "\t%s from %s", edge.Site.Common().Description(), edge.Caller.Func)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
root := r.callgraph.Root()
|
root := r.callgraph.Root
|
||||||
var callers []serial.Caller
|
var callers []serial.Caller
|
||||||
for _, edge := range r.edges {
|
for _, edge := range r.edges {
|
||||||
var c serial.Caller
|
var c serial.Caller
|
||||||
c.Caller = edge.Caller.Func().String()
|
c.Caller = edge.Caller.Func.String()
|
||||||
if edge.Caller == root {
|
if edge.Caller == root {
|
||||||
c.Desc = "synthetic call"
|
c.Desc = "synthetic call"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,15 +15,9 @@ import (
|
||||||
|
|
||||||
// doCallgraph displays the entire callgraph of the current program.
|
// doCallgraph displays the entire callgraph of the current program.
|
||||||
//
|
//
|
||||||
// Nodes may be seem to appear multiple times due to (limited)
|
|
||||||
// context sensitivity.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): add options for restricting the display to a region
|
// TODO(adonovan): add options for restricting the display to a region
|
||||||
// of interest: function, package, subgraph, dirtree, goroutine, etc.
|
// of interest: function, package, subgraph, dirtree, goroutine, etc.
|
||||||
//
|
//
|
||||||
// TODO(adonovan): add an option to project away context sensitivity.
|
|
||||||
// The callgraph API should provide this feature.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): add an option to partition edges by call site.
|
// TODO(adonovan): add an option to partition edges by call site.
|
||||||
//
|
//
|
||||||
// TODO(adonovan): elide nodes for synthetic functions?
|
// TODO(adonovan): elide nodes for synthetic functions?
|
||||||
|
@ -41,7 +35,7 @@ func doCallgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type callgraphResult struct {
|
type callgraphResult struct {
|
||||||
callgraph callgraph.Graph
|
callgraph *callgraph.Graph
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *callgraphResult) display(printf printfFunc) {
|
func (r *callgraphResult) display(printf printfFunc) {
|
||||||
|
@ -51,36 +45,10 @@ The numbered nodes form a spanning tree.
|
||||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||||
number follows in parentheses.
|
number follows in parentheses.
|
||||||
`)
|
`)
|
||||||
root := r.callgraph.Root()
|
|
||||||
|
|
||||||
// context-insensitive (CI) call graph.
|
printed := make(map[*callgraph.Node]int)
|
||||||
ci := make(map[*ssa.Function]map[*ssa.Function]bool)
|
var print func(caller *callgraph.Node, indent int)
|
||||||
|
print = func(caller *callgraph.Node, indent int) {
|
||||||
// 1. Visit the CS call graph and build the CI call graph.
|
|
||||||
visited := make(map[callgraph.Node]bool)
|
|
||||||
var visit func(caller callgraph.Node)
|
|
||||||
visit = func(caller callgraph.Node) {
|
|
||||||
if !visited[caller] {
|
|
||||||
visited[caller] = true
|
|
||||||
|
|
||||||
cicallees := ci[caller.Func()]
|
|
||||||
if cicallees == nil {
|
|
||||||
cicallees = make(map[*ssa.Function]bool)
|
|
||||||
ci[caller.Func()] = cicallees
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range caller.Edges() {
|
|
||||||
cicallees[e.Callee.Func()] = true
|
|
||||||
visit(e.Callee)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visit(root)
|
|
||||||
|
|
||||||
// 2. Print the CI callgraph.
|
|
||||||
printed := make(map[*ssa.Function]int)
|
|
||||||
var print func(caller *ssa.Function, indent int)
|
|
||||||
print = func(caller *ssa.Function, indent int) {
|
|
||||||
if num, ok := printed[caller]; !ok {
|
if num, ok := printed[caller]; !ok {
|
||||||
num = len(printed)
|
num = len(printed)
|
||||||
printed[caller] = num
|
printed[caller] = num
|
||||||
|
@ -88,20 +56,20 @@ Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||||
// Sort the children into name order for deterministic* output.
|
// Sort the children into name order for deterministic* output.
|
||||||
// (*mostly: anon funcs' names are not globally unique.)
|
// (*mostly: anon funcs' names are not globally unique.)
|
||||||
var funcs funcsByName
|
var funcs funcsByName
|
||||||
for callee := range ci[caller] {
|
for callee := range callgraph.CalleesOf(caller) {
|
||||||
funcs = append(funcs, callee)
|
funcs = append(funcs, callee.Func)
|
||||||
}
|
}
|
||||||
sort.Sort(funcs)
|
sort.Sort(funcs)
|
||||||
|
|
||||||
printf(caller, "%d\t%*s%s", num, 4*indent, "", caller)
|
printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func)
|
||||||
for _, callee := range funcs {
|
for _, callee := range funcs {
|
||||||
print(callee, indent+1)
|
print(r.callgraph.Nodes[callee], indent+1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf(caller, "\t%*s%s (%d)", 4*indent, "", caller, num)
|
printf(caller, "\t%*s%s (%d)", 4*indent, "", caller.Func, num)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print(root.Func(), 0)
|
print(r.callgraph.Root, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type funcsByName []*ssa.Function
|
type funcsByName []*ssa.Function
|
||||||
|
@ -111,21 +79,14 @@ func (s funcsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }
|
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }
|
||||||
|
|
||||||
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
nodes := r.callgraph.Nodes()
|
cg := make([]serial.CallGraph, len(r.callgraph.Nodes))
|
||||||
|
for _, n := range r.callgraph.Nodes {
|
||||||
numbering := make(map[callgraph.Node]int)
|
j := &cg[n.ID]
|
||||||
for i, n := range nodes {
|
fn := n.Func
|
||||||
numbering[n] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
cg := make([]serial.CallGraph, len(nodes))
|
|
||||||
for i, n := range nodes {
|
|
||||||
j := &cg[i]
|
|
||||||
fn := n.Func()
|
|
||||||
j.Name = fn.String()
|
j.Name = fn.String()
|
||||||
j.Pos = fset.Position(fn.Pos()).String()
|
j.Pos = fset.Position(fn.Pos()).String()
|
||||||
for callee := range callgraph.CalleesOf(n) {
|
for callee := range callgraph.CalleesOf(n) {
|
||||||
j.Children = append(j.Children, numbering[callee])
|
j.Children = append(j.Children, callee.ID)
|
||||||
}
|
}
|
||||||
sort.Ints(j.Children)
|
sort.Ints(j.Children)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,8 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
cg := ptrAnalysis(o).CallGraph
|
cg := ptrAnalysis(o).CallGraph
|
||||||
|
|
||||||
// Search for an arbitrary path from a root to the target function.
|
// Search for an arbitrary path from a root to the target function.
|
||||||
isEnd := func(n callgraph.Node) bool { return n.Func() == target }
|
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
|
||||||
callpath := callgraph.PathSearch(cg.Root(), isEnd)
|
callpath := callgraph.PathSearch(cg.Root, isEnd)
|
||||||
if callpath != nil {
|
if callpath != nil {
|
||||||
callpath = callpath[1:] // remove synthetic edge from <root>
|
callpath = callpath[1:] // remove synthetic edge from <root>
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||||
type callstackResult struct {
|
type callstackResult struct {
|
||||||
qpos *QueryPos
|
qpos *QueryPos
|
||||||
target *ssa.Function
|
target *ssa.Function
|
||||||
callpath []callgraph.Edge
|
callpath []*callgraph.Edge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *callstackResult) display(printf printfFunc) {
|
func (r *callstackResult) display(printf printfFunc) {
|
||||||
|
@ -70,7 +70,7 @@ func (r *callstackResult) display(printf printfFunc) {
|
||||||
printf(r.target, "%s", r.target)
|
printf(r.target, "%s", r.target)
|
||||||
for i := len(r.callpath) - 1; i >= 0; i-- {
|
for i := len(r.callpath) - 1; i >= 0; i-- {
|
||||||
edge := r.callpath[i]
|
edge := r.callpath[i]
|
||||||
printf(edge.Site, "%s from %s", edge.Site.Common().Description(), edge.Caller.Func())
|
printf(edge.Site, "%s from %s", edge.Site.Common().Description(), edge.Caller.Func)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
||||||
|
@ -83,7 +83,7 @@ func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||||
edge := r.callpath[i]
|
edge := r.callpath[i]
|
||||||
callers = append(callers, serial.Caller{
|
callers = append(callers, serial.Caller{
|
||||||
Pos: fset.Position(edge.Site.Pos()).String(),
|
Pos: fset.Position(edge.Site.Pos()).String(),
|
||||||
Caller: edge.Caller.Func().String(),
|
Caller: edge.Caller.Func.String(),
|
||||||
Desc: edge.Site.Common().Description(),
|
Desc: edge.Site.Common().Description(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,7 +259,7 @@ func TestMultipleQueries(t *testing.T) {
|
||||||
// Loader
|
// Loader
|
||||||
var buildContext = build.Default
|
var buildContext = build.Default
|
||||||
buildContext.GOPATH = "testdata"
|
buildContext.GOPATH = "testdata"
|
||||||
conf := loader.Config{Build: &buildContext}
|
conf := loader.Config{Build: &buildContext, SourceImports: true}
|
||||||
filename := "testdata/src/main/multi.go"
|
filename := "testdata/src/main/multi.go"
|
||||||
conf.CreateFromFilenames("", filename)
|
conf.CreateFromFilenames("", filename)
|
||||||
iprog, err := conf.Load()
|
iprog, err := conf.Load()
|
||||||
|
|
|
@ -7,31 +7,41 @@
|
||||||
"pos": "-",
|
"pos": "-",
|
||||||
"children": [
|
"children": [
|
||||||
1,
|
1,
|
||||||
2
|
5
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "main.init",
|
|
||||||
"pos": "-"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "main.main",
|
"name": "main.main",
|
||||||
"pos": "testdata/src/main/callgraph-json.go:24:6",
|
"pos": "testdata/src/main/callgraph-json.go:24:6",
|
||||||
"children": [
|
"children": [
|
||||||
|
2,
|
||||||
3,
|
3,
|
||||||
6,
|
4
|
||||||
7,
|
|
||||||
8
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "main.call",
|
"name": "main.call",
|
||||||
"pos": "testdata/src/main/callgraph-json.go:12:6",
|
"pos": "testdata/src/main/callgraph-json.go:12:6",
|
||||||
"children": [
|
"children": [
|
||||||
4,
|
6,
|
||||||
5
|
7
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "main.nop",
|
||||||
|
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main.call2",
|
||||||
|
"pos": "testdata/src/main/callgraph-json.go:19:6",
|
||||||
|
"children": [
|
||||||
|
8
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main.init",
|
||||||
|
"pos": "-"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "main.A",
|
"name": "main.A",
|
||||||
"pos": "testdata/src/main/callgraph-json.go:7:6"
|
"pos": "testdata/src/main/callgraph-json.go:7:6"
|
||||||
|
@ -40,21 +50,6 @@
|
||||||
"name": "main.B",
|
"name": "main.B",
|
||||||
"pos": "testdata/src/main/callgraph-json.go:9:6"
|
"pos": "testdata/src/main/callgraph-json.go:9:6"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "main.nop",
|
|
||||||
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "main.nop",
|
|
||||||
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "main.call2",
|
|
||||||
"pos": "testdata/src/main/callgraph-json.go:19:6",
|
|
||||||
"children": [
|
|
||||||
9
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "func@31.8",
|
"name": "func@31.8",
|
||||||
"pos": "testdata/src/main/callgraph-json.go:31:8"
|
"pos": "testdata/src/main/callgraph-json.go:31:8"
|
||||||
|
|
Loading…
Reference in New Issue