Now Reading
Discovering unreachable features with deadcode

Discovering unreachable features with deadcode

2024-01-23 12:05:53

Alan Donovan
12 December 2023

Features which are a part of your mission’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.
Right this moment we’re happy to share a device named deadcode that can assist you determine them.

$ go set up golang.org/x/instruments/cmd/deadcode@newest
$ deadcode -help
The deadcode command reviews unreachable features in Go packages.

Utilization: deadcode [flags] bundle...

Instance

During the last 12 months or so, we’ve been making quite a lot 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 present operate, taking care to
be certain that its new conduct satisfies the wants of all present callers.
Typically, after placing in all that effort, we’d 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 identified this beforehand our refactoring job would have been
simpler.

The straightforward Go program beneath illustrates the issue:

module instance.com/greet
go 1.21
bundle predominant

import "fmt"

func predominant() {
    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 will’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 will see is known as from predominant.
But when we work forwards from predominant, we will see that no Goodbyer values
are ever created, so the Greet name in predominant can solely attain Helloer.Greet.
That’s the thought behind the algorithm utilized by the deadcode device.

Once we run deadcode on this program, the device 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 information, we will safely take away each features,
together with the Goodbyer sort itself.

The device may also clarify why the hi there operate is stay. It responds
with a series of operate calls that reaches hi there, ranging from predominant:

$ deadcode -whylive=instance.com/greet.hi there .
                  instance.com/greet.predominant
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 required packages,
then converts them into an
intermediate representation
just like a typical compiler.

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

RTA appears on the statements within the physique of every reachable operate to
collect three sorts of knowledge: the set of features it calls immediately;
the set of dynamic calls it makes via 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 features, and if it’s the primary time we’ve encountered the
callee, we examine its operate physique the identical approach we did for predominant.

Dynamic calls via 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 could
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 predominant, in order that its strategies are actually attainable
targets of dynamic calls.

This results in a chicken-and-egg state of affairs. 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
features.
This class of issues, referred to as “dynamic programming”, might 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 predominant operate causes Helloer to be
instantiated, and the g.Greet name
dispatches to the Greet methodology of every sort instantiated to this point.

Dynamic calls to (non-method) features are handled just like
interfaces of a single methodology.
And calls made using reflection
are thought of 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 circumstances.

Checks

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

Nevertheless, most library packages have checks, and checks have predominant
features. We don’t see them as a result of they’re generated behind the
scenes of go check, however we will embrace them within the evaluation utilizing the
-test flag.

If this reviews {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 features in encoding/json
that aren’t reached by any of its checks:

See Also

$ 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 device reviews 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 device’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 right.

The deadcode device is not any exception: it should approximate the set of
targets of dynamic calls via operate and interface values or
utilizing reflection.
On this respect, the device is sound. In different phrases, if it reviews a
operate as lifeless code, it means the operate can’t be referred to as even
via these dynamic mechanisms. Nevertheless the device could fail to report
some features that in reality can by no means be executed.

The deadcode device should additionally approximate the set of calls created from
features not written in Go, which it can not see.
On this respect, the device will not be sound.
Its evaluation will not be conscious of features referred to as solely from
meeting code, or of the aliasing of features that arises from
the go:linkname directive.
Fortuitously each of those options are hardly ever used outdoors the Go runtime.

Attempt it out

We run deadcode periodically on our tasks, 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 deal with 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 features
“vampire code”!

Please strive it out:

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

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

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top