Skip to content

x/tools/cmd/deadcode: Deadcode not reported with different argument order in workspace #73652

New issue

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

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

Already on GitHub? Sign in to your account

Open
nicholascapo opened this issue May 9, 2025 · 3 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. ToolProposal Issues describing a requested change to a Go tool or command-line program. Tools This label describes issues relating to any tools in the x/tools repository.
Milestone

Comments

@nicholascapo
Copy link

nicholascapo commented May 9, 2025

Go version

go version go1.24.2 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/nicholas/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/nicholas/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3441140769=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/nicholas/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/nicholas/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/lib/go-1.24'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/home/nicholas/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go-1.24/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK='/home/nicholas/reproduction/go.work'
PKG_CONFIG='pkg-config'

What did you do?

Argument order can hide some dead code

$ go run golang.org/x/tools/cmd/[email protected] ./svc/... ./lib/...

$ go run golang.org/x/tools/cmd/[email protected] ./lib/... ./svc/...
lib/a/a.go:3:6: unreachable func: A

Real Life example

At $work we use a private monorepo with several services, along with library packages that are shared across services.
Each service may contain several other packages that are not shared with other services.
In addition we has a "tools" module that doesn't import much from the library.

Each service is a module, tools is a module, and the library is a module.
The root of the repo is a workspace.

Linter

We have an internal linter that runs deadcode and allows us to ignore certain dead functions (etc) based on //nolint:deadcode comments.
So we expect some deadcode to be reported as dead, and then our linter can ignore it.
The linter also reports when code with a //nolint:deadcode comment was not reported as dead (that way we can remove the comment).
This works pretty well, but isn't actually relevant to the issue.

Problem

It was discovered that running deadcode across all the modules sometimes produces a much smaller report than other times.
This appears to happen only in specific circumstances, but it is reliable to reproduce.

Additionally, we were able to reproduce it with some package names, but not others.
In one specific case, adding tools/a/a.go with a single package a line (no added dead code), causes deadcode to fail to report dead code (from another module).
But moving that same file to tools/x/x.go does not trigger the bug.

While debugging, we discovered that the order of package arguments to deadcode reliably causes it to fail.
The difference is remarkable too: Depending on the order of the arguments, deadcode will report either one or several thousand lines.

Minimal Reproduction

Setup
# create a base directory
mkdir reproduction
cd reproduction

# make library
mkdir lib
cd lib
go mod init lib

# library source
mkdir a
echo 'package a \n\n func A(){}\n' > a/a.go

cd ..

# make service
mkdir svc
cd svc
go mod init svc

# service source
mkdir s
echo 'package main \n\n func main(){ println("main") }\n' > s/main.go

cd ..

# make workspace
go work init
go work use -r .

# build (to confirm correctness)
go build -v ./lib/... ./svc/...
$ tree
.
├── go.work
├── lib
│   ├── a
│   │   └── a.go
│   └── go.mod
└── svc
    ├── go.mod
    └── s
        └── main.go

This is an example workspace with a service and library module, which each contain a single package.
The only main() is in svc/s/main.go and it does not import lib/a.
Therefore lib/a.A() is unused and dead.

But this is only found based on the order of the arguments to deadcode:

$ go run golang.org/x/tools/cmd/[email protected] ./svc/... ./lib/...

$ go run golang.org/x/tools/cmd/[email protected] ./lib/... ./svc/...
lib/a/a.go:3:6: unreachable func: A

What did you see happen?

Based on argument order, in a workspace, deadcode may fail to report dead code

What did you expect to see?

Reporting of dead code is consistent even with different argument orders

@gopherbot gopherbot added the Tools This label describes issues relating to any tools in the x/tools repository. label May 9, 2025
@gopherbot gopherbot added this to the Unreleased milestone May 9, 2025
@cherrymui cherrymui added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label May 9, 2025
@cherrymui
Copy link
Member

cc @adonovan

@gabyhelp gabyhelp added the ToolProposal Issues describing a requested change to a Go tool or command-line program. label May 9, 2025
@xieyuschen
Copy link
Member

xieyuschen commented May 10, 2025

Hi @nicholascapo , thanks for reporting the issue, but I believe you have missed the filter flag behavior. To solve your issue, you can specify -filter=(report all unused cases), or wite your own reg expr to filter out the packages you're interested in, see deadcode doc

For example:

$  go run golang.org/x/tools/cmd/[email protected] -filter= ./svc/... ./lib/...
$  go run golang.org/x/tools/cmd/[email protected] -filter=^lib ./svc/... ./lib/...

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.

@adonovan , I believe we may add a support for go.work support, as in this scenario, the module name of the first package cannot covermultiple go modules under the workspace. We need to make the filterFlags regex supports all modules under a go workspace. hdyt?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. ToolProposal Issues describing a requested change to a Go tool or command-line program. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

5 participants