github.com/jujuyuki/gospal@v1.0.1-0.20210215170718-af79fae13b20/ssa/callgraph.go (about)

     1  package ssa
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"go/token"
     7  	"io"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	"golang.org/x/tools/go/callgraph"
    12  	"golang.org/x/tools/go/callgraph/cha"
    13  	"golang.org/x/tools/go/callgraph/rta"
    14  	"golang.org/x/tools/go/callgraph/static"
    15  	"golang.org/x/tools/go/ssa"
    16  )
    17  
    18  // CallGraph is a representation of CallGraph, wrapped with metadata.
    19  type CallGraph struct {
    20  	cg      *callgraph.Graph // Internal cached copy of the callgraph.
    21  	edges   []*cgEdge        // Result of callgraph analysis.
    22  	prog    *ssa.Program     // SSA Program for which the callgraph is built from.
    23  	usedFns []*ssa.Function  // Functions actually used by current Program.
    24  	allFns  []*ssa.Function  // Functions in the current Program (including unused).
    25  }
    26  
    27  // AllFunctions return all ssa.Functions defined in the current Program.
    28  func (g *CallGraph) AllFunctions() ([]*ssa.Function, error) {
    29  	// If cached.
    30  	if g.allFns != nil {
    31  		return g.allFns, nil
    32  	}
    33  
    34  	visited := make(map[*ssa.Function]bool)
    35  	if err := callgraph.GraphVisitEdges(g.cg, func(edge *callgraph.Edge) error {
    36  		if _, ok := visited[edge.Caller.Func]; !ok {
    37  			visited[edge.Caller.Func] = true
    38  		}
    39  		if _, ok := visited[edge.Callee.Func]; !ok {
    40  			visited[edge.Callee.Func] = true
    41  		}
    42  		return nil
    43  	}); err != nil {
    44  		return nil, errors.Wrap(err, "callgraph: failed to visit edges")
    45  	}
    46  
    47  	for fn := range visited {
    48  		g.allFns = append(g.allFns, fn)
    49  	}
    50  	return g.allFns, nil
    51  }
    52  
    53  // UsedFunctions return a slice of ssa.Function actually used by the current
    54  // Program, rooted at main.init() and main.main().
    55  func (g *CallGraph) UsedFunctions() ([]*ssa.Function, error) {
    56  	// Cached.
    57  	if g.usedFns != nil {
    58  		return g.usedFns, nil
    59  	}
    60  
    61  	callTree := make(map[*ssa.Function][]*ssa.Function)
    62  	if err := callgraph.GraphVisitEdges(g.cg, func(edge *callgraph.Edge) error {
    63  		callTree[edge.Caller.Func] = append(callTree[edge.Caller.Func], edge.Callee.Func)
    64  		return nil
    65  	}); err != nil {
    66  		return nil, errors.Wrap(err, "callgraph: failed to visit edges")
    67  	}
    68  
    69  	mains, err := MainPkgs(g.prog, false)
    70  	if err != nil {
    71  		return nil, errors.Wrap(err, "callgraph: failed to find main packages (Check if this this a command?)")
    72  	}
    73  
    74  	var fnQueue []*ssa.Function
    75  	for _, main := range mains {
    76  		if main.Func("main") != nil {
    77  			fnQueue = append(fnQueue, main.Func("init"), main.Func("main"))
    78  		}
    79  	}
    80  
    81  	visited := make(map[*ssa.Function]bool)
    82  	for len(fnQueue) > 0 {
    83  		headFn := fnQueue[0]
    84  		fnQueue = fnQueue[1:]
    85  		visited[headFn] = true
    86  		for _, fn := range callTree[headFn] {
    87  			if _, ok := visited[fn]; !ok { // If not visited
    88  				fnQueue = append(fnQueue, fn)
    89  			}
    90  			visited[fn] = true
    91  		}
    92  	}
    93  
    94  	for fn := range visited {
    95  		g.usedFns = append(g.usedFns, fn)
    96  	}
    97  	return g.usedFns, nil
    98  }
    99  
   100  // populateEdges populates a slice of edges in the CallGraph.
   101  func (g *CallGraph) populateEdges(edge *callgraph.Edge) error {
   102  	e := &cgEdge{
   103  		Caller:   edge.Caller.Func,
   104  		Callee:   edge.Callee.Func,
   105  		position: token.Position{Offset: -1},
   106  		edge:     edge,
   107  		fset:     g.prog.Fset,
   108  	}
   109  	g.edges = append(g.edges, e)
   110  	return nil
   111  }
   112  
   113  // WriteGraphviz writes callgraph to w in graphviz dot format.
   114  func (g *CallGraph) WriteGraphviz(w io.Writer) error {
   115  	if g.edges == nil {
   116  		if err := callgraph.GraphVisitEdges(g.cg, g.populateEdges); err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	bufw := bufio.NewWriter(w)
   122  	bufw.WriteString("digraph callgraph {\n")
   123  	// Instead of using template..
   124  	for _, edge := range g.edges {
   125  		bufw.WriteString(fmt.Sprintf("  %q -> %q\n", edge.Caller, edge.Callee))
   126  	}
   127  	bufw.WriteString("}\n")
   128  	bufw.Flush()
   129  	return nil
   130  }
   131  
   132  // cgEdge is a single edge in the callgraph.
   133  //
   134  // Code based on golang.org/x/tools/cmd/callgraph
   135  //
   136  type cgEdge struct {
   137  	Caller *ssa.Function
   138  	Callee *ssa.Function
   139  
   140  	edge     *callgraph.Edge
   141  	fset     *token.FileSet
   142  	position token.Position // initialized lazily
   143  }
   144  
   145  func (e *cgEdge) pos() *token.Position {
   146  	if e.position.Offset == -1 {
   147  		e.position = e.fset.Position(e.edge.Pos()) // called lazily
   148  	}
   149  	return &e.position
   150  }
   151  
   152  func (e *cgEdge) Filename() string { return e.pos().Filename }
   153  func (e *cgEdge) Column() int      { return e.pos().Column }
   154  func (e *cgEdge) Line() int        { return e.pos().Line }
   155  func (e *cgEdge) Offset() int      { return e.pos().Offset }
   156  
   157  func (e *cgEdge) Dynamic() string {
   158  	if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
   159  		return "dynamic"
   160  	}
   161  	return "static"
   162  }
   163  
   164  func (e *cgEdge) Description() string { return e.edge.Description() }
   165  
   166  // BuildCallGraph constructs a callgraph from ssa.Info.
   167  // algo is algorithm available in golang.org/x/tools/go/callgraph, which
   168  // includes:
   169  //  - static  static calls only (unsound)
   170  //  - cha     Class Hierarchy Analysis
   171  //  - rta     Rapid Type Analysis
   172  //  - pta     inclusion-based Points-To Analysis
   173  //
   174  func (info *Info) BuildCallGraph(algo string, tests bool) (*CallGraph, error) {
   175  	var cg *callgraph.Graph
   176  	switch algo {
   177  	case "static":
   178  		cg = static.CallGraph(info.Prog)
   179  
   180  	case "cha":
   181  		cg = cha.CallGraph(info.Prog)
   182  
   183  	case "pta":
   184  		ptrCfg, err := info.PtrAnlysCfg(tests)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		ptrCfg.BuildCallGraph = true
   189  		ptares, err := info.RunPtrAnlys(ptrCfg)
   190  		if err != nil {
   191  			return nil, err // internal error in pointer analysis
   192  		}
   193  		cg = ptares.CallGraph
   194  
   195  	case "rta":
   196  		mains, err := MainPkgs(info.Prog, tests)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  		var roots []*ssa.Function
   201  		for _, main := range mains {
   202  			roots = append(roots, main.Func("init"), main.Func("main"))
   203  		}
   204  		rtares := rta.Analyze(roots, true)
   205  		cg = rtares.CallGraph
   206  	}
   207  
   208  	cg.DeleteSyntheticNodes()
   209  
   210  	return &CallGraph{cg: cg, prog: info.Prog}, nil
   211  }