Skip to content

Commit

Permalink
WIP: add callgraph package
Browse files Browse the repository at this point in the history
  • Loading branch information
M. J. Fromberger committed Oct 28, 2021
1 parent 6108d0a commit 5b80c21
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -37,6 +37,7 @@ require (
golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
google.golang.org/grpc v1.41.0
golang.org/x/tools v0.1.5
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
pgregory.net/rapid v0.4.7
)
115 changes: 115 additions & 0 deletions tools/panic/callgraph/callgraph.go
@@ -0,0 +1,115 @@
package callgraph

import (
"fmt"
"go/ast"
"go/types"
"sort"

"golang.org/x/tools/go/loader"
)

type Triple struct {
Caller Entry
Target Entry
Site Location
}

type Entry struct {
Package string // canonical import path
Name string // name relative to the package ("" for calls at file scope)
}

type Location struct {
Path string
Offset int // 0-based
Line, Col int // 1-based line, 0-based byte offset
}

type Graph struct {
cfg *loader.Config
}

func New() *Graph {
cfg := new(loader.Config)
cfg.TypeCheckFuncBodies = func(ip string) bool {
_, ok := cfg.ImportPkgs[ip]
return ok
}
return &Graph{cfg: cfg}
}

func (g *Graph) Import(ipath string) { g.cfg.Import(ipath) }

func (g *Graph) ImportWithTests(ipath string) { g.cfg.ImportWithTests(ipath) }

func (g *Graph) Process(f func(*Triple)) error {
pgm, err := g.cfg.Load()
if err != nil {
return fmt.Errorf("loading program: %v", err)
}
var pkgs []*loader.PackageInfo
for _, pkg := range pgm.Imported {
pkgs = append(pkgs, pkg)
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Pkg.Path() < pkgs[j].Pkg.Path()
})

for _, pkg := range pkgs {
for _, file := range pkg.Files {
fname := g.cfg.Fset.Position(file.Pos()).Filename

var nodes []ast.Node
parent := func() string {
for i := len(nodes) - 1; i >= 0; i-- {
switch t := nodes[i].(type) {
case *ast.FuncDecl:
return t.Name.Name
}
}
return fname
}

ast.Walk(visitFunc(func(node ast.Node) {
if node == nil {
nodes = nodes[:len(nodes)-1]
return
}
nodes = append(nodes, node)

switch t := node.(type) {
case *ast.Ident:
ref := pkg.Info.Uses[t]
if ref == nil {
return // no referent
}
var refPath string
if _, ok := ref.Type().(*types.Signature); ok {
refPath = ref.Pkg().Path() // OK, function
} else if _, ok := ref.(*types.Builtin); ok {
// OK, builtin
} else {
return // not a function call or reference
}
pos := g.cfg.Fset.Position(t.Pos())
f(&Triple{
Caller: Entry{Package: pkg.Pkg.Path(), Name: parent()},
Target: Entry{Package: refPath, Name: ref.Name()},
Site: Location{
Path: pos.Filename,
Offset: pos.Offset,
Line: pos.Line,
Col: pos.Column - 1,
},
})
}
}), file)
}
}
return nil
}

type visitFunc func(ast.Node)

func (v visitFunc) Visit(n ast.Node) ast.Visitor { v(n); return v }
19 changes: 19 additions & 0 deletions tools/panic/callgraph/callgraph_test.go
@@ -0,0 +1,19 @@
package callgraph_test

import (
"testing"

"github.com/tendermint/tendermint/tools/panic/callgraph"
)

func TestStub(t *testing.T) {
g := callgraph.New()
g.ImportWithTests("github.com/tendermint/tendermint/internal/consensus")
if err := g.Process(func(cg *callgraph.Triple) {
if cg.Target.Name == "panic" {
t.Logf("Panic call at %v", cg.Site)
}
}); err != nil {
t.Fatal(err)
}
}

0 comments on commit 5b80c21

Please sign in to comment.