Go-Callvis Viewer

Home|gocallvis/output.go
1package main
2
3import (
4    "bytes"
5    "fmt"
6    "go/build"
7    "go/types"
8    "path/filepath"
9    "strings"
10
11    "golang.org/x/tools/go/callgraph"
12    "golang.org/x/tools/go/ssa"
13)
14
15func isSynthetic(edge *callgraph.Edgebool {
16    return edge.Caller.Func.Pkg == nil || edge.Callee.Func.Synthetic != ""
17}
18
19func inStd(node *callgraph.Nodebool {
20    pkg_ := build.Import(node.Func.Pkg.Pkg.Path(), ""0)
21    return pkg.Goroot
22}
23
24func printOutput(
25    prog *ssa.Program,
26    mainPkg *ssa.Package,
27    cg *callgraph.Graph,
28    focusPkg *types.Package,
29    limitPaths,
30    ignorePaths,
31    includePaths []string,
32    groupBy []string,
33    nostd,
34    nointer bool,
35) ([]byteerror) {
36    var groupTypegroupPkg bool
37    for _g := range groupBy {
38        switch g {
39        case "pkg":
40            groupPkg = true
41        case "type":
42            groupType = true
43        }
44    }
45
46    cluster := NewDotCluster("focus")
47    cluster.Attrs = dotAttrs{
48        "bgcolor":   "white",
49        "label":     "",
50        "labelloc":  "t",
51        "labeljust""c",
52        "fontsize":  "18",
53    }
54    if focusPkg != nil {
55        cluster.Attrs["bgcolor"] = "#e6ecfa"
56        cluster.Attrs["label"] = focusPkg.Name()
57    }
58
59    var (
60        nodes []*dotNode
61        edges []*dotEdge
62    )
63
64    nodeMap := make(map[string]*dotNode)
65    edgeMap := make(map[string]*dotEdge)
66
67    cg.DeleteSyntheticNodes()
68
69    logf("%d limit prefixes: %v"len(limitPaths), limitPaths)
70    logf("%d ignore prefixes: %v"len(ignorePaths), ignorePaths)
71    logf("%d include prefixes: %v"len(includePaths), includePaths)
72    logf("no std packages: %v"nostd)
73
74    var isFocused = func(edge *callgraph.Edgebool {
75        caller := edge.Caller
76        callee := edge.Callee
77        if focusPkg != nil && (caller.Func.Pkg.Pkg.Path() == focusPkg.Path() || callee.Func.Pkg.Pkg.Path() == focusPkg.Path()) {
78            return true
79        }
80        fromFocused := false
81        toFocused := false
82        for _e := range caller.In {
83            if !isSynthetic(e) && focusPkg != nil &&
84                e.Caller.Func.Pkg.Pkg.Path() == focusPkg.Path() {
85                fromFocused = true
86                break
87            }
88        }
89        for _e := range callee.Out {
90            if !isSynthetic(e) && focusPkg != nil &&
91                e.Callee.Func.Pkg.Pkg.Path() == focusPkg.Path() {
92                toFocused = true
93                break
94            }
95        }
96        if fromFocused && toFocused {
97            logf("edge semi-focus: %s"edge)
98            return true
99        }
100        return false
101    }
102
103    var inIncludes = func(node *callgraph.Nodebool {
104        pkgPath := node.Func.Pkg.Pkg.Path()
105        for _p := range includePaths {
106            if strings.HasPrefix(pkgPathp) {
107                return true
108            }
109        }
110        return false
111    }
112
113    var inLimits = func(node *callgraph.Nodebool {
114        pkgPath := node.Func.Pkg.Pkg.Path()
115        for _p := range limitPaths {
116            if strings.HasPrefix(pkgPathp) {
117                return true
118            }
119        }
120        return false
121    }
122
123    var inIgnores = func(node *callgraph.Nodebool {
124        pkgPath := node.Func.Pkg.Pkg.Path()
125        for _p := range ignorePaths {
126            if strings.HasPrefix(pkgPathp) {
127                return true
128            }
129        }
130        return false
131    }
132
133    var isInter = func(edge *callgraph.Edgebool {
134        //caller := edge.Caller
135        callee := edge.Callee
136        if callee.Func.Object() != nil && !callee.Func.Object().Exported() {
137            return true
138        }
139        return false
140    }
141
142    count := 0
143    err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edgeerror {
144        count++
145
146        caller := edge.Caller
147        callee := edge.Callee
148
149        posCaller := prog.Fset.Position(caller.Func.Pos())
150        posCallee := prog.Fset.Position(callee.Func.Pos())
151        posEdge := prog.Fset.Position(edge.Pos())
152        //fileCaller := fmt.Sprintf("%s:%d", posCaller.Filename, posCaller.Line)
153        filenameCaller := filepath.Base(posCaller.Filename)
154
155        // omit synthetic calls
156        if isSynthetic(edge) {
157            return nil
158        }
159
160        callerPkg := caller.Func.Pkg.Pkg
161        calleePkg := callee.Func.Pkg.Pkg
162
163        // focus specific pkg
164        if focusPkg != nil &&
165            !isFocused(edge) {
166            return nil
167        }
168
169        // omit std
170        if nostd &&
171            (inStd(caller) || inStd(callee)) {
172            return nil
173        }
174
175        // omit inter
176        if nointer && isInter(edge) {
177            return nil
178        }
179
180        include := false
181        // include path prefixes
182        if len(includePaths) > 0 &&
183            (inIncludes(caller) || inIncludes(callee)) {
184            logf("include: %s -> %s"callercallee)
185            include = true
186        }
187
188        if !include {
189            // limit path prefixes
190            if len(limitPaths) > 0 &&
191                (!inLimits(caller) || !inLimits(callee)) {
192                logf("NOT in limit: %s -> %s"callercallee)
193                return nil
194            }
195
196            // ignore path prefixes
197            if len(ignorePaths) > 0 &&
198                (inIgnores(caller) || inIgnores(callee)) {
199                logf("IS ignored: %s -> %s"callercallee)
200                return nil
201            }
202        }
203
204        //var buf bytes.Buffer
205        //data, _ := json.MarshalIndent(caller.Func, "", " ")
206        //logf("call node: %s -> %s\n %v", caller, callee, string(data))
207        logf("call node: %s -> %s (%s -> %s) %v\n"caller.Func.Pkgcallee.Func.PkgcallercalleefilenameCaller)
208
209        var sprintNode = func(node *callgraph.NodeisCaller bool) *dotNode {
210            // only once
211            key := node.Func.String()
212            nodeTooltip := ""
213
214            fileCaller := fmt.Sprintf("%s:%d"filepath.Base(posCaller.Filename), posCaller.Line)
215            fileCallee := fmt.Sprintf("%s:%d"filepath.Base(posCallee.Filename), posCallee.Line)
216
217            if isCaller {
218                nodeTooltip = fmt.Sprintf("%s | defined in %s"node.Func.String(), fileCaller)
219            } else {
220                nodeTooltip = fmt.Sprintf("%s | defined in %s"node.Func.String(), fileCallee)
221            }
222
223            if nok := nodeMap[key]; ok {
224                return n
225            }
226
227            // is focused
228            isFocused := focusPkg != nil &&
229                node.Func.Pkg.Pkg.Path() == focusPkg.Path()
230            attrs := make(dotAttrs)
231
232            // node label
233            label := node.Func.RelString(node.Func.Pkg.Pkg)
234
235            // func signature
236            sign := node.Func.Signature
237            if node.Func.Parent() != nil {
238                sign = node.Func.Parent().Signature
239            }
240
241            // omit type from label
242            if groupType && sign.Recv() != nil {
243                parts := strings.Split(label".")
244                label = parts[len(parts)-1]
245            }
246
247            pkg_ := build.Import(node.Func.Pkg.Pkg.Path(), ""0)
248            // set node color
249            if isFocused {
250                attrs["fillcolor"] = "lightblue"
251            } else if pkg.Goroot {
252                attrs["fillcolor"] = "#adedad"
253            } else {
254                attrs["fillcolor"] = "moccasin"
255            }
256
257            // include pkg name
258            if !groupPkg && !isFocused {
259                label = fmt.Sprintf("%s\n%s"node.Func.Pkg.Pkg.Name(), label)
260            }
261
262            attrs["label"] = label
263
264            // func styles
265            if node.Func.Parent() != nil {
266                attrs["style"] = "dotted,filled"
267            } else if node.Func.Object() != nil && node.Func.Object().Exported() {
268                attrs["penwidth"] = "1.5"
269            } else {
270                attrs["penwidth"] = "0.5"
271            }
272
273            c := cluster
274
275            // group by pkg
276            if groupPkg && !isFocused {
277                label := node.Func.Pkg.Pkg.Name()
278                if pkg.Goroot {
279                    label = node.Func.Pkg.Pkg.Path()
280                }
281                key := node.Func.Pkg.Pkg.Path()
282                if _ok := c.Clusters[key]; !ok {
283                    c.Clusters[key] = &dotCluster{
284                        ID:       key,
285                        Clustersmake(map[string]*dotCluster),
286                        AttrsdotAttrs{
287                            "penwidth":  "0.8",
288                            "fontsize":  "16",
289                            "label":     label,
290                            "style":     "filled",
291                            "fillcolor""lightyellow",
292                            "URL":       fmt.Sprintf("/?f=%s"key),
293                            "fontname":  "Tahoma bold",
294                            "tooltip":   fmt.Sprintf("package: %s"key),
295                            "rank":      "sink",
296                        },
297                    }
298                    if pkg.Goroot {
299                        c.Clusters[key].Attrs["fillcolor"] = "#E0FFE1"
300                    }
301                }
302                c = c.Clusters[key]
303            }
304
305            // group by type
306            if groupType && sign.Recv() != nil {
307                label := strings.Split(node.Func.RelString(node.Func.Pkg.Pkg), ".")[0]
308                key := sign.Recv().Type().String()
309                if _ok := c.Clusters[key]; !ok {
310                    c.Clusters[key] = &dotCluster{
311                        ID:       key,
312                        Clustersmake(map[string]*dotCluster),
313                        AttrsdotAttrs{
314                            "penwidth":  "0.5",
315                            "fontsize":  "15",
316                            "fontcolor""#222222",
317                            "label":     label,
318                            "labelloc":  "b",
319                            "style":     "rounded,filled",
320                            "fillcolor""wheat2",
321                            "tooltip":   fmt.Sprintf("type: %s"key),
322                        },
323                    }
324                    if isFocused {
325                        c.Clusters[key].Attrs["fillcolor"] = "lightsteelblue"
326                    } else if pkg.Goroot {
327                        c.Clusters[key].Attrs["fillcolor"] = "#c2e3c2"
328                    }
329                }
330                c = c.Clusters[key]
331            }
332
333            attrs["tooltip"] = nodeTooltip
334
335            n := &dotNode{
336                ID:    node.Func.String(),
337                Attrsattrs,
338            }
339
340            if c != nil {
341                c.Nodes = append(c.Nodesn)
342            } else {
343                nodes = append(nodesn)
344            }
345
346            nodeMap[key] = n
347            return n
348        }
349        callerNode := sprintNode(edge.Callertrue)
350        calleeNode := sprintNode(edge.Calleefalse)
351
352        // edges
353        attrs := make(dotAttrs)
354
355        // dynamic call
356        if edge.Site != nil && edge.Site.Common().StaticCallee() == nil {
357            attrs["style"] = "dashed"
358        }
359
360        // go & defer calls
361        switch edge.Site.(type) {
362        case *ssa.Go:
363            attrs["arrowhead"] = "normalnoneodot"
364        case *ssa.Defer:
365            attrs["arrowhead"] = "normalnoneodiamond"
366        }
367
368        // colorize calls outside focused pkg
369        if focusPkg != nil &&
370            (calleePkg.Path() != focusPkg.Path() || callerPkg.Path() != focusPkg.Path()) {
371            attrs["color"] = "saddlebrown"
372        }
373
374        // use position in file where callee is called as tooltip for the edge
375        fileEdge := fmt.Sprintf(
376            "at %s:%d: calling [%s]",
377            filepath.Base(posEdge.Filename),
378            posEdge.Line,
379            edge.Callee.Func.String(),
380        )
381
382        // omit duplicate calls, except for tooltip enhancements
383        key := fmt.Sprintf("%s = %s => %s"caller.Funcedge.Description(), callee.Func)
384        if _ok := edgeMap[key]; !ok {
385            attrs["tooltip"] = fileEdge
386            e := &dotEdge{
387                From:  callerNode,
388                To:    calleeNode,
389                Attrsattrs,
390            }
391            edgeMap[key] = e
392        } else {
393            // make sure, tooltip is created correctly
394            if _okk := edgeMap[key].Attrs["tooltip"]; !okk {
395                edgeMap[key].Attrs["tooltip"] = fileEdge
396            } else {
397                edgeMap[key].Attrs["tooltip"] = fmt.Sprintf(
398                    "%s\n%s",
399                    edgeMap[key].Attrs["tooltip"],
400                    fileEdge,
401                )
402            }
403        }
404
405        return nil
406    })
407    if err != nil {
408        return nilerr
409    }
410
411    // get edges form edgeMap
412    for _e := range edgeMap {
413        e.From.Attrs["tooltip"] = fmt.Sprintf(
414            "%s\n%s",
415            e.From.Attrs["tooltip"],
416            e.Attrs["tooltip"],
417        )
418        edges = append(edgese)
419    }
420
421    logf("%d/%d edges"len(edges), count)
422
423    title := ""
424    if mainPkg != nil && mainPkg.Pkg != nil {
425        title = mainPkg.Pkg.Path()
426    }
427    dot := &dotGraph{
428        Title:   title,
429        Minlen:  minlen,
430        Clustercluster,
431        Nodes:   nodes,
432        Edges:   edges,
433        Options: map[string]string{
434            "minlen":    fmt.Sprint(minlen),
435            "nodesep":   fmt.Sprint(nodesep),
436            "nodeshape"fmt.Sprint(nodeshape),
437            "nodestyle"fmt.Sprint(nodestyle),
438            "rankdir":   fmt.Sprint(rankdir),
439        },
440    }
441
442    var buf bytes.Buffer
443    if err := dot.WriteDot(&buf); err != nil {
444        return nilerr
445    }
446
447    return buf.Bytes(), nil
448}
449
MembersX
printOutput.BlockStmt.BlockStmt.key
printOutput.BlockStmt.BlockStmt.BlockStmt.parts
printOutput.BlockStmt.BlockStmt._
printOutput.BlockStmt.BlockStmt.n
printOutput.BlockStmt.BlockStmt.fileCallee
printOutput.BlockStmt.BlockStmt.e
printOutput.nointer
printOutput.groupPkg
printOutput.BlockStmt.toFocused
printOutput.BlockStmt.RangeStmt_1714.e
printOutput.BlockStmt.include
printOutput.BlockStmt.BlockStmt.label
printOutput.BlockStmt.BlockStmt.sign
printOutput.prog
printOutput.RangeStmt_653.g
printOutput.BlockStmt.filenameCaller
printOutput.BlockStmt.calleePkg
printOutput.title
printOutput.BlockStmt.pkgPath
printOutput.BlockStmt.RangeStmt_2663.p
printOutput.BlockStmt.BlockStmt.attrs
printOutput.BlockStmt.BlockStmt.BlockStmt.label
printOutput.nodeMap
printOutput.BlockStmt.attrs
isSynthetic
printOutput
printOutput.focusPkg
printOutput.cluster
printOutput.BlockStmt.BlockStmt.pkg
printOutput.BlockStmt.fileEdge
inStd
inStd.node
printOutput.edges
printOutput.BlockStmt.posCaller
printOutput.edgeMap
inStd._
printOutput.BlockStmt.RangeStmt_1886.e
printOutput.err
printOutput.dot
printOutput.BlockStmt.key
printOutput.groupType
printOutput.BlockStmt.RangeStmt_2254.p
printOutput.BlockStmt.BlockStmt.c
printOutput.BlockStmt.calleeNode
printOutput.BlockStmt.callee
printOutput.BlockStmt.BlockStmt.nodeTooltip
printOutput.BlockStmt.BlockStmt.fileCaller
printOutput.buf
isSynthetic.edge
printOutput.mainPkg
printOutput.includePaths
printOutput.nostd
printOutput.BlockStmt.posEdge
printOutput.BlockStmt.callerNode
printOutput.RangeStmt_9700.e
inStd.pkg
printOutput.nodes
printOutput.BlockStmt.caller
printOutput.BlockStmt.posCallee
printOutput.BlockStmt.fromFocused
printOutput.BlockStmt.BlockStmt.BlockStmt.key
printOutput.BlockStmt.RangeStmt_2459.p
printOutput.count
printOutput.BlockStmt.callerPkg
printOutput.cg
printOutput.limitPaths
printOutput.ignorePaths
printOutput.groupBy
Members
X