blob: 44d47ad198aa56efad5e4136ea6129a6549b2ff2 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
The deadcode command reports unreachable functions in Go programs.
Usage: deadcode [flags] package...
The deadcode command loads a Go program from source then uses Rapid
Type Analysis (RTA) to build a call graph of all the functions
reachable from the program's main function. Any functions that are not
reachable are reported as dead code, grouped by package.
Packages are expressed in the notation of 'go list' (or other
underlying build system if you are using an alternative
golang.org/x/go/packages driver). Only executable (main) packages are
considered starting points for the analysis.
The -test flag causes it to analyze test executables too. Tests
sometimes make use of functions that would otherwise appear to be dead
code, and public API functions reported as dead with -test indicate
possible gaps in your test coverage. Bear in mind that an Example test
function without an "Output:" comment is merely documentation:
it is dead code, and does not contribute coverage.
The -filter flag restricts results to packages that match the provided
regular expression; its default value is the module name of the first
package. Use -filter= to display all results.
Example: show all dead code within the gopls module:
$ deadcode -test golang.org/x/tools/gopls/...
The analysis can soundly analyze dynamic calls though func values,
interface methods, and reflection. However, it does not currently
understand the aliasing created by //go:linkname directives, so it
will fail to recognize that calls to a linkname-annotated function
with no body in fact dispatch to the function named in the annotation.
This may result in the latter function being spuriously reported as dead.
By default, the tool does not report dead functions in generated files,
as determined by the special comment described in
https://go.dev/s/generatedcode. Use the -generated flag to include them.
In any case, just because a function is reported as dead does not mean
it is unconditionally safe to delete it. For example, a dead function
may be referenced by another dead function, and a dead method may be
required to satisfy an interface that is never called.
Some judgement is required.
The analysis is valid only for a single GOOS/GOARCH/-tags configuration,
so a function reported as dead may be live in a different configuration.
Consider running the tool once for each configuration of interest.
Consider using a line-oriented output format (see below) to make it
easier to compute the intersection of results across all runs.
# Output
The command supports three output formats.
With no flags, the command prints the name and location of each dead
function in the form of a typical compiler diagnostic, for example:
$ deadcode -f='{{range .Funcs}}{{println .Position}}{{end}}' -test ./gopls/...
gopls/internal/lsp/command.go:1206:6: unreachable func: openClientEditor
gopls/internal/lsp/template/parse.go:414:18: unreachable func: Parsed.WriteNode
gopls/internal/lsp/template/parse.go:419:18: unreachable func: wrNode.writeNode
With the -json flag, the command prints an array of Package
objects, as defined by the JSON schema (see below).
With the -f=template flag, the command executes the specified template
on each Package record. So, this template shows dead functions grouped
by package:
$ deadcode -f='{{println .Path}}{{range .Funcs}}{{printf "\t%s\n" .Name}}{{end}}{{println}}' -test ./gopls/...
golang.org/x/tools/gopls/internal/lsp
openClientEditor
golang.org/x/tools/gopls/internal/lsp/template
Parsed.WriteNode
wrNode.writeNode
# Why is a function not dead?
The -whylive=function flag explain why the named function is not dead
by showing an arbitrary shortest path to it from one of the main functions.
(To enumerate the functions in a program, or for more sophisticated
call graph queries, use golang.org/x/tools/cmd/callgraph.)
Fully static call paths are preferred over paths involving dynamic
edges, even if longer. Paths starting from a non-test package are
preferred over those from tests. Paths from main functions are
preferred over paths from init functions.
The result is a list of Edge objects (see JSON schema below).
Again, the -json and -f=template flags may be used to control
the formatting of the list of Edge objects.
The default format shows, for each edge in the path, whether the call
is static or dynamic, and its source line number. For example:
$ deadcode -whylive=bytes.Buffer.String -test ./internal/cmd/deadcode/...
golang.org/x/tools/internal/cmd/deadcode.main
static@L0117 --> golang.org/x/tools/go/packages.Load
static@L0262 --> golang.org/x/tools/go/packages.defaultDriver
static@L0305 --> golang.org/x/tools/go/packages.goListDriver
static@L0153 --> golang.org/x/tools/go/packages.goListDriver$1
static@L0154 --> golang.org/x/tools/go/internal/packagesdriver.GetSizesForArgsGolist
static@L0044 --> bytes.Buffer.String
# JSON schema
type Package struct {
Name string // declared name
Path string // full import path
Funcs []Function // list of dead functions within it
}
type Function struct {
Name string // name (sans package qualifier)
Position Position // file/line/column of function declaration
Generated bool // function is declared in a generated .go file
}
type Edge struct {
Initial string // initial entrypoint (main or init); first edge only
Kind string // = static | dynamic
Position Position // file/line/column of call site
Callee string // target of the call
}
type Position struct {
File string // name of file
Line, Col int // line and byte index, both 1-based
}
*/
package main