Discovering unreachable capabilities with deadcode – The Go Programming Language

The Go Blog

Alan Donovan
12 December 2023

Features which are a part of your challenge’s supply code however can by no means be
reached in any execution are referred to as “lifeless code”, they usually exert a drag
on codebase upkeep efforts.
As we speak we’re happy to share a instrument named deadcode that can assist you determine them.

$ go set up golang.org/x/instruments/cmd/deadcode@newest
$ deadcode -help
The deadcode command experiences unreachable capabilities in Go applications.

Utilization: deadcode [flags] bundle...

Instance

Over the past 12 months or so, we’ve been making a number of adjustments to the
construction of gopls, the
language server for Go that powers VS Code and different editors.
A typical change would possibly rewrite some current operate, taking care to
make sure that its new habits satisfies the wants of all current callers.
Generally, after placing in all that effort, we might uncover to our
frustration that one of many callers was by no means truly reached in any
execution, so it may safely have been been deleted.
If we had recognized this beforehand our refactoring activity would have been
simpler.

The straightforward Go program under illustrates the issue:

module instance.com/greet
go 1.21
bundle foremost

import "fmt"

func foremost() {
    var g Greeter
    g = Helloer{}
    g.Greet()
}

sort Greeter interface{ Greet() }

sort Helloer struct{}
sort Goodbyer struct{}

var _ Greeter = Helloer{}  // Helloer  implements Greeter
var _ Greeter = Goodbyer{} // Goodbyer implements Greeter

func (Helloer) Greet()  { hi there() }
func (Goodbyer) Greet() { goodbye() }

func hi there()   { fmt.Println("hi there") }
func goodbye() { fmt.Println("goodbye") }

Once we execute it, it says hi there:

$ go run .
hi there

It’s clear from its output that this program executes the hi there
operate however not the goodbye operate.
What’s much less clear at a look is that the goodbye operate can
by no means be referred to as.
Nevertheless, we are able to’t merely delete goodbye, as a result of it’s required by the
Goodbyer.Greet methodology, which in flip is required to implement the
Greeter interface whose Greet methodology we are able to see is known as from foremost.
But when we work forwards from foremost, we are able to see that no Goodbyer values
are ever created, so the Greet name in foremost can solely attain Helloer.Greet.
That’s the concept behind the algorithm utilized by the deadcode instrument.

Once we run deadcode on this program, the instrument tells us that the
goodbye operate and the Goodbyer.Greet methodology are each unreachable:

$ deadcode .
greet.go:23: unreachable func: goodbye
greet.go:20: unreachable func: Goodbyer.Greet

With this data, we are able to safely take away each capabilities,
together with the Goodbyer sort itself.

The instrument may clarify why the hi there operate is dwell. It responds
with a sequence of operate calls that reaches hi there, ranging from foremost:

$ deadcode -whylive=instance.com/greet.hi there .
                  instance.com/greet.foremost
dynamic@L0008 --> instance.com/greet.Helloer.Greet
 static@L0019 --> instance.com/greet.hi there

The output is designed to be simple to learn on a terminal, however you possibly can
use the -json or -f=template flags to specify richer output codecs for
consumption by different instruments.

The way it works

The deadcode command
loads,
parses,
and type-checks the desired packages,
then converts them into an
intermediate representation
much like a typical compiler.

It then makes use of an algorithm referred to as
Rapid Type Analysis (RTA)
to construct up the set of capabilities which are reachable,
which is initially simply the entry factors of every foremost bundle:
the foremost operate,
and the bundle initializer operate,
which assigns international variables and calls capabilities named init.

RTA appears to be like on the statements within the physique of every reachable operate to
collect three sorts of data: the set of capabilities it calls immediately;
the set of dynamic calls it makes by means of interface strategies;
and the set of varieties it converts to an interface.

Direct operate calls are simple: we simply add the callee to the set of
reachable capabilities, and if it’s the primary time we’ve encountered the
callee, we examine its operate physique the identical approach we did for foremost.

Dynamic calls by means of interface strategies are trickier, as a result of we don’t
know the set of varieties that implement the interface. We don’t need
to imagine that each attainable methodology in this system whose sort matches
is a attainable goal for the decision, as a result of a few of these varieties might
be instantiated solely from lifeless code! That’s why we collect the set of
varieties transformed to interfaces: the conversion makes every of those
varieties reachable from foremost, in order that its strategies at the moment are attainable
targets of dynamic calls.

This results in a chicken-and-egg scenario. As we encounter every new
reachable operate, we uncover extra interface methodology calls and extra
conversions of concrete varieties to interface varieties.
However because the cross product of those two units (interface methodology calls ×
concrete varieties) grows ever bigger, we uncover new reachable
capabilities.
This class of issues, referred to as “dynamic programming”, may be solved by
(conceptually) making checkmarks in a big two-dimensional desk,
including rows and columns as we go, till there are not any extra checks to
add. The checkmarks within the closing desk tells us what’s reachable;
the clean cells are the lifeless code.


illustration of Rapid Type Analysis

The foremost operate causes Helloer to be
instantiated, and the g.Greet name
dispatches to the Greet methodology of every sort instantiated thus far.

Dynamic calls to (non-method) capabilities are handled much like
interfaces of a single methodology.
And calls made using reflection
are thought-about to succeed in any methodology of any sort utilized in an interface
conversion, or any sort derivable from one utilizing the replicate bundle.
However the precept is similar in all instances.

Checks

RTA is a whole-program evaluation. Meaning it all the time begins from a
foremost operate and works ahead: you possibly can’t begin from a library
bundle akin to encoding/json.

Nevertheless, most library packages have assessments, and assessments have foremost
capabilities. We don’t see them as a result of they’re generated behind the
scenes of go check, however we are able to embody them within the evaluation utilizing the
-test flag.

If this experiences {that a} operate in a library bundle is lifeless, that’s
an indication that your check protection could possibly be improved.
For instance, this command lists all of the capabilities in encoding/json
that aren’t reached by any of its assessments:

$ deadcode -test -filter=encoding/json encoding/json
encoding/json/decode.go:150:31: unreachable func: UnmarshalFieldError.Error
encoding/json/encode.go:225:28: unreachable func: InvalidUTF8Error.Error

(The -filter flag restricts the output to packages matching the
common expression. By default, the instrument experiences all packages within the
preliminary module.)

Soundness

All static evaluation instruments
necessarily
produce imperfect approximations of the attainable dynamic
behaviors of the goal program.
A instrument’s assumptions and inferences could also be “sound”, that means
conservative however maybe overly cautious, or “unsound”, that means
optimistic however not all the time appropriate.

The deadcode instrument isn’t any exception: it should approximate the set of
targets of dynamic calls by means of operate and interface values or
utilizing reflection.
On this respect, the instrument is sound. In different phrases, if it experiences a
operate as lifeless code, it means the operate can’t be referred to as even
by means of these dynamic mechanisms. Nevertheless the instrument might fail to report
some capabilities that actually can by no means be executed.

The deadcode instrument should additionally approximate the set of calls comprised of
capabilities not written in Go, which it can not see.
On this respect, the instrument just isn’t sound.
Its evaluation just isn’t conscious of capabilities referred to as completely from
meeting code, or of the aliasing of capabilities that arises from
the go:linkname directive.
Luckily each of those options are hardly ever used outdoors the Go runtime.

Attempt it out

We run deadcode periodically on our initiatives, particularly after
refactoring work, to assist determine components of this system which are no
longer wanted.

With the lifeless code laid to relaxation, you possibly can give attention to eliminating code
whose time has come to an finish however that stubbornly stays alive,
persevering with to empty your life pressure. We name such undead capabilities
“vampire code”!

Please attempt it out:

$ go set up golang.org/x/instruments/cmd/deadcode@newest

We’ve discovered it helpful, and we hope you do too.

Read More

Vinkmag ad

Read Previous

Chrome experimental AI options

Read Next

Brex Layoffs

Leave a Reply

Your email address will not be published. Required fields are marked *

Most Popular