From 4e94a82bf2ffaa995006fe535deac27d152b6ecf Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 24 May 2022 06:34:27 +0200 Subject: [PATCH 01/20] Add GitHub workflow (#118) --- .github/workflows/ci.yml | 42 ++++++++ .gitignore | 2 +- .travis.yml | 12 --- Makefile | 63 ++++++++---- dot.go | 210 ++++++++++++++++++--------------------- dot_cgo.go | 37 +++++++ dot_nocgo.go | 8 ++ 7 files changed, 223 insertions(+), 151 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100644 dot_cgo.go create mode 100644 dot_nocgo.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0919e9c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 */12 * * *' + +jobs: + + build-test: + name: "Build & Test" + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + go: [ '1.18', '1.17' ] + + steps: + - uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Download Deps + run: go mod download + + - name: Check go.mod + run: | + go mod tidy -v + git diff --name-only --exit-code go.mod || ( git diff && echo "Run go tidy to update go.mod" && false ) + + - name: Build All + run: make build + + - name: Test All + run: make test diff --git a/.gitignore b/.gitignore index 989104a..f4e7b10 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,5 @@ _testmain.go *.out # Builds -build/ +/.build/ go-callvis diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 97a5ed7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -dist: xenial - -language: go -go: - - "1.13.x" - - "1.12.x" - -install: true - -script: - - make test - - make install diff --git a/Makefile b/Makefile index b1e76d0..5240250 100644 --- a/Makefile +++ b/Makefile @@ -1,34 +1,56 @@ -SHELL = /bin/bash +SHELL := /usr/bin/env bash -o pipefail -GIT_VERSION ?= $(shell git describe --always --tags --always --dirty) +GIT_VERSION ?= $(shell git describe --always --tags --match 'v*' --dirty) +COMMIT ?= $(shell git rev-parse HEAD) +BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) +BUILD_DATE ?= $(shell date +%s) +BUILD_HOST ?= $(shell hostname) +BUILD_USER ?= $(shell id -un) + +PROJECT := go-callvis +BUILD_DIR ?= .build GOOS ?= $(shell go env GOOS) GOARCH = amd64 PLATFORMS := linux-$(GOARCH) darwin-$(GOARCH) -BUILD_DIR ?= ./build -ORG := github.com/ofabry -PROJECT := go-callvis -REPOPATH ?= $(ORG)/$(PROJECT) -BUILD_PACKAGE = $(REPOPATH) - GO_BUILD_TAGS ?= "" -GO_LDFLAGS := "-X main.commit=$(GIT_VERSION)" -GO_FILES := $(shell go list -f '{{join .Deps "\n"}}' $(BUILD_PACKAGE) | grep $(ORG) | xargs go list -f '{{ range $$file := .GoFiles }} {{$$.Dir}}/{{$$file}}{{"\n"}}{{end}}') +GO_LDFLAGS := \ + -X main.commit=$(GIT_VERSION) +GO_FILES := $(shell go list ./... | xargs go list -f '{{ range $$file := .GoFiles }} {{$$.Dir}}/{{$$file}}{{"\n"}}{{end}}') + +ifeq ($(NOSTRIP),) +GO_LDFLAGS += -w -s +endif + +ifeq ($(NOTRIM),) +GO_BUILD_ARGS += -trimpath +endif + +ifeq ($(V),1) +GO_BUILD_ARGS += -v +endif export GO111MODULE=on +export DOCKER_BUILDKIT=1 + +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -install: - go install -tags $(GO_BUILD_TAGS) -ldflags $(GO_LDFLAGS) +build: ## Build go-callvis + go build -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) -all: - go build -tags $(GO_BUILD_TAGS) -ldflags $(GO_LDFLAGS) +test: ## Run unit tests + go test -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) -short -race ./... + +install: ## Install go-callvis + go install -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) $(BUILD_DIR)/$(PROJECT): $(BUILD_DIR)/$(PROJECT)-$(GOOS)-$(GOARCH) cp $(BUILD_DIR)/$(PROJECT)-$(GOOS)-$(GOARCH) $@ $(BUILD_DIR)/$(PROJECT)-%-$(GOARCH): $(GO_FILES) $(BUILD_DIR) - GOOS=$* GOARCH=$(GOARCH) go build -tags $(GO_BUILD_TAGS) -ldflags $(GO_LDFLAGS) -o $@ $(BUILD_PACKAGE) + GOOS=$* GOARCH=$(GOARCH) go build -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" -o $@ $(GO_BUILD_ARGS) %.sha256: % shasum -a 256 $< &> $@ @@ -40,13 +62,10 @@ $(BUILD_DIR): cross: $(foreach platform, $(PLATFORMS), $(BUILD_DIR)/$(PROJECT)-$(platform).sha256) -release: cross +release: cross ## Release go-callvis ls -hl $(BUILD_DIR) -test: $(BUILD_DIR)/$(PROJECT) - go test -v $(REPOPATH) - -clean: - rm -rf $(BUILD_DIR) +clean: ## Clean build directory + rm -vrf $(BUILD_DIR) -.PHONY: cross release install test clean +.PHONY: help build test install cross release clean diff --git a/dot.go b/dot.go index 0584b8e..43e8da4 100644 --- a/dot.go +++ b/dot.go @@ -1,85 +1,25 @@ package main import ( - "bytes" - "fmt" - "io" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" - - "github.com/goccy/go-graphviz" + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" ) var ( - minlen uint - nodesep float64 - nodeshape string - nodestyle string - rankdir string + minlen uint + nodesep float64 + nodeshape string + nodestyle string + rankdir string ) -// location of dot executable for converting from .dot to .svg -// it's usually at: /usr/bin/dot -var dotExe string - -// dotToImageGraphviz generates a SVG using the 'dot' utility, returning the filepath -func dotToImageGraphviz(outfname string, format string, dot []byte) (string, error) { - if dotExe == "" { - dot, err := exec.LookPath("dot") - if err != nil { - log.Fatalln("unable to find program 'dot', please install it or check your PATH") - } - dotExe = dot - } - - var img string - if outfname == "" { - img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) - } else { - img = fmt.Sprintf("%s.%s", outfname, format) - } - cmd := exec.Command(dotExe, fmt.Sprintf("-T%s", format), "-o", img) - cmd.Stdin = bytes.NewReader(dot) - var stderr bytes.Buffer - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) - } - return img, nil -} - -func dotToImage(outfname string, format string, dot []byte) (string, error) { - if *graphvizFlag { - return dotToImageGraphviz(outfname, format, dot) - } - - g := graphviz.New() - graph, err := graphviz.ParseBytes(dot) - if err != nil { - return "", err - } - defer func() { - if err := graph.Close(); err != nil { - log.Fatal(err) - } - g.Close() - }() - var img string - if outfname == "" { - img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) - } else { - img = fmt.Sprintf("%s.%s", outfname, format) - } - if err := g.RenderFilename(graph, graphviz.Format(format), img); err != nil { - return "", err - } - return img, nil -} - const tmplCluster = `{{define "cluster" -}} {{printf "subgraph %q {" .}} {{printf "%s" .Attrs.Lines}} @@ -125,82 +65,120 @@ const tmplGraph = `digraph gocallvis { //==[ type def/func: dotCluster ]=============================================== type dotCluster struct { - ID string - Clusters map[string]*dotCluster - Nodes []*dotNode - Attrs dotAttrs + ID string + Clusters map[string]*dotCluster + Nodes []*dotNode + Attrs dotAttrs } func NewDotCluster(id string) *dotCluster { - return &dotCluster{ - ID: id, - Clusters: make(map[string]*dotCluster), - Attrs: make(dotAttrs), - } + return &dotCluster{ + ID: id, + Clusters: make(map[string]*dotCluster), + Attrs: make(dotAttrs), + } } func (c *dotCluster) String() string { - return fmt.Sprintf("cluster_%s", c.ID) + return fmt.Sprintf("cluster_%s", c.ID) } //==[ type def/func: dotNode ]=============================================== type dotNode struct { - ID string - Attrs dotAttrs + ID string + Attrs dotAttrs } func (n *dotNode) String() string { - return n.ID + return n.ID } //==[ type def/func: dotEdge ]=============================================== type dotEdge struct { - From *dotNode - To *dotNode - Attrs dotAttrs + From *dotNode + To *dotNode + Attrs dotAttrs } //==[ type def/func: dotAttrs ]=============================================== type dotAttrs map[string]string func (p dotAttrs) List() []string { - l := []string{} - for k, v := range p { - l = append(l, fmt.Sprintf("%s=%q", k, v)) - } - return l + l := []string{} + for k, v := range p { + l = append(l, fmt.Sprintf("%s=%q", k, v)) + } + return l } func (p dotAttrs) String() string { - return strings.Join(p.List(), " ") + return strings.Join(p.List(), " ") } func (p dotAttrs) Lines() string { - return fmt.Sprintf("%s;", strings.Join(p.List(), ";\n")) + return fmt.Sprintf("%s;", strings.Join(p.List(), ";\n")) } //==[ type def/func: dotGraph ]=============================================== type dotGraph struct { - Title string - Minlen uint - Attrs dotAttrs - Cluster *dotCluster - Nodes []*dotNode - Edges []*dotEdge - Options map[string]string + Title string + Minlen uint + Attrs dotAttrs + Cluster *dotCluster + Nodes []*dotNode + Edges []*dotEdge + Options map[string]string } func (g *dotGraph) WriteDot(w io.Writer) error { - t := template.New("dot") - for _, s := range []string{tmplCluster, tmplNode, tmplEdge, tmplGraph} { - if _, err := t.Parse(s); err != nil { - return err - } - } - var buf bytes.Buffer - if err := t.Execute(&buf, g); err != nil { - return err - } - _, err := buf.WriteTo(w) - return err + t := template.New("dot") + for _, s := range []string{tmplCluster, tmplNode, tmplEdge, tmplGraph} { + if _, err := t.Parse(s); err != nil { + return err + } + } + var buf bytes.Buffer + if err := t.Execute(&buf, g); err != nil { + return err + } + _, err := buf.WriteTo(w) + return err +} + +func dotToImage(outfname string, format string, dot []byte) (string, error) { + if *graphvizFlag { + return runDotToImageCallSystemGraphviz(outfname, format, dot) + } + + return runDotToImage(outfname, format, dot) +} + +// location of dot executable for converting from .dot to .svg +// it's usually at: /usr/bin/dot +var dotSystemBinary string + +// runDotToImageCallSystemGraphviz generates a SVG using the 'dot' utility, returning the filepath +func runDotToImageCallSystemGraphviz(outfname string, format string, dot []byte) (string, error) { + if dotSystemBinary == "" { + dot, err := exec.LookPath("dot") + if err != nil { + log.Fatalln("unable to find program 'dot', please install it or check your PATH") + } + dotSystemBinary = dot + } + + var img string + if outfname == "" { + img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) + } else { + img = fmt.Sprintf("%s.%s", outfname, format) + } + cmd := exec.Command(dotSystemBinary, fmt.Sprintf("-T%s", format), "-o", img) + cmd.Stdin = bytes.NewReader(dot) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) + } + return img, nil } diff --git a/dot_cgo.go b/dot_cgo.go new file mode 100644 index 0000000..a93435e --- /dev/null +++ b/dot_cgo.go @@ -0,0 +1,37 @@ +//go:build cgo +// +build cgo + +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/goccy/go-graphviz" +) + +func runDotToImage(outfname string, format string, dot []byte) (string, error) { + g := graphviz.New() + graph, err := graphviz.ParseBytes(dot) + if err != nil { + return "", err + } + defer func() { + if err := graph.Close(); err != nil { + log.Fatal(err) + } + g.Close() + }() + var img string + if outfname == "" { + img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) + } else { + img = fmt.Sprintf("%s.%s", outfname, format) + } + if err := g.RenderFilename(graph, graphviz.Format(format), img); err != nil { + return "", err + } + return img, nil +} diff --git a/dot_nocgo.go b/dot_nocgo.go new file mode 100644 index 0000000..bfde541 --- /dev/null +++ b/dot_nocgo.go @@ -0,0 +1,8 @@ +//go:build !cgo +// +build !cgo + +package main + +func runDotToImage(outfname string, format string, dot []byte) (string, error) { + return runDotToImageCallSystemGraphviz(outfname, format, dot) +} From 06b3e4b2686a87c6c1a40e620fe6a5b31e1c2a0d Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 24 May 2022 12:39:58 +0800 Subject: [PATCH 02/20] Add supports for all callgraph construction algo (#104) * add supports for all callgraph construction algo * minor fix Co-authored-by: Ondrej Fabry --- analysis.go | 99 +++++++++++++++++++++++++++++++++++++---------------- main.go | 32 +++++++++-------- output.go | 22 +++++++----- 3 files changed, 99 insertions(+), 54 deletions(-) diff --git a/analysis.go b/analysis.go index a3c92bd..7c02067 100644 --- a/analysis.go +++ b/analysis.go @@ -5,6 +5,10 @@ import ( "fmt" "go/build" "go/types" + "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/callgraph/cha" + "golang.org/x/tools/go/callgraph/rta" + "golang.org/x/tools/go/callgraph/static" "io" "log" "net/http" @@ -18,6 +22,15 @@ import ( "golang.org/x/tools/go/ssa/ssautil" ) +type CallGraphType string + +const ( + CallGraphTypeStatic CallGraphType = "static" + CallGraphTypeCha = "cha" + CallGraphTypeRta = "rta" + CallGraphTypePointer = "pointer" +) + //==[ type def/func: analysis ]=============================================== type renderOpts struct { cacheDir string @@ -29,6 +42,7 @@ type renderOpts struct { nointer bool refresh bool nostd bool + algo CallGraphType } // mainPackages returns the main packages to analyze. @@ -48,24 +62,25 @@ func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) { //==[ type def/func: analysis ]=============================================== type analysis struct { - opts *renderOpts - prog *ssa.Program - pkgs []*ssa.Package - mains []*ssa.Package - result *pointer.Result + opts *renderOpts + prog *ssa.Program + pkgs []*ssa.Package + mainPkg *ssa.Package + callgraph *callgraph.Graph } var Analysis *analysis func (a *analysis) DoAnalysis( + algo CallGraphType, dir string, tests bool, args []string, ) error { cfg := &packages.Config{ - Mode: packages.LoadAllSyntax, - Tests: tests, - Dir: dir, + Mode: packages.LoadAllSyntax, + Tests: tests, + Dir: dir, BuildFlags: build.Default.BuildTags, } @@ -82,26 +97,50 @@ func (a *analysis) DoAnalysis( prog, pkgs := ssautil.AllPackages(initial, 0) prog.Build() - mains, err := mainPackages(pkgs) - if err != nil { - return err - } - - config := &pointer.Config{ - Mains: mains, - BuildCallGraph: true, + var graph *callgraph.Graph + var mainPkg *ssa.Package + + switch algo { + case CallGraphTypeStatic: + graph = static.CallGraph(prog) + case CallGraphTypeCha: + graph = cha.CallGraph(prog) + case CallGraphTypeRta: + mains, err := mainPackages(prog.AllPackages()) + if err != nil { + return err + } + var roots []*ssa.Function + mainPkg = mains[0] + for _, main := range mains { + roots = append(roots, main.Func("main")) + } + graph = rta.Analyze(roots, true).CallGraph + case CallGraphTypePointer: + mains, err := mainPackages(prog.AllPackages()) + if err != nil { + return err + } + mainPkg = mains[0] + config := &pointer.Config{ + Mains: mains, + BuildCallGraph: true, + } + ptares, err := pointer.Analyze(config) + if err != nil { + return err + } + graph = ptares.CallGraph + default: + return fmt.Errorf("invalid call graph type: %s", a.opts.algo) } - result, err := pointer.Analyze(config) - if err != nil { - return err // internal error in pointer analysis - } //cg.DeleteSyntheticNodes() - a.prog = prog - a.pkgs = pkgs - a.mains = mains - a.result = result + a.prog = prog + a.pkgs = pkgs + a.mainPkg = mainPkg + a.callgraph = graph return nil } @@ -119,10 +158,10 @@ func (a *analysis) OptsSetup() { } func (a *analysis) ProcessListArgs() (e error) { - var groupBy []string - var ignorePaths []string + var groupBy []string + var ignorePaths []string var includePaths []string - var limitPaths []string + var limitPaths []string for _, g := range strings.Split(a.opts.group[0], ",") { g := strings.TrimSpace(g) @@ -165,7 +204,7 @@ func (a *analysis) ProcessListArgs() (e error) { return } -func (a *analysis) OverrideByHTTP(r *http.Request) () { +func (a *analysis) OverrideByHTTP(r *http.Request) { if f := r.FormValue("f"); f == "all" { a.opts.focus = "" } else if f != "" { @@ -235,8 +274,8 @@ func (a *analysis) Render() ([]byte, error) { dot, err := printOutput( a.prog, - a.mains[0].Pkg, - a.result.CallGraph, + a.mainPkg, + a.callgraph, focusPkg, a.opts.limit, a.opts.ignore, diff --git a/main.go b/main.go index 5579fce..1e8e7db 100644 --- a/main.go +++ b/main.go @@ -31,20 +31,22 @@ Flags: ` var ( - focusFlag = flag.String("focus", "main", "Focus specific package using name or import path.") - groupFlag = flag.String("group", "pkg", "Grouping functions by packages and/or types [pkg, type] (separated by comma)") - limitFlag = flag.String("limit", "", "Limit package paths to given prefixes (separated by comma)") - ignoreFlag = flag.String("ignore", "", "Ignore package paths containing given prefixes (separated by comma)") - includeFlag = flag.String("include", "", "Include package paths with given prefixes (separated by comma)") - nostdFlag = flag.Bool("nostd", false, "Omit calls to/from packages in standard library.") - nointerFlag = flag.Bool("nointer", false, "Omit calls to unexported functions.") - testFlag = flag.Bool("tests", false, "Include test code.") - graphvizFlag = flag.Bool("graphviz", false, "Use Graphviz's dot program to render images.") - httpFlag = flag.String("http", ":7878", "HTTP service address.") - skipBrowser = flag.Bool("skipbrowser", false, "Skip opening browser.") - outputFile = flag.String("file", "", "output filename - omit to use server mode") - outputFormat = flag.String("format", "svg", "output file format [svg | png | jpg | ...]") - cacheDir = flag.String("cacheDir", "", "Enable caching to avoid unnecessary re-rendering, you can force rendering by adding 'refresh=true' to the URL query or emptying the cache directory") + focusFlag = flag.String("focus", "main", "Focus specific package using name or import path.") + groupFlag = flag.String("group", "pkg", "Grouping functions by packages and/or types [pkg, type] (separated by comma)") + limitFlag = flag.String("limit", "", "Limit package paths to given prefixes (separated by comma)") + ignoreFlag = flag.String("ignore", "", "Ignore package paths containing given prefixes (separated by comma)") + includeFlag = flag.String("include", "", "Include package paths with given prefixes (separated by comma)") + nostdFlag = flag.Bool("nostd", false, "Omit calls to/from packages in standard library.") + nointerFlag = flag.Bool("nointer", false, "Omit calls to unexported functions.") + testFlag = flag.Bool("tests", false, "Include test code.") + graphvizFlag = flag.Bool("graphviz", false, "Use Graphviz's dot program to render images.") + httpFlag = flag.String("http", ":7878", "HTTP service address.") + skipBrowser = flag.Bool("skipbrowser", false, "Skip opening browser.") + outputFile = flag.String("file", "", "output filename - omit to use server mode") + outputFormat = flag.String("format", "svg", "output file format [svg | png | jpg | ...]") + cacheDir = flag.String("cacheDir", "", "Enable caching to avoid unnecessary re-rendering, you can force rendering by adding 'refresh=true' to the URL query or emptying the cache directory") + callgraphAlgo = flag.String("algo", CallGraphTypePointer, fmt.Sprintf("The algorithm used to construct the call graph. Possible values inlcude: %q, %q, %q, %q", + CallGraphTypeStatic, CallGraphTypeCha, CallGraphTypeRta, CallGraphTypePointer)) debugFlag = flag.Bool("debug", false, "Enable verbose log.") versionFlag = flag.Bool("version", false, "Show version and exit.") @@ -140,7 +142,7 @@ func main() { urlAddr := parseHTTPAddr(httpAddr) Analysis = new(analysis) - if err := Analysis.DoAnalysis("", tests, args); err != nil { + if err := Analysis.DoAnalysis(CallGraphType(*callgraphAlgo), "", tests, args); err != nil { log.Fatal(err) } diff --git a/output.go b/output.go index e2cc1e3..9e44436 100644 --- a/output.go +++ b/output.go @@ -23,7 +23,7 @@ func inStd(node *callgraph.Node) bool { func printOutput( prog *ssa.Program, - mainPkg *types.Package, + mainPkg *ssa.Package, cg *callgraph.Graph, focusPkg *types.Package, limitPaths, @@ -148,7 +148,7 @@ func printOutput( posCaller := prog.Fset.Position(caller.Func.Pos()) posCallee := prog.Fset.Position(callee.Func.Pos()) - posEdge := prog.Fset.Position(edge.Pos()) + posEdge := prog.Fset.Position(edge.Pos()) //fileCaller := fmt.Sprintf("%s:%d", posCaller.Filename, posCaller.Line) filenameCaller := filepath.Base(posCaller.Filename) @@ -208,11 +208,11 @@ func printOutput( var sprintNode = func(node *callgraph.Node, isCaller bool) *dotNode { // only once - key := node.Func.String() + key := node.Func.String() nodeTooltip := "" - fileCaller := fmt.Sprintf("%s:%d", filepath.Base(posCaller.Filename), posCaller.Line) - fileCallee := fmt.Sprintf("%s:%d", filepath.Base(posCallee.Filename), posCallee.Line) + fileCaller := fmt.Sprintf("%s:%d", filepath.Base(posCaller.Filename), posCaller.Line) + fileCallee := fmt.Sprintf("%s:%d", filepath.Base(posCallee.Filename), posCallee.Line) if isCaller { nodeTooltip = fmt.Sprintf("%s | defined in %s", node.Func.String(), fileCaller) @@ -420,18 +420,22 @@ func printOutput( logf("%d/%d edges", len(edges), count) + title := "" + if mainPkg != nil && mainPkg.Pkg != nil { + title = mainPkg.Pkg.Path() + } dot := &dotGraph{ - Title: mainPkg.Path(), + Title: title, Minlen: minlen, Cluster: cluster, Nodes: nodes, Edges: edges, Options: map[string]string{ - "minlen": fmt.Sprint(minlen), - "nodesep": fmt.Sprint(nodesep), + "minlen": fmt.Sprint(minlen), + "nodesep": fmt.Sprint(nodesep), "nodeshape": fmt.Sprint(nodeshape), "nodestyle": fmt.Sprint(nodestyle), - "rankdir": fmt.Sprint(rankdir), + "rankdir": fmt.Sprint(rankdir), }, } From e281d70677325fc99bacef0a591efd255e534a13 Mon Sep 17 00:00:00 2001 From: Xinyu Xie <103246318+xinyubot@users.noreply.github.com> Date: Mon, 8 Aug 2022 05:23:52 +0800 Subject: [PATCH 03/20] update(go.mod): dependency update to support go 1.18 generics (#121) --- go.mod | 17 +++++++++++++---- go.sum | 31 +++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index bb8ebbe..8ebe104 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,18 @@ module github.com/ofabry/go-callvis -go 1.12 +go 1.17 require ( - github.com/goccy/go-graphviz v0.0.6 - github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 - golang.org/x/tools v0.1.9 + github.com/goccy/go-graphviz v0.0.9 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + golang.org/x/tools v0.1.11 +) + +require ( + github.com/fogleman/gg v1.3.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect ) diff --git a/go.sum b/go.sum index aaf23e9..d3d9479 100644 --- a/go.sum +++ b/go.sum @@ -2,27 +2,29 @@ github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4r github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/goccy/go-graphviz v0.0.6 h1:sCT69fmH2KKsObVfsozYyKXxrqmIfo3SyHZs72xkgxs= -github.com/goccy/go-graphviz v0.0.6/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= +github.com/goccy/go-graphviz v0.0.9 h1:s/FMMJ1Joj6La3S5ApO3Jk2cwM4LpXECC2muFx3IPQQ= +github.com/goccy/go-graphviz v0.0.9/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= +golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -30,17 +32,18 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 3b5d49b003b1c4e392c1ef03c7db0962d82e58b0 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Mon, 8 Aug 2022 00:33:22 +0200 Subject: [PATCH 04/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 475c29b..00efe68 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Here you can find descriptions for various types of output. #### Requirements -- [Go](https://golang.org/dl/) 1.13+ +- [Go](https://golang.org/dl/) 1.17+ - [Graphviz](http://www.graphviz.org/download/) (optional, required only with `-graphviz` flag) ### Installation From a410f11381117a36174c476ae6d1eb0971d673e1 Mon Sep 17 00:00:00 2001 From: saberder <48213077+saberder@users.noreply.github.com> Date: Mon, 19 Sep 2022 23:29:21 +0200 Subject: [PATCH 05/20] update(go.mod): dependency update to support go 1.19 (#131) --- go.mod | 4 ++-- go.sum | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 8ebe104..4369b38 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/goccy/go-graphviz v0.0.9 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 - golang.org/x/tools v0.1.11 + golang.org/x/tools v0.1.12 ) require ( @@ -14,5 +14,5 @@ require ( github.com/pkg/errors v0.9.1 // indirect golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect ) diff --git a/go.sum b/go.sum index d3d9479..32ecf77 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,7 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -25,25 +25,24 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8e7cb0681b19ef4411f09aa9aaed76b43adbc05d Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 9 May 2023 01:02:13 +0200 Subject: [PATCH 06/20] Update ci.yml (#148) * Update ci.yml Signed-off-by: Ondrej Fabry * Update ci.yml Signed-off-by: Ondrej Fabry * Update to Go 1.20 and upgrade deps Signed-off-by: Ondrej Fabry * Change to 1.19 in go.mod Signed-off-by: Ondrej Fabry --------- Signed-off-by: Ondrej Fabry Signed-off-by: Ondrej Fabry Co-authored-by: Ondrej Fabry --- .github/workflows/ci.yml | 19 +++++++++---------- go.mod | 12 ++++++------ go.sum | 33 +++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0919e9c..18b7b73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,34 +9,33 @@ on: - cron: '0 */12 * * *' jobs: - build-test: name: "Build & Test" runs-on: ubuntu-latest - strategy: fail-fast: false matrix: - go: [ '1.18', '1.17' ] + go: [ '1.20', '1.19' ] steps: - - uses: actions/checkout@v3 + - name: "Checkout" + uses: actions/checkout@v3 - - name: Setup Go ${{ matrix.go }} + - name: "Setup Go ${{ matrix.go }}" uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - - name: Download Deps - run: go mod download + - name: "Download Deps" + run: go mod download -x - - name: Check go.mod + - name: "Check go.mod" run: | go mod tidy -v git diff --name-only --exit-code go.mod || ( git diff && echo "Run go tidy to update go.mod" && false ) - - name: Build All + - name: "Build" run: make build - - name: Test All + - name: "Test" run: make test diff --git a/go.mod b/go.mod index 4369b38..cafc0b3 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,18 @@ module github.com/ofabry/go-callvis -go 1.17 +go 1.19 require ( - github.com/goccy/go-graphviz v0.0.9 + github.com/goccy/go-graphviz v0.1.1 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 - golang.org/x/tools v0.1.12 + golang.org/x/tools v0.8.0 ) require ( github.com/fogleman/gg v1.3.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/image v0.6.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/sys v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 32ecf77..89dcb03 100644 --- a/go.sum +++ b/go.sum @@ -1,48 +1,53 @@ github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= -github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/goccy/go-graphviz v0.0.9 h1:s/FMMJ1Joj6La3S5ApO3Jk2cwM4LpXECC2muFx3IPQQ= -github.com/goccy/go-graphviz v0.0.9/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= +github.com/goccy/go-graphviz v0.1.1 h1:MGrsnzBxTyt7KG8FhHsFPDTGvF7UaQMmSa6A610DqPg= +github.com/goccy/go-graphviz v0.1.1/go.mod h1:lpnwvVDjskayq84ZxG8tGCPeZX/WxP88W+OJajh+gFk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= -github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= -golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= +golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 7edb24ad040340058bfc38cd25a4a030d15358aa Mon Sep 17 00:00:00 2001 From: Nikolay Dubina Date: Tue, 9 May 2023 07:04:19 +0800 Subject: [PATCH 07/20] Update README.md (#137) Signed-off-by: Nikolay Dubina --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 00efe68..7f6b339 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@

Github release Build status + go-recipes Slack channel

From 7c403339af182fab7593f707cc0723354b915886 Mon Sep 17 00:00:00 2001 From: "Y.Horie" Date: Tue, 9 May 2023 08:07:18 +0900 Subject: [PATCH 08/20] Fix build budge (#155) Signed-off-by: Ondrej Fabry Co-authored-by: Ondrej Fabry --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f6b339..0447786 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Github release - Build status + Build status go-recipes Slack channel

From 79702ceab5a5647da6430f9cc25f949d54611237 Mon Sep 17 00:00:00 2001 From: mikaello <2505178+mikaello@users.noreply.github.com> Date: Tue, 9 May 2023 01:07:56 +0200 Subject: [PATCH 09/20] docs: use 'go install' instead of 'go get' (#146) * docs: use 'go install' instead of 'go get' As of Go 1.17, 'go get' is deprecated, and as of 1.18 'go get' will no longer build packages. 'go install' should be used instead, see https://go.dev/doc/go-get-install-deprecation Signed-off-by: mikaello <2505178+mikaello@users.noreply.github.com> * fix: install latest Signed-off-by: mikaello <2505178+mikaello@users.noreply.github.com> --------- Signed-off-by: mikaello <2505178+mikaello@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0447786..4f01b53 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Here you can find descriptions for various types of output. ### Installation ```sh -go get -u github.com/ofabry/go-callvis +go install github.com/ofabry/go-callvis@latest # or git clone https://github.com/ofabry/go-callvis.git cd go-callvis && make install From 39cbadbbdf0b4ba395f4cfc26454505f41e109e2 Mon Sep 17 00:00:00 2001 From: minhtrex Date: Tue, 9 May 2023 08:09:32 +0900 Subject: [PATCH 10/20] Fix issue go-callvis with build tags (#139) * fix issue go-callvis with -tags * remove un-needed code --- analysis.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/analysis.go b/analysis.go index 7c02067..1ca13d0 100644 --- a/analysis.go +++ b/analysis.go @@ -81,7 +81,7 @@ func (a *analysis) DoAnalysis( Mode: packages.LoadAllSyntax, Tests: tests, Dir: dir, - BuildFlags: build.Default.BuildTags, + BuildFlags: getBuildFlags(), } initial, err := packages.Load(cfg, args...) @@ -375,3 +375,20 @@ func copyFile(src, dst string) (int64, error) { nBytes, err := io.Copy(destination, source) return nBytes, err } + +func getBuildFlags() []string { + buildFlagTags := getBuildFlagTags(build.Default.BuildTags) + if len(buildFlagTags) == 0 { + return nil + } + + return []string{buildFlagTags} +} + +func getBuildFlagTags(buildTags []string) string { + if len(buildTags) > 0 { + return "-tags=" + strings.Join(buildTags, ",") + } + + return "" +} From 92b20e070386c593a5cbc99b4df3bf239420d5b6 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 9 May 2023 01:11:27 +0200 Subject: [PATCH 11/20] Update version.go (#133) Signed-off-by: Ondrej Fabry --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 0ff398a..5206572 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) var ( - version = "v0.6.1" + version = "v0.7.0" commit = "(unknown)" ) From 219da1466b0af15cec684973262c82d61184723c Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Tue, 9 May 2023 01:22:17 +0200 Subject: [PATCH 12/20] Update README.md Signed-off-by: Ondrej Fabry --- README.md | 94 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4f01b53..d8c7068 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,12 @@ the code much higher or when you are just simply trying to understand code of so ### Features -- 🆕 **support for Go modules!** :boom: -- focus specific package in the program - click on package to quickly switch the focus using [interactive viewer](#interactive-viewer) -- group functions by package and/or methods by type +- focus specific package in the program +- group functions by package +- group methods by their receiver type - filter packages to specific import path prefixes -- ignore funcs from standard library +- ignore calls to/from standard library - omit various types of function calls ### Output preview @@ -40,52 +40,34 @@ the code much higher or when you are just simply trying to understand code of so It runs [pointer analysis](https://godoc.org/golang.org/x/tools/go/pointer) to construct the call graph of the program and uses the data to generate output in [dot format](http://www.graphviz.org/content/dot-language), which can be rendered with Graphviz tools. -## Reference guide - -Here you can find descriptions for various types of output. - -### Packages / Types - -|Represents | Style| -|----------: | :-------------| -|`focused` | **blue** color| -|`stdlib` | **green** color| -|`other` | **yellow** color| - -### Functions / Methods - -|Represents | Style| -|-----------: | :--------------| -|`exported` | **bold** border| -|`unexported` | **normal** border| -|`anonymous` | **dotted** border| - -### Calls - -|Represents | Style| -|-----------: | :-------------| -|`internal` | **black** color| -|`external` | **brown** color| -|`static` | **solid** line| -|`dynamic` | **dashed** line| -|`regular` | **simple** arrow| -|`concurrent` | arrow with **circle**| -|`deferred` | arrow with **diamond**| - ## Quick start +### Installation + #### Requirements -- [Go](https://golang.org/dl/) 1.17+ +- [Go](https://golang.org/dl/) 1.19+ - [Graphviz](http://www.graphviz.org/download/) (optional, required only with `-graphviz` flag) -### Installation +To install go-callvis, run: ```sh +# Latest release go install github.com/ofabry/go-callvis@latest -# or + +# Development version +go install github.com/ofabry/go-callvis@master +``` + +Alternatively, clone the repository and compile the source code: + +```sh +# Clone repository git clone https://github.com/ofabry/go-callvis.git -cd go-callvis && make install +cd go-callvis + +# Compile and install +make install ``` ### Usage @@ -152,6 +134,38 @@ Usage of go-callvis: Run `go-callvis -h` to list all supported options. +## Reference guide + +Here you can find descriptions for various types of output. + +### Packages / Types + +|Represents | Style| +|----------: | :-------------| +|`focused` | **blue** color| +|`stdlib` | **green** color| +|`other` | **yellow** color| + +### Functions / Methods + +|Represents | Style| +|-----------: | :--------------| +|`exported` | **bold** border| +|`unexported` | **normal** border| +|`anonymous` | **dotted** border| + +### Calls + +|Represents | Style| +|-----------: | :-------------| +|`internal` | **black** color| +|`external` | **brown** color| +|`static` | **solid** line| +|`dynamic` | **dashed** line| +|`regular` | **simple** arrow| +|`concurrent` | arrow with **circle**| +|`deferred` | arrow with **diamond**| + ## Examples Here is an example for the project [syncthing](https://github.com/syncthing/syncthing). From 73065024adcdc9644cd51d5353a8b489bdd4e223 Mon Sep 17 00:00:00 2001 From: Jie Date: Tue, 31 Dec 2024 00:56:53 +0800 Subject: [PATCH 13/20] fix rta analysis panic (#161) (#198) --- analysis.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/analysis.go b/analysis.go index 1ca13d0..c9494da 100644 --- a/analysis.go +++ b/analysis.go @@ -94,7 +94,11 @@ func (a *analysis) DoAnalysis( } // Create and build SSA-form program representation. - prog, pkgs := ssautil.AllPackages(initial, 0) + mode := ssa.BuilderMode(0) + if algo == CallGraphTypeRta { + mode = ssa.InstantiateGenerics + } + prog, pkgs := ssautil.AllPackages(initial, mode) prog.Build() var graph *callgraph.Graph From 5341ae2ea28a578634746df74dc426fd4a8a40c0 Mon Sep 17 00:00:00 2001 From: Jie Date: Tue, 31 Dec 2024 01:12:04 +0800 Subject: [PATCH 14/20] add package init functions when algo is rta (#199) Co-authored-by: Ondrej Fabry --- analysis.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/analysis.go b/analysis.go index c9494da..66d4b08 100644 --- a/analysis.go +++ b/analysis.go @@ -60,6 +60,26 @@ func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) { return mains, nil } +// initFuncs returns all package init functions +func initFuncs(pkgs []*ssa.Package) ([]*ssa.Function, error) { + var inits []*ssa.Function + for _, p := range pkgs { + if p == nil { + continue + } + for name, member := range p.Members { + fun, ok := member.(*ssa.Function) + if !ok { + continue + } + if name == "init" || strings.HasPrefix(name, "init#") { + inits = append(inits, fun) + } + } + } + return inits, nil +} + //==[ type def/func: analysis ]=============================================== type analysis struct { opts *renderOpts @@ -119,6 +139,15 @@ func (a *analysis) DoAnalysis( for _, main := range mains { roots = append(roots, main.Func("main")) } + + inits, err := initFuncs(prog.AllPackages()) + if err != nil { + return err + } + for _, init := range inits { + roots = append(roots, init) + } + graph = rta.Analyze(roots, true).CallGraph case CallGraphTypePointer: mains, err := mainPackages(prog.AllPackages()) From 25ddacda2a53ba3e044273a25864086397ee05c8 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Wed, 1 Jan 2025 18:03:05 +0100 Subject: [PATCH 15/20] Fix issues with latest Go and generics (#202) * Updated dependencies; removed `pointer` analyzer as it panics in go 1.22; updated docs * Updated pipeline * Alwaysa use InstantiateGenerics mode * Improve margin when using go graphviz for rendering * Fix detecting std packages * Update deps with fixes for go callgraph analysis * Handle synthetic function detection where callee pkg is nil * Add logging for analysis process Signed-off-by: Ondrej Fabry * Update CI workflow Signed-off-by: Ondrej Fabry --------- Signed-off-by: Ondrej Fabry Co-authored-by: Egor Aristov --- .github/workflows/ci.yml | 9 +- .gitignore | 3 + Makefile | 2 +- README.md | 2 + analysis.go | 63 +++++++------- dot.go | 176 +++++++++++++++++++-------------------- dot_cgo.go | 6 +- go.mod | 17 ++-- go.sum | 64 +++++--------- handler.go | 8 +- main.go | 12 ++- output.go | 46 ++++++---- 12 files changed, 200 insertions(+), 208 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18b7b73..cc92e94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,11 +2,10 @@ name: CI on: push: - branches: [ master ] pull_request: branches: [ master ] schedule: - - cron: '0 */12 * * *' + - cron: '0 3 * * *' jobs: build-test: @@ -15,14 +14,14 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.20', '1.19' ] + go: [ 'stable', 'oldstable', '1.21' ] steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Setup Go ${{ matrix.go }}" - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} diff --git a/.gitignore b/.gitignore index f4e7b10..4e82328 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ _testmain.go # Builds /.build/ go-callvis + +# IDE +/.idea/ diff --git a/Makefile b/Makefile index 5240250..d81824e 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' build: ## Build go-callvis - go build -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) + go build -v -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) test: ## Run unit tests go test -tags $(GO_BUILD_TAGS) -ldflags "$(GO_LDFLAGS)" $(GO_BUILD_ARGS) -short -race ./... diff --git a/README.md b/README.md index d8c7068..be41df9 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,8 @@ Usage of go-callvis: a list of build tags to consider satisfied during the build. For more information about build tags, see the description of build constraints in the documentation for the go/build package -tests Include test code. + -algo string + Use specific algorithm for package analyzer: static, cha or rta (default "static") -version Show version and exit. ``` diff --git a/analysis.go b/analysis.go index 66d4b08..86ad102 100644 --- a/analysis.go +++ b/analysis.go @@ -5,19 +5,20 @@ import ( "fmt" "go/build" "go/types" - "golang.org/x/tools/go/callgraph" - "golang.org/x/tools/go/callgraph/cha" - "golang.org/x/tools/go/callgraph/rta" - "golang.org/x/tools/go/callgraph/static" "io" "log" "net/http" "os" "path/filepath" "strings" + "time" + + "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/callgraph/cha" + "golang.org/x/tools/go/callgraph/rta" + "golang.org/x/tools/go/callgraph/static" "golang.org/x/tools/go/packages" - "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" ) @@ -25,13 +26,12 @@ import ( type CallGraphType string const ( - CallGraphTypeStatic CallGraphType = "static" - CallGraphTypeCha = "cha" - CallGraphTypeRta = "rta" - CallGraphTypePointer = "pointer" + CallGraphTypeStatic CallGraphType = "static" + CallGraphTypeCha CallGraphType = "cha" + CallGraphTypeRta CallGraphType = "rta" ) -//==[ type def/func: analysis ]=============================================== +// ==[ type def/func: analysis ]=============================================== type renderOpts struct { cacheDir string focus string @@ -80,7 +80,7 @@ func initFuncs(pkgs []*ssa.Package) ([]*ssa.Function, error) { return inits, nil } -//==[ type def/func: analysis ]=============================================== +// ==[ type def/func: analysis ]=============================================== type analysis struct { opts *renderOpts prog *ssa.Program @@ -97,6 +97,9 @@ func (a *analysis) DoAnalysis( tests bool, args []string, ) error { + logf("begin analysis") + defer logf("analysis done") + cfg := &packages.Config{ Mode: packages.LoadAllSyntax, Tests: tests, @@ -104,23 +107,25 @@ func (a *analysis) DoAnalysis( BuildFlags: getBuildFlags(), } + logf("loading packages") + initial, err := packages.Load(cfg, args...) if err != nil { return err } - if packages.PrintErrors(initial) > 0 { return fmt.Errorf("packages contain errors") } + logf("loaded %d initial packages, building program", len(initial)) + // Create and build SSA-form program representation. - mode := ssa.BuilderMode(0) - if algo == CallGraphTypeRta { - mode = ssa.InstantiateGenerics - } + mode := ssa.InstantiateGenerics prog, pkgs := ssautil.AllPackages(initial, mode) prog.Build() + logf("build done, computing callgraph (algo: %v)", algo) + var graph *callgraph.Graph var mainPkg *ssa.Package @@ -139,7 +144,7 @@ func (a *analysis) DoAnalysis( for _, main := range mains { roots = append(roots, main.Func("main")) } - + inits, err := initFuncs(prog.AllPackages()) if err != nil { return err @@ -149,26 +154,11 @@ func (a *analysis) DoAnalysis( } graph = rta.Analyze(roots, true).CallGraph - case CallGraphTypePointer: - mains, err := mainPackages(prog.AllPackages()) - if err != nil { - return err - } - mainPkg = mains[0] - config := &pointer.Config{ - Mains: mains, - BuildCallGraph: true, - } - ptares, err := pointer.Analyze(config) - if err != nil { - return err - } - graph = ptares.CallGraph default: return fmt.Errorf("invalid call graph type: %s", a.opts.algo) } - //cg.DeleteSyntheticNodes() + logf("callgraph resolved with %d nodes", len(graph.Nodes)) a.prog = prog a.pkgs = pkgs @@ -276,6 +266,9 @@ func (a *analysis) Render() ([]byte, error) { focusPkg *types.Package ) + start := time.Now() + logf("begin rendering") + if a.opts.focus != "" { if ssaPkg = a.prog.ImportedPackage(a.opts.focus); ssaPkg == nil { if strings.Contains(a.opts.focus, "/") { @@ -302,7 +295,7 @@ func (a *analysis) Render() ([]byte, error) { } } focusPkg = ssaPkg.Pkg - logf("focusing: %v", focusPkg.Path()) + logf("focusing package: %v (path: %v)", focusPkg.Name(), focusPkg.Path()) } dot, err := printOutput( @@ -321,6 +314,8 @@ func (a *analysis) Render() ([]byte, error) { return nil, fmt.Errorf("processing failed: %v", err) } + logf("rendering done (took %v sec)", time.Since(start).Round(time.Millisecond).Seconds()) + return dot, nil } diff --git a/dot.go b/dot.go index 43e8da4..87145d7 100644 --- a/dot.go +++ b/dot.go @@ -1,23 +1,23 @@ package main import ( - "bytes" - "fmt" - "io" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" ) var ( - minlen uint - nodesep float64 - nodeshape string - nodestyle string - rankdir string + minlen uint + nodesep float64 + nodeshape string + nodestyle string + rankdir string ) const tmplCluster = `{{define "cluster" -}} @@ -52,7 +52,7 @@ const tmplGraph = `digraph gocallvis { pad="0.0"; nodesep="{{.Options.nodesep}}"; - node [shape="{{.Options.nodeshape}}" style="{{.Options.nodestyle}}" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"]; + node [shape="{{.Options.nodeshape}}" style="{{.Options.nodestyle}}" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.16,0.0"]; edge [minlen="{{.Options.minlen}}"] {{template "cluster" .Cluster}} @@ -63,94 +63,94 @@ const tmplGraph = `digraph gocallvis { } ` -//==[ type def/func: dotCluster ]=============================================== +// ==[ type def/func: dotCluster ]=============================================== type dotCluster struct { - ID string - Clusters map[string]*dotCluster - Nodes []*dotNode - Attrs dotAttrs + ID string + Clusters map[string]*dotCluster + Nodes []*dotNode + Attrs dotAttrs } func NewDotCluster(id string) *dotCluster { - return &dotCluster{ - ID: id, - Clusters: make(map[string]*dotCluster), - Attrs: make(dotAttrs), - } + return &dotCluster{ + ID: id, + Clusters: make(map[string]*dotCluster), + Attrs: make(dotAttrs), + } } func (c *dotCluster) String() string { - return fmt.Sprintf("cluster_%s", c.ID) + return fmt.Sprintf("cluster_%s", c.ID) } -//==[ type def/func: dotNode ]=============================================== +// ==[ type def/func: dotNode ]=============================================== type dotNode struct { - ID string - Attrs dotAttrs + ID string + Attrs dotAttrs } func (n *dotNode) String() string { - return n.ID + return n.ID } -//==[ type def/func: dotEdge ]=============================================== +// ==[ type def/func: dotEdge ]=============================================== type dotEdge struct { - From *dotNode - To *dotNode - Attrs dotAttrs + From *dotNode + To *dotNode + Attrs dotAttrs } -//==[ type def/func: dotAttrs ]=============================================== +// ==[ type def/func: dotAttrs ]=============================================== type dotAttrs map[string]string func (p dotAttrs) List() []string { - l := []string{} - for k, v := range p { - l = append(l, fmt.Sprintf("%s=%q", k, v)) - } - return l + l := []string{} + for k, v := range p { + l = append(l, fmt.Sprintf("%s=%q", k, v)) + } + return l } func (p dotAttrs) String() string { - return strings.Join(p.List(), " ") + return strings.Join(p.List(), " ") } func (p dotAttrs) Lines() string { - return fmt.Sprintf("%s;", strings.Join(p.List(), ";\n")) + return fmt.Sprintf("%s;", strings.Join(p.List(), ";\n")) } -//==[ type def/func: dotGraph ]=============================================== +// ==[ type def/func: dotGraph ]=============================================== type dotGraph struct { - Title string - Minlen uint - Attrs dotAttrs - Cluster *dotCluster - Nodes []*dotNode - Edges []*dotEdge - Options map[string]string + Title string + Minlen uint + Attrs dotAttrs + Cluster *dotCluster + Nodes []*dotNode + Edges []*dotEdge + Options map[string]string } func (g *dotGraph) WriteDot(w io.Writer) error { - t := template.New("dot") - for _, s := range []string{tmplCluster, tmplNode, tmplEdge, tmplGraph} { - if _, err := t.Parse(s); err != nil { - return err - } - } - var buf bytes.Buffer - if err := t.Execute(&buf, g); err != nil { - return err - } - _, err := buf.WriteTo(w) - return err + t := template.New("dot") + for _, s := range []string{tmplCluster, tmplNode, tmplEdge, tmplGraph} { + if _, err := t.Parse(s); err != nil { + return err + } + } + var buf bytes.Buffer + if err := t.Execute(&buf, g); err != nil { + return err + } + _, err := buf.WriteTo(w) + return err } func dotToImage(outfname string, format string, dot []byte) (string, error) { - if *graphvizFlag { - return runDotToImageCallSystemGraphviz(outfname, format, dot) - } + if *graphvizFlag { + return runDotToImageCallSystemGraphviz(outfname, format, dot) + } - return runDotToImage(outfname, format, dot) + return runDotToImage(outfname, format, dot) } // location of dot executable for converting from .dot to .svg @@ -159,26 +159,26 @@ var dotSystemBinary string // runDotToImageCallSystemGraphviz generates a SVG using the 'dot' utility, returning the filepath func runDotToImageCallSystemGraphviz(outfname string, format string, dot []byte) (string, error) { - if dotSystemBinary == "" { - dot, err := exec.LookPath("dot") - if err != nil { - log.Fatalln("unable to find program 'dot', please install it or check your PATH") - } - dotSystemBinary = dot - } - - var img string - if outfname == "" { - img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) - } else { - img = fmt.Sprintf("%s.%s", outfname, format) - } - cmd := exec.Command(dotSystemBinary, fmt.Sprintf("-T%s", format), "-o", img) - cmd.Stdin = bytes.NewReader(dot) - var stderr bytes.Buffer - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) - } - return img, nil + if dotSystemBinary == "" { + dot, err := exec.LookPath("dot") + if err != nil { + log.Fatalln("unable to find program 'dot', please install it or check your PATH") + } + dotSystemBinary = dot + } + + var img string + if outfname == "" { + img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) + } else { + img = fmt.Sprintf("%s.%s", outfname, format) + } + cmd := exec.Command(dotSystemBinary, fmt.Sprintf("-T%s", format), "-o", img) + cmd.Stdin = bytes.NewReader(dot) + var stderr bytes.Buffer + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) + } + return img, nil } diff --git a/dot_cgo.go b/dot_cgo.go index a93435e..4821bdc 100644 --- a/dot_cgo.go +++ b/dot_cgo.go @@ -20,9 +20,11 @@ func runDotToImage(outfname string, format string, dot []byte) (string, error) { } defer func() { if err := graph.Close(); err != nil { - log.Fatal(err) + log.Printf("error closing graph: %v", err) + } + if err := g.Close(); err != nil { + log.Printf("error closing graphviz: %v", err) } - g.Close() }() var img string if outfname == "" { diff --git a/go.mod b/go.mod index cafc0b3..d8784c8 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,21 @@ module github.com/ofabry/go-callvis -go 1.19 +go 1.22.0 + +toolchain go1.23.1 require ( - github.com/goccy/go-graphviz v0.1.1 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 - golang.org/x/tools v0.8.0 + github.com/goccy/go-graphviz v0.1.2 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + golang.org/x/tools v0.28.0 ) require ( github.com/fogleman/gg v1.3.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/image v0.6.0 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 89dcb03..84bfe8b 100644 --- a/go.sum +++ b/go.sum @@ -1,53 +1,27 @@ github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= +github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/goccy/go-graphviz v0.1.1 h1:MGrsnzBxTyt7KG8FhHsFPDTGvF7UaQMmSa6A610DqPg= -github.com/goccy/go-graphviz v0.1.1/go.mod h1:lpnwvVDjskayq84ZxG8tGCPeZX/WxP88W+OJajh+gFk= +github.com/goccy/go-graphviz v0.1.2 h1:sWSJ6w13BCm/ZOUTHDVrdvbsxqN8yyzaFcHrH/hQ9Yg= +github.com/goccy/go-graphviz v0.1.2/go.mod h1:pMYpbAqJT10V8dzV1JN/g/wUlG/0imKPzn3ZsrchGCI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= -golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= diff --git a/handler.go b/handler.go index 70975d8..948920c 100644 --- a/handler.go +++ b/handler.go @@ -25,7 +25,7 @@ func handler(w http.ResponseWriter, r *http.Request) { var img string if img = Analysis.FindCachedImg(); img != "" { - log.Println("serving file:", img) + log.Println("serving cached file:", img) http.ServeFile(w, r, img) return } @@ -38,17 +38,17 @@ func handler(w http.ResponseWriter, r *http.Request) { output, err := Analysis.Render() if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("rendering failed: %v", err.Error()), http.StatusInternalServerError) return } if r.Form.Get("format") == "dot" { - log.Println("writing dot output..") + log.Println("writing dot output") fmt.Fprint(w, string(output)) return } - log.Printf("converting dot to %s..\n", *outputFormat) + log.Printf("converting dot to %s\n", *outputFormat) img, err = dotToImage("", *outputFormat, output) if err != nil { diff --git a/main.go b/main.go index 1e8e7db..92fe911 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,10 @@ // go-callvis: a tool to help visualize the call graph of a Go program. -// package main import ( "flag" "fmt" "go/build" - "io/ioutil" "log" "net" "net/http" @@ -45,8 +43,8 @@ var ( outputFile = flag.String("file", "", "output filename - omit to use server mode") outputFormat = flag.String("format", "svg", "output file format [svg | png | jpg | ...]") cacheDir = flag.String("cacheDir", "", "Enable caching to avoid unnecessary re-rendering, you can force rendering by adding 'refresh=true' to the URL query or emptying the cache directory") - callgraphAlgo = flag.String("algo", CallGraphTypePointer, fmt.Sprintf("The algorithm used to construct the call graph. Possible values inlcude: %q, %q, %q, %q", - CallGraphTypeStatic, CallGraphTypeCha, CallGraphTypeRta, CallGraphTypePointer)) + callgraphAlgo = flag.String("algo", string(CallGraphTypeStatic), fmt.Sprintf("The algorithm used to construct the call graph. Possible values inlcude: %q, %q, %q", + CallGraphTypeStatic, CallGraphTypeCha, CallGraphTypeRta)) debugFlag = flag.Bool("debug", false, "Enable verbose log.") versionFlag = flag.Bool("version", false, "Show version and exit.") @@ -103,14 +101,14 @@ func outputDot(fname string, outputFormat string) { log.Fatalf("%v\n", err) } - log.Println("writing dot output..") + log.Println("writing dot output") - writeErr := ioutil.WriteFile(fmt.Sprintf("%s.gv", fname), output, 0755) + writeErr := os.WriteFile(fmt.Sprintf("%s.gv", fname), output, 0755) if writeErr != nil { log.Fatalf("%v\n", writeErr) } - log.Printf("converting dot to %s..\n", outputFormat) + log.Printf("converting dot to %s\n", outputFormat) _, err = dotToImage(fname, outputFormat, output) if err != nil { diff --git a/output.go b/output.go index 9e44436..579a874 100644 --- a/output.go +++ b/output.go @@ -13,12 +13,22 @@ import ( ) func isSynthetic(edge *callgraph.Edge) bool { - return edge.Caller.Func.Pkg == nil || edge.Callee.Func.Synthetic != "" + // TODO: consider handling callee.Func.Pkg == nil + // this could still generate a node for the call, might be useful + return edge.Caller.Func.Pkg == nil || edge.Callee.Func.Pkg == nil || edge.Callee.Func.Synthetic != "" } func inStd(node *callgraph.Node) bool { - pkg, _ := build.Import(node.Func.Pkg.Pkg.Path(), "", 0) - return pkg.Goroot + //pkg, _ := build.Import(node.Func.Pkg.Pkg.Path(), "", 0) + //return pkg.Goroot + return isStdPkgPath(node.Func.Pkg.Pkg.Path()) +} + +func isStdPkgPath(path string) bool { + if strings.Contains(path, ".") { + return false + } + return true } func printOutput( @@ -33,6 +43,10 @@ func printOutput( nostd, nointer bool, ) ([]byte, error) { + + logf("printing output for: %v", focusPkg) + logf("src dirs: %+v, default build context: %+v", build.Default.SrcDirs(), build.Default) + var groupType, groupPkg bool for _, g := range groupBy { switch g { @@ -78,17 +92,15 @@ func printOutput( return true } fromFocused := false - toFocused := false for _, e := range caller.In { - if !isSynthetic(e) && focusPkg != nil && - e.Caller.Func.Pkg.Pkg.Path() == focusPkg.Path() { + if !isSynthetic(e) && focusPkg != nil && e.Caller.Func.Pkg.Pkg.Path() == focusPkg.Path() { fromFocused = true break } } + toFocused := false for _, e := range callee.Out { - if !isSynthetic(e) && focusPkg != nil && - e.Callee.Func.Pkg.Pkg.Path() == focusPkg.Path() { + if !isSynthetic(e) && focusPkg != nil && e.Callee.Func.Pkg.Pkg.Path() == focusPkg.Path() { toFocused = true break } @@ -157,6 +169,8 @@ func printOutput( return nil } + //logf(" - %s -> %s (%s -> %s) %v\n", caller.Func.Pkg, callee.Func.Pkg, caller, callee, filenameCaller) + callerPkg := caller.Func.Pkg.Pkg calleePkg := callee.Func.Pkg.Pkg @@ -167,8 +181,7 @@ func printOutput( } // omit std - if nostd && - (inStd(caller) || inStd(callee)) { + if nostd && (inStd(caller) || inStd(callee)) { return nil } @@ -244,11 +257,13 @@ func printOutput( label = parts[len(parts)-1] } - pkg, _ := build.Import(node.Func.Pkg.Pkg.Path(), "", 0) + pkgPath := node.Func.Pkg.Pkg.Path() + isStdPkg := isStdPkgPath(pkgPath) + // set node color if isFocused { attrs["fillcolor"] = "lightblue" - } else if pkg.Goroot { + } else if isStdPkg { attrs["fillcolor"] = "#adedad" } else { attrs["fillcolor"] = "moccasin" @@ -275,7 +290,7 @@ func printOutput( // group by pkg if groupPkg && !isFocused { label := node.Func.Pkg.Pkg.Name() - if pkg.Goroot { + if isStdPkg { label = node.Func.Pkg.Pkg.Path() } key := node.Func.Pkg.Pkg.Path() @@ -295,7 +310,7 @@ func printOutput( "rank": "sink", }, } - if pkg.Goroot { + if isStdPkg { c.Clusters[key].Attrs["fillcolor"] = "#E0FFE1" } } @@ -323,7 +338,7 @@ func printOutput( } if isFocused { c.Clusters[key].Attrs["fillcolor"] = "lightsteelblue" - } else if pkg.Goroot { + } else if isStdPkg { c.Clusters[key].Attrs["fillcolor"] = "#c2e3c2" } } @@ -418,6 +433,7 @@ func printOutput( edges = append(edges, e) } + logf("%d/%d nodes", len(nodeMap), len(cg.Nodes)) logf("%d/%d edges", len(edges), count) title := "" From 2c9801f653e562d10c9d96b77a6e27beb9263d9d Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 2 Jan 2025 12:41:02 +0100 Subject: [PATCH 16/20] Bump version to v0.7.1 Signed-off-by: Ondrej Fabry --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 5206572..07e7677 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) var ( - version = "v0.7.0" + version = "v0.7.1" commit = "(unknown)" ) From 14a1decccc2645ff03df4615a2fc07850685fc89 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 2 Jan 2025 15:52:37 +0100 Subject: [PATCH 17/20] Update FUNDING.yml Signed-off-by: Ondrej Fabry --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index fbe664a..3139905 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -patreon: ofabry +patreon: ondrajz open_collective: go-callvis # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] From f9165ca5ba5c76c130296ef3283ad876eee88a6d Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 2 Jan 2025 15:59:09 +0100 Subject: [PATCH 18/20] Update README.md Signed-off-by: Ondrej Fabry --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be41df9..b8e816c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

go-callvis

- Github release + Github release Build status go-recipes Slack channel From d142bcf80860c9321f6d1041526c4626d932a2c6 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 2 Jan 2025 16:04:26 +0100 Subject: [PATCH 19/20] Update golang.org/x/image to v0.18.0 Signed-off-by: Ondrej Fabry --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d8784c8..55e664b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/fogleman/gg v1.3.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/image v0.15.0 // indirect + golang.org/x/image v0.18.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 84bfe8b..008c29d 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= From fb6b442a41444f756c4444c9bc15669d5e75e4e7 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 2 Jan 2025 16:31:22 +0100 Subject: [PATCH 20/20] Update README.md Signed-off-by: Ondrej Fabry --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b8e816c..271179b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@

go-callvis

- Github release - Build status + Github release + Build status go-recipes Slack channel

@@ -31,7 +31,7 @@ the code much higher or when you are just simply trying to understand code of so ### Output preview -[![main](images/main.png)](https://raw.githubusercontent.com/ofabry/go-callvis/master/images/main.png) +[![main](images/main.png)](images/main.png) > Check out the [source code](examples/main) for the above image. @@ -172,7 +172,7 @@ Here you can find descriptions for various types of output. Here is an example for the project [syncthing](https://github.com/syncthing/syncthing). -[![syncthing example](images/syncthing.png)](https://raw.githubusercontent.com/ofabry/go-callvis/master/images/syncthing.png) +[![syncthing example](images/syncthing.png)](https://raw.githubusercontent.com/ondrajz/go-callvis/master/images/syncthing.png) > Check out [more examples](examples) and used command options. @@ -183,15 +183,15 @@ Join [#go-callvis](https://gophers.slack.com/archives/go-callvis) channel at [go ### How to help Did you find any bugs or have some suggestions? -- Feel free to open [new issue](https://github.com/ofabry/go-callvis/issues/new) or start discussion in the slack channel. +- Feel free to open [new issue](https://github.com/ondrajz/go-callvis/issues/new) or start discussion in the slack channel. Do you want to contribute to the project? -- Fork the repository and open a pull request. [Here](https://github.com/ofabry/go-callvis/projects/1) you can find TODO features. +- Fork the repository and open a pull request. [Here](https://github.com/ondrajz/go-callvis/projects/1) you can find TODO features. --- #### Roadmap -##### The *interactive tool* described below has been published as a *separate project* called [goexplorer](https://github.com/ofabry/goexplorer)! +##### The *interactive tool* described below has been published as a *separate project* called [goexplorer](https://github.com/ondrajz/goexplorer)! > Ideal goal of this project is to make web app that would locally store the call graph data and then provide quick access of the call graphs for any package of your dependency tree. At first it would show an interactive map of overall dependencies between packages and then by selecting particular package it would show the call graph and provide various options to alter the output dynamically.