Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

x/tools/cmd/deadcode: add new command #63501

Closed
adonovan opened this issue Oct 11, 2023 · 22 comments
Closed

x/tools/cmd/deadcode: add new command #63501

adonovan opened this issue Oct 11, 2023 · 22 comments
Assignees
Labels
Analysis Issues related to static analysis (vet, x/tools/go/analysis) Proposal Proposal-Accepted
Milestone

Comments

@adonovan
Copy link
Member

adonovan commented Oct 11, 2023

The golang.org/x/tools/internal/cmd/deadcode command reports dead code, that is, functions not reachable by a sequence of calls from main, even through reflection, based on an RTA-based call graph. We have found it useful for identifying orphaned bits of code in x/tools and we imagine others might find it useful too. We propose to publish it at cmd/deadcode in the same repo.

This is an example of the tool in action, showing functions in x/tools not reachable from gopls' main or any of its tests, and suppressing functions in generated source files.

xtools$ deadcode -test ./gopls/...
golang.org/x/tools/gopls/internal/lsp
	openClientEditor

golang.org/x/tools/gopls/internal/lsp/cache
	(fileLoadScope).aScope
	(packageLoadScope).aScope
	(moduleLoadScope).aScope
	(viewLoadScope).aScope

golang.org/x/tools/gopls/internal/lsp/debug/log
	(Level).Log
	(Level).Logf

golang.org/x/tools/gopls/internal/lsp/filecache
	SetBudget

golang.org/x/tools/gopls/internal/lsp/regtest
	(State).outstandingWork

golang.org/x/tools/gopls/internal/lsp/source
	WidestPackageForFile

golang.org/x/tools/gopls/internal/lsp/template
	(*Parsed).WriteNode
	(wrNode).writeNode

xtools$ deadcode -test -json ./gopls/...
[
	{
		"Path": "golang.org/x/tools/gopls/internal/lsp",
		"Funcs": [
			{
				"Name": "golang.org/x/tools/gopls/internal/lsp.openClientEditor",
				"RelName": "openClientEditor",
				"Posn": "/Users/adonovan/w/xtools/gopls/internal/lsp/command.go:1178:6",
				"Generated": false
			}
		]
	},
...
@gopherbot gopherbot added this to the Proposal milestone Oct 11, 2023
@adonovan adonovan added the Analysis Issues related to static analysis (vet, x/tools/go/analysis) label Oct 11, 2023
@earthboundkid
Copy link
Contributor

Cf @bep's https://github.com/bep/punused, which I have used to check for dead code before. That one can have false positive though.

@adonovan
Copy link
Member Author

I'd particularly interested in feedback on how we could improve the user interface to make it clearer. For example, the first command I ran to produce the example above used just ./gopls, so it did not include tests from subpackages, causing it to report some functions that are only used from those tests. Perhaps better defaults would make such mistakes less likely.

Please try it out and let us know what you find.

@earthboundkid
Copy link
Contributor

-generated=false is awkward. -exclude-generated would be less awkward.

@adonovan adonovan self-assigned this Oct 21, 2023
@hdonnay
Copy link

hdonnay commented Oct 26, 2023

One thing I noticed is that it needs some logic around Example* functions in tests.

@adonovan
Copy link
Member Author

it needs some logic around Example* functions in tests.

Logic to do what? The help message says:

Bear in mind that an Example test
function without an "Output:" comment is merely documentation:
it is dead code, and does not contribute coverage.

Are you suggesting that it should treat non-executed Example functions as somehow reachable? I think its current behavior is more sensible. It's also unlikely that anyone would casually delete an Example based on the report produced by this tool.

@rsc rsc changed the title proposal: publish x/tools/cmd/deadcode proposal: x/tools/cmd/deadcode: add new command Oct 27, 2023
@rsc
Copy link
Contributor

rsc commented Oct 27, 2023

Why does the output print package "path"? Why not just print path, or perhaps like the go command # path?
package "path" is weird because that's not the syntax of a package declaration.

@rsc
Copy link
Contributor

rsc commented Oct 27, 2023

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@hdonnay
Copy link

hdonnay commented Oct 27, 2023

Logic to do what? The help message says:

Bear in mind that an Example test
function without an "Output:" comment is merely documentation:
it is dead code, and does not contribute coverage.

Are you suggesting that it should treat non-executed Example functions as somehow reachable? I think its current behavior is more sensible. It's also unlikely that anyone would casually delete an Example based on the report produced by this tool.

I can see the logic in that, but having the tool not recognize functions that are meaningful to the standard go tooling feels like friction for most users. The invocation is going to become deadcode . | grep -v Example for users to remove the technically-correct-but-not-useful output.

@gopherbot
Copy link

Change https://go.dev/cl/539661 mentions this issue: internal/cmd/deadcode: support -json, -format=template

@findleyr
Copy link
Contributor

findleyr commented Nov 6, 2023

-generated=false is awkward. -exclude-generated would be less awkward.

I think this depends on the default behavior. If generated files are excluded by default, then -generated=true or -include_generated seems clearer than the doubly negative -exclude_generated=false.

And based on my experience with this command, I think generated files should be excluded by default. I almost always want to exclude generated code, and commands should err on the side of less noise.

gopherbot pushed a commit to golang/tools that referenced this issue Nov 6, 2023
This change adds support for JSON output and text/template
formatting of output records, in the manner of 'go list (-f|-json)'.
Plus test.

Also, the -generated flag is now enabled by default,
and it affects all output modes.  Plus test.

Updates golang/go#63501

Change-Id: I1374abad78d800f92739de5c75b28e6e5189caa1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/539661
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
@gopherbot
Copy link

Change https://go.dev/cl/540218 mentions this issue: internal/cmd/deadcode: omit package/func keywords in default output

gopherbot pushed a commit to golang/tools that referenced this issue Nov 7, 2023
...and don't quote the package path.

Updates golang/go#63501

Change-Id: Ic4f52bf74d7ddd185f2179deed55118971bfa7ed
Reviewed-on: https://go-review.googlesource.com/c/tools/+/540218
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
@rsc
Copy link
Contributor

rsc commented Nov 10, 2023

One small note about the output: the parens in (*Parsed).WriteNode are both pedantic and harmful.
Pedantic because there is no potential for confusion, since there is only one actual method definition in the source code, so distinguishing pointer vs non-pointer receiver is unnecessary.
Harmful because the result is different syntax from what go doc uses,
so you can't paste the output into a go doc command line.
This should be printed as plain 'Parsed.WriteNode'.
Same thing for (Level).Log: just Level.Log.

@rsc
Copy link
Contributor

rsc commented Nov 10, 2023

In the JSON, the fields "RelName" and "Posn" seem to be new spellings for Go. Are they necessary?
Posn in particular seems like it should be Pos or Position, following our usual spellings.
And maybe Name/RelName should be ID/Name?

@rsc
Copy link
Contributor

rsc commented Nov 10, 2023

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

See the top comment.

@adonovan
Copy link
Member Author

Pedantic because there is no potential for confusion, since there is only one actual method definition in the source code, so distinguishing pointer vs non-pointer receiver is unnecessary.

Fair enough. (These names from go/ssa, which must be able to distinguish (T).f from (*T).f.)

In the JSON, the fields "RelName" and "Posn" seem to be new spellings for Go. Are they necessary?

Perhaps not: once we've simplified (*T).f to T.f then package-qualifying it is as simple as pkg.T.f. Nice.

@gopherbot
Copy link

Change https://go.dev/cl/541238 mentions this issue: internal/cmd/deadcode: simplify (*T).f -> T.f in output

gopherbot pushed a commit to golang/tools that referenced this issue Nov 13, 2023
This change:
- uses the simpler notation pkg.T.f instead of (*pkg.T).f
  in all output, by using a simplified fork of ssa.Function.String
  that only works on source functions (and package inits).
- uses callgraph.Graph.DeleteSyntheticNodes to elide wrappers
  that aren't interesting to the user.
- simplifies the JSON schema now that it's trivial to
  package-qualify any function by prepending "pkg.".
- uses better spellings for JSON fields.

Also, tests of new behavior.

Updates golang/go#63501

Change-Id: Ie57399c4e05a882e294437b53317eb0c1b322e9f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/541238
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Commit-Queue: Alan Donovan <adonovan@google.com>
@rsc
Copy link
Contributor

rsc commented Nov 14, 2023

Sounds like RelName can be deleted. Posn should be kept but changed to Pos or Position.

@rsc
Copy link
Contributor

rsc commented Nov 14, 2023

I'm still not sure about the semi-structured default output. It is easy to fall back on "well it doesn't matter, programs will use json", but really the default output should be as usable for people as we can make it, including people typing at a Unix prompt. The current output is not greppable, for example. Also, I think it would make sense to think of deadcode as like vet in that it flags code that the user should take a look at. That strongly suggests the position belongs in the default output, so that it is easy to navigate to that code. So the output should probably be individual lines, not stateful sections, and each line should begin with the position:

file:line: pkg.name

And the file:line should default to relative to the current directory, like go vet and go build do. With the file:line, the full package path is probably unnecessary:

$ deadcode ./...
internal/lsp/command.go:1178:6: unused func: openClientEditor 
...

The "unused func:" is important because deadcode might also be printing ordinary build errors.
It also leaves the door open to "unused var" in the future (or maybe that's already there?).

@gopherbot
Copy link

Change https://go.dev/cl/542375 mentions this issue: internal/cmd/deadcode: use compiler-style diagnostics by default

@adonovan
Copy link
Member Author

adonovan commented Nov 14, 2023

Sounds like RelName can be deleted. Posn should be kept but changed to Pos or Position.

Yep, that was done in CL 541238.

I'm still not sure about the semi-structured default output.

OK, done in 542375. I chose "unreachable" over "unused" because it's a property of the call graph, not the defs/uses graph.

$ go run ./internal/cmd/deadcode -test ./gopls/...
gopls/internal/lsp/command.go:1206:6: unreachable func: openClientEditor
gopls/internal/lsp/cache/pkg.go:100:22: unreachable func: fileLoadScope.aScope
...

The old format is now just an example -f template in the documentation.

gopherbot pushed a commit to golang/tools that referenced this issue Nov 15, 2023
The previous format is now relegated to an example -f template
in the documentation.

Updates golang/go#63501

Change-Id: I29548121431282edbda07e3a75c131ac33104a6f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/542375
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
@rsc
Copy link
Contributor

rsc commented Dec 4, 2023

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

See the top comment.

@rsc rsc changed the title proposal: x/tools/cmd/deadcode: add new command x/tools/cmd/deadcode: add new command Dec 4, 2023
@rsc rsc modified the milestones: Proposal, Backlog Dec 4, 2023
@gopherbot
Copy link

Change https://go.dev/cl/547035 mentions this issue: cmd/deadcode: publish internal/cmd/deadcode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Analysis Issues related to static analysis (vet, x/tools/go/analysis) Proposal Proposal-Accepted
Projects
Status: Accepted
Development

No branches or pull requests

6 participants