Staticcheck 2019.2 Release Notes

Performance improvements

Staticcheck 2019.2 brings major performance improvements and a reduction in memory usage.

Staticcheck has been redesigned to only keep those packages in memory that are actively being processed. This allows for much larger workspaces to be checked in one go. While previously it may have been necessary to split a list of packages into many invocations of staticcheck, this is now handled intelligently and efficiently by staticcheck itself.

In particular, memory usage is now closely tied to parallelism: having more CPU cores available allows for more packages to be processed in parallel, which increases the number of packages held in memory at any one time. Not only does this make good use of available resources – systems with more CPU cores also tend to have more memory available – it also exposes a single, easy to use knob for trading execution time for memory use. By setting GOMAXPROCS to a value lower than the number of available cores, memory usage of staticcheck will be reduced, at the cost of taking longer to complete.

We've observed reductions in memory usage of 2x to 8x when checking large code bases.

Package 2019.1.1 2019.2¹ Change
net/http 3.543 s / 677 MB 3.747 s / 254 MB +5.76% / -62.48%
strconv 1.628 s / 294 MB 1.678 s / 118 MB +3.07% / -59.86%
image/color 1.304 s / 225 MB 1.702 s / 138 MB +30.52% / -38.67%
std 26.234 s / 3987 MB 19.444 s / 1054 MB -25.88% / -73.56%
github.com/cockroachdb/cockroach/pkg/... 88.644 s / 15959 MB 93.798 s / 4156 MB +5.81% / -73.96%
¹: The fact cache was empty for all benchmarks.

In addition, staticcheck now employs caching to speed up repeated checking of packages. In the past, when checking a package, all of its dependencies had to be loaded from source and analyzed. Now, we can make use of Go's build cache, as well as cache our own analysis facts. This makes staticcheck behave a lot more like go build, where repeated builds are much faster.

Package Uncached Cached Change
net/http 3.747 s / 254 MB 1.545 s / 195 MB -58.77% / -23.23%
strconv 1.678 s / 118 MB 0.495 s / 57 MB -70.5% / -51.69%
image/color 1.702 s / 138 MB 0.329 s / 31 MB -80.67% / -77.54%
std 19.444 s / 1054 MB 15.099 s / 887 MB -22.35% / -15.84%
github.com/cockroachdb/cockroach/pkg/... 93.798 s / 4156 MB 47.205 s / 2516 MB -49.67% / -39.46%

This combination of improvements not only compensates for the increased memory usage that 2019.1 introduced, it also brings the memory usage and execution times way below the levels of those seen in the 2017.2 release, which had previously been our most efficient release.

It should be noted that all of these improvements are part of the staticcheck command itself, not the individual checks. Tools such as golangci-lint will have to replicate our efforts to benefit from these improvements.

The go/analysis framework

Part of the redesign of staticcheck involved porting our code to the go/analysis framework.

The go/analysis framework is a framework for writing static analysis tools such as staticcheck and go vet. It provides an API that enables interoperability between different analyses and analysis drivers – drivers being the code that actually executes analyses. The intention is that any driver can trivially use any analysis that is implemented using go/analysis.

With the exception of U1000, all of our checks are now go/analysis analyses. Furthermore, the staticcheck command is now a go/analysis driver.

With our move to this framework, we enable other drivers to reuse our checks without having to patch them. This should be of particular interest to golangci-lint, which previously took to patching staticcheck, sometimes in subtly incorrect ways. Another high-profile go/analysis driver is gopls, the Go language server. It will now be much easier for gopls to use staticcheck to analyze code, should it so desire.

Theoretically it would also allow us to use third-party analyses as part of staticcheck. Due to quality control reasons, however, we will likely refrain from doing so. Nonetheless it would be trivial for users to maintain internal forks of cmd/staticcheck that use third-party analyses.

Improvements to the CLI

We've made several minor improvements to the command-line interface of staticcheck that improve usability and debuggability.

SIGINFO handler

Upon receiving the SIGINFO signal – or SIGUSR1 on platforms that lack SIGINFO – staticcheck will dump statistics, such as the current phase and how many packages are left to analyze.


Packages: 37/619 initial, 38/1011 total; Workers: 8/8; Problems: 73

Explaining checks

Using the new -explain flag, a check's documentation can be displayed right in the terminal, eliminating the need to browse to https://staticcheck.io/docs/checks.


$ staticcheck -explain S1007
Simplify regular expression by using raw string literal

Raw string literals use ` instead of " and do not support
any escape sequences. This means that the backslash (\) can be used
freely, without the need of escaping.

Since regular expressions have their own escape sequences, raw strings
can improve their readability.

Before:

    regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")

After:

    regexp.Compile(`\A(\w+) profile: total \d+\n\z`)

Available since
    2017.1

-debug.version

The -debug.version flag causes staticcheck to print detailed version information, such as the Go version used to compile it, as well as the versions of all dependencies if built using Go modules. This feature is intended for debugging issues, and we will ask for its output from users who file issues.


$ staticcheck -debug.version
staticcheck (devel, v0.0.0-20190602125119-5a4a2f4a438d)

Compiled with Go version: go1.12.5
Main module:
	honnef.co/go/tools@v0.0.0-20190602125119-5a4a2f4a438d (sum: h1:U5vSGN1Bjr0Yd/4pRcp8iRUCs3S5TIPzoAeTEFV2aiU=)
Dependencies:
	github.com/BurntSushi/toml@v0.3.1 (sum: h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=)
	golang.org/x/tools@v0.0.0-20190530171427-2b03ca6e44eb (sum: h1:mnQlcVx8Qq8L70HV0DxUGuiuAtiEHTwF1gYJE/EL9nU=)

Enabling unused's whole program mode

When we merged unused into staticcheck, we lost the ability to specify the -exported flag to report unused exported identifiers. Staticcheck 2019.2 restores this ability with the new -unused.whole-program flag.

Range information in diagnostics

Many of our checks now emit [start, end] ranges for findings instead of just positions. These ranges can be accessed via the json output formatter, as well as by using go/analysis.Diagnostic directly, such as in gopls.

Note that not all checks are able to emit range information.

Installing staticcheck as a module

As part of the 2019.2 release, we've turned staticcheck into a Go module. From now on, if using Go modules, you can install specific versions of staticcheck with go get honnef.co/go/tools/cmd/staticcheck@<version>, though do note that older releases do not have a go.mod file. You can still download them as modules, but Go will record indirect dependencies in the main module's go.mod file, and no minimum versions are specified.

Staticcheck will not use Semantic Versioning for its releases. It is our belief that Semver is a poor fit for applications and is more suited towards libraries. For example, almost every release of staticcheck has backwards incompatible changes to some APIs that aren't meant for public consumption, but which we expose nevertheless so that tinkerers can use them.

However, we use so-called pre-release versions of the form v0.0.0-2019.2. These allow us to embed our versioning scheme in that of Semver, with correct sorting and updating of versions. Furthermore, these versions ensure that go get ..., if not specifying an explicit version (that is, if using the query latest), will install the latest released version of staticcheck and not the master branch.

While you can use these pre-release version numbers directly, you can also use the canonical versions of the form 2019.2 instead. The Go tool will automatically translate these versions to the appropriate pre-releases.

To install the master branch, use go get honnef.co/go/tools/cmd/staticcheck@master

Removal of deprecated functionality

Staticcheck 2019.1 deprecated the unused, gosimple, and megacheck utilities, as they have been merged into staticcheck. Furthermore, it deprecated the -ignore flag, which has been replaced by linter directives.

This release no longer includes these deprecated utilities, nor does it provide the deprecated flag.

Checks

New checks

Numerous new checks have been added in this release:

  • S1033 flags unnecessary guards around calls to delete.
  • S1034 simplifies type switches involving redundant type assertions.
  • SA1026 flags attempts at marshaling invalid types.
  • SA1027 flags incorrectly aligned atomic accesses.
  • SA4020 flags unreachable case clauses in type switches.
  • SA4021 flags calls to append with a single argument, as x = append(y) is equivalent to x = y.
  • SA5008 flags certain kinds of invalid struct tags.
  • SA5009 verifies the correctness of Printf calls.
  • SA6005 flags inefficient string comparisons involving strings.ToLower or strings.ToUpper when they can be replaced with strings.EqualFold.
  • SA9005 flags attempts at marshaling structs with no public fields nor custom marshaling.
  • ST1017 flags so-called yoda conditions, which take the form of if 42 == x.
  • ST1018 flags string literals containing zero-width characters.

Changed checks

Several checks have been improved:

  • SA1019 now flags imports of deprecated packages.
  • SA4000 no longer flags comparisons between custom float types. Additionally, it avoids a false positive caused by cgo.
  • SA4006 no longer flags unused values in code generated by goyacc. This avoids noise caused by the nature of the generated state machine.
  • ST1005 no longer flags error messages that start with capitalized type names.
  • ST1006 no longer flags receiver names in generated code.
  • SA5002 no longer suggests replacing for false { with for {.
  • Added "SIP" and "RTP" as default initialisms to ST1003.
  • SA1006, SA4003, S1017, and S1020 match more code patterns.
  • S1021 is less eager to merge declarations and assignments when multiple assignments are involved.
  • U1000 has been rewritten, eliminating a variety of false positives.

Sustainable open source and a personal plea

Staticcheck is an open source project developed primarily by me, Dominik Honnef, in my free time. While this model of software development has gotten increasingly common, it is not very sustainable. Time has to be split between open source work and paid work to sustain one's life. This is made especially unfortunate by the fact that hundreds of companies rely on open source each day, but few consider giving back to it, even though it would directly benefit their businesses, ensuring that the software they rely on keeps being developed.

I have long been soliciting donations for staticcheck on Patreon to make its development more sustainable. A fair number of individuals have generously pledged their support and I am very grateful to them. Unfortunately, only few companies support staticcheck's development, and I'd like for that to change.

To people who are familiar with Patreon, it might've always seemed like an odd choice for a software project. Patreon focuses on art and creative work, and on individuals supporting said work, not companies. I am therefore excited to announce my participation in GitHub Sponsors, a new way of supporting developers, directly on GitHub.

GitHub Sponsors allows you to easily support developers by sponsoring them on a monthly basis, via a few simple clicks. It is fully integrated with the platform and can use your existing billing information, making it an effortless process. To encourage more company sponsorships I offer to display your company's logo prominently on staticcheck's website for $250 USD a month, to show my appreciation for your contribution and to show to the world how much you care about code quality.

Please don't hesitate contacting me directly if neither GitHub Sponsors nor Patreon seem suitable to you but you'd like to support me nevertheless. I am sure we can work something out.

Staticcheck 2019.2.1 release notes

The 2019.2 release has an unfortunate bug that prevents staticcheck from running on 32-bit architectures, causing it to crash unconditionally. This release fixes that crash.