Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: x/tools/cmd/callgraph: Add caller/callee definition locations to the edge output #65980

Closed
siroa opened this issue Feb 28, 2024 · 6 comments
Assignees
Labels
Milestone

Comments

@siroa
Copy link

siroa commented Feb 28, 2024

Proposal Details

  • The current output of callgraph edges lacks comprehensive details, making it less practical for users who need to locate function definitions. This proposal aims to address this by adding the precise locations of function definitions (file/line/col) for both caller and callee, which would significantly improve usability.
  • To implement these improvements, the proposal includes the following changes:
    • Modify the pos() function
    • Introduce new functions to capture the caller/caller's file, line, and column information.

Example (cmd/callgraph/main.go)

type Edge struct {
	Caller *ssa.Function
	Callee *ssa.Function

	edge         *callgraph.Edge
	fset         *token.FileSet
	position     token.Position // initialized lazily
	samePosition token.Pos
}

func (e *Edge) pos(pos token.Pos) *token.Position {
	if e.position.Offset == -1 || e.samePosition != pos {
		e.position = e.fset.Position(pos) // called lazily
		e.samePosition = pos
	}
	return &e.position
}

func (e *Edge) Filename() string { return e.pos(e.edge.Pos()).Filename }
func (e *Edge) Column() int      { return e.pos(e.edge.Pos()).Column }
func (e *Edge) Line() int        { return e.pos(e.edge.Pos()).Line }
func (e *Edge) Offset() int      { return e.pos(e.edge.Pos()).Offset }

func (e *Edge) CallerLine() int   { return e.pos(e.Caller.Pos()).Line }
func (e *Edge) CallerColumn() int { return e.pos(e.Caller.Pos()).Column }

func (e *Edge) CalleeFilename() string { return e.pos(e.Callee.Pos()).Filename }
func (e *Edge) CalleeLine() int        { return e.pos(e.Callee.Pos()).Line }
func (e *Edge) CalleeColumn() int      { return e.pos(e.Callee.Pos()).Column }
@siroa siroa added the Proposal label Feb 28, 2024
@gopherbot gopherbot added this to the Proposal milestone Feb 28, 2024
@adonovan
Copy link
Member

You can already print these fields by altering the value of the -format flag:

xtools$ callgraph
callgraph: display the call graph of a Go program.

Usage:

  callgraph [-algo=static|cha|rta|vta] [-test] [-format=...] package...

Flags:

-algo      Specifies the call-graph construction algorithm, one of:

            static      static calls only (unsound)
            cha         Class Hierarchy Analysis
            rta         Rapid Type Analysis
            vta         Variable Type Analysis

           The algorithms are ordered by increasing precision in their
           treatment of dynamic calls (and thus also computational cost).
           RTA requires a whole program (main or test), and
           include only functions reachable from main.

-test      Include the package's tests in the analysis.

-format    Specifies the format in which each call graph edge is displayed.
           One of:

            digraph     output suitable for input to
                        golang.org/x/tools/cmd/digraph.
            graphviz    output in AT&T GraphViz (.dot) format.

           All other values are interpreted using text/template syntax.
           The default value is:

            {{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}

           The structure passed to the template is (effectively):

                   type Edge struct {
                           Caller      *ssa.Function // calling function
                           Callee      *ssa.Function // called function

                           // Call site:
                           Filename    string // containing file
                           Offset      int    // offset within file of '('
                           Line        int    // line number
                           Column      int    // column number of call
                           Dynamic     string // "static" or "dynamic"
                           Description string // e.g. "static method call"
                   }

           Caller and Callee are *ssa.Function values, which print as
           "(*sync/atomic.Mutex).Lock", but other attributes may be
           derived from them, e.g. Caller.Pkg.Pkg.Path yields the
           import path of the enclosing package.  Consult the go/ssa
           API documentation for details.

Examples:

  Show the call graph of the trivial web server application:

    callgraph -format digraph $GOROOT/src/net/http/triv.go

  Same, but show only the packages of each function:

    callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
      $GOROOT/src/net/http/triv.go | sort | uniq

  Show functions that make dynamic calls into the 'fmt' test package,
  using the Rapid Type Analysis algorithm:

    callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=rta fmt |
      sed -ne 's/-dynamic-/--/p' |
      sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq

  Show all functions directly called by the callgraph tool's main function:

    callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
      digraph succs golang.org/x/tools/cmd/callgraph.main

xtools$ callgraph  -format='{{.Filename}}:{{.Line}}:{{.Column}}: {{.Caller}} -{{.Dynamic}}-> {{.Callee}}'  ./cmd/callgraph/ | head 
src/text/template/parse/node.go:375:25: (*text/template/parse.IdentifierNode).writeTo -static-> (*text/template/parse.IdentifierNode).String
src/strings/builder.go:41:31: (*strings.Builder).copyCheck -static-> strings.noescape
src/strings/builder.go:115:13: (*strings.Builder).WriteString -static-> (*strings.Builder).copyCheck
...

@siroa
Copy link
Author

siroa commented Feb 28, 2024

@adonovan Thank you for your prompt reply.
This proposal intended to solve the last item of the TODO described here.

(cmd/callgraph/main.go:9-20)

// TODO(adonovan):
//
// Features:
// - restrict graph to a single package
// - output
//   - functions reachable from root (use digraph tool?)
//   - unreachable functions (use digraph tool?)
//   - dynamic (runtime) types
//   - indexed output (numbered nodes)
//   - JSON output
//   - additional template fields:
//     callee file/line/col

Here is an example of the proposed command line.

callgraph -algo=cha -format='{{.Caller}}--{{.CallerLine}}:{{.CallerColumn}}--{{.Filename}}--{{.Dynamic}}--{{.Line}}:{{.Column}}-->{{.Callee}}--{{.CalleeLine}}:{{.CalleeColumn}}--{{.CalleeFilename}}'
runtime.gcParkAssist--610:6--/usr/local/go/src/runtime/mgcmark.go--static--631:36-->(*runtime.guintptr).set--266:21--/usr/local/go/src/runtime/runtime2.go
runtime.gcParkAssist--610:6--/usr/local/go/src/runtime/mgcmark.go--static--633:9-->runtime.unlock--110:6--/usr/local/go/src/runtime/lock_futex.go

Are you suggesting that it is possible to output these in the current version?

@adonovan adonovan reopened this Feb 28, 2024
@adonovan
Copy link
Member

adonovan commented Feb 28, 2024

Fair enough: it is technically possible:

$ callgraph -format='{{(.Caller.Prog.Fset.Position .Caller.Pos).Filename}}:{{(.Caller.Prog.Fset.Position .Caller.Pos).Line}}' ./cmd/digraph

but it isn't very practical. I think adding a posn function to the template environment might be all that is needed:

{{(posn .Caller).Filename}}

@siroa
Copy link
Author

siroa commented Feb 29, 2024

Thank you for the detailed explanation. Following your suggestion, I have considered the following proposals:

format flag:

-format="{{(posn .Caller).Filename}}--{{(posn .Caller).Line}}"

code:

funcMap := template.FuncMap{
		"posn": func(f *ssa.Function) token.Position { return prog.Fset.Position(f.Pos()) },
	}
tmpl, err := template.New("-format").Funcs(funcMap).Parse(format)

or

format flag:

-format="{{(.Posn .Caller).Filename}}--{{(.Posn .Caller).Line}}"

code:

func (e *Edge) Posn(f *ssa.Function) token.Position { return e.fset.Position(f.Pos()) }

@gopherbot
Copy link

Change https://go.dev/cl/567838 mentions this issue: cmd/callgraph: add 'posn' template helper

@adonovan
Copy link
Member

Thanks @siroa, I've mailed a CL to add (and document) the posn template function using your first approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants