ondrajz / go-callvis

Visualize call graph of a Go program using Graphviz
https://ofabry.github.io/go-callvis
MIT License
5.91k stars 408 forks source link

Focus on function instead of package #21

Open npollitt opened 6 years ago

npollitt commented 6 years ago

I have a web service that registers callback functions that never get explicitly called. Running this tool skips over those functions, which is exactly what I want to graph.

ondrajz commented 6 years ago

Could you maybe provide some example? If they are never explicitly called, how else are they called?

dt-rush commented 6 years ago

I guess this would mean treating the function in question as though it were main(), or invoked by main with the appropriate arguments. I'd also like to be able to target individual functions.

dt-rush commented 6 years ago

note: I am not familiar with any of this

I have never looked into any of these packages before, but from reading the main.go, it seems that the way that pointer.Config works is such as to need mains, that is, proper go programs/packages and not merely a function call floating in space.

The mains []*ssa.Package variable is a necessary field of pointer.Config, which is passed to pointer.Analyze in order to produce the call graph data that eventually becomes transformed by output.go into the "DOT" language used by graphviz.

Keeping in mind that I'm very unfamiliar with any of these packages and have just spent about 40 minutes looking into docs, it seems we might be able to do the following, but I'm not sure I could do this without quite a bit more effort and I may even be simply wrong:

possible solution

Say we are giving a function in a package which we want to visualize, as a command line argument: We get the package name and the name of the function.

We might be able to use the ssa package to create from scratch a simplified ssa.Program / ssa.Package which does one thing: call the function. We would include our synthetic ssa.Package as an element of mains []*ssa.Package in our pointer.Config object which we pass to pointer.Analyze().

This would require that the receiver of the function, if it's a method, and its params, also be part of the program (and imported if necessary). Since they don't need to have actual values, we could simply instantiate the structs. So for example, if we had a function in package hello, defined in hello.go:

func (o *ObjectOfSomeKind) doTheThing(a *packagea.StructA, b packageb.StructB) {
    ... definition ...
}

We could build the following (seems easy right......?):

package main

// note: we would need the actual import paths for the packages, not just their names.
// we could probably do this by parsing `hello.go`
import (
    "hello"
    "packagea"
    "packageb"
)

func main() {
    var o hello.ObjectOfSomeKind
    var a packagea.StructA
    var b packageb.StructB
    o.doTheThing(&a, b)
}

Now, in the ssa GoDoc, we have the following:

func (*Program) NewFunction

func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function

NewFunction returns a new synthetic Function instance belonging to prog, with its name and signature fields set as specified.

The caller is responsible for initializing the remaining fields of the function object, e.g. Pkg, Params, Blocks.

It is practically impossible for clients to construct well-formed SSA functions/packages/programs directly, so we assume this is the job of the Builder alone. NewFunction exists to provide clients a little flexibility. For example, analysis tools may wish to construct fake Functions for the root of the callgraph, a fake "reflect" package, etc.

This last paragraph makes it seems like constructing well-formed SSA packages / programs is "practically impossible", which does not give me much hope. Maybe we'd have a better time constructing a temporary .go source file and using that as a shortcut around the ssa difficulties?

dt-rush commented 6 years ago

I haven't found a good way of making this not produce either too little or too much output... difficulties in generating the code aside.

dt-rush commented 6 years ago

I've found a good library for generating the code: jennifer.

As far as reading the source file with the focused function to determine how to build the temporary stub main.go we want to include as the only element of mains in pointer.Config (which is fed to pointer.Analyze), we can use parser.ParseFile to produce an ast.File which we can travese to find the necessary param types to instantiate and packages to import in order to support calling our function.

dt-rush commented 6 years ago

To sum up the state of this issue: go-callvis is written only to produce graphs for programs, and it's no fault of go-callvis, as it depends on pointer.Analyze which runs its analysis (producing output we use to build our graph) given a program. I propose that we feed the beast what it desires, by creating a temporary stub main.go which merely does what's necessary to call our single function: importing needed packages and instantiating needed params/the function's receiver.

dt-rush commented 6 years ago

It turns out that github.com/dave/jennifer can also automatically add import statements for qualified names. So the matter of added necessary imports for the param types of the function is near-solved

ondrajz commented 6 years ago

Thank you for your commentts. I will think about some appropriate way to deal with non-main packages.

Meanwhile, please checkout upcoming change for go-callvis: https://github.com/TrueFurby/go-callvis/pull/29

The call graph output is now served via HTTP server in SVG format and you can click on packages to change focus.

rohantmp commented 5 years ago

Even cobra type cli programs have their subcommands registered instead of explicitly called.