Skip to content

Commit

Permalink
Port staticcheck to the go/analysis framework
Browse files Browse the repository at this point in the history
This change ports all static analysis checks to the go/analysis
framework and turns cmd/staticcheck into a sophisticated runner for
analyses.

Since our previous framework was built around the idea of having all
data in memory at once, some changes had to be made to accomodate the
modular go/analysis framework.

All information about dependencies have to be serialized as facts.
This includes information such as which objects are deprecated and
which functions are pure. We have thus converted the 'functions'
package to act as analyses instead of generating a global set of
information.

SSA packages are built per package under analysis no single SSA
program exists. This also means that nodes in the SSA graph aren't
canonical; the same function in a dependency may be represented by
many different objects. We no longer store anything SSA related across
analyses.

go/analysis is designed around the idea of enabling caching of facts,
and thus cmd/staticcheck was designed to utilize caching. We rely on
the Go build cache to avoid loading packages from source, and we
implement our own cache of facts to avoid reanalyzing packages that
haven't changed. This combination can greatly reduce memory use as
well as runtime. For smaller packages, it even allows real-time
checking in something like a language server.

We've replaced our own testing utilities with
go/analysis/analysistest, primarily for the sake of consistency with
other analyses.

Reimplementing 'unused' in the new framework required extra work, and
special knowledge in the runner. Unused cannot analyze packages in
isolation, since files may be shared between test variants and
identifiers may only be used in some variations of a package. Unused
still exposes an entry-point matching the go/analysis framework, but
it doesn't compute any facts nor report any diagnostics. Instead, it
incrementally builds a view of all packages it sees. After all
packages have been analyzed, the result can be queried and processed.

While all other analyses can be reused by other runners directly,
using unused will require special code in the runner.

We've deleted the gosimple, unused and megacheck binaries. They had
already been deprecated and there was little point in porting them to
the new framework. With the removal of the unused binary there is
currently no way to use its whole program mode. This will be rectified
in a follow-up commit which adds said mode as its own check in
staticcheck.
  • Loading branch information
dominikh committed Apr 26, 2019
1 parent b088cad commit c7b3dbf
Show file tree
Hide file tree
Showing 202 changed files with 5,827 additions and 3,356 deletions.
1 change: 0 additions & 1 deletion cmd/gosimple/README.md

This file was deleted.

20 changes: 0 additions & 20 deletions cmd/gosimple/gosimple.go

This file was deleted.

1 change: 0 additions & 1 deletion cmd/megacheck/README.md

This file was deleted.

93 changes: 0 additions & 93 deletions cmd/megacheck/megacheck.go

This file was deleted.

19 changes: 13 additions & 6 deletions cmd/staticcheck/staticcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main // import "honnef.co/go/tools/cmd/staticcheck"
import (
"os"

"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/lint/lintutil"
"honnef.co/go/tools/simple"
Expand All @@ -16,12 +17,18 @@ func main() {
fs := lintutil.FlagSet("staticcheck")
fs.Parse(os.Args[1:])

checkers := []lint.Checker{
simple.NewChecker(),
staticcheck.NewChecker(),
stylecheck.NewChecker(),
&unused.Checker{},
var cs []*analysis.Analyzer
for _, v := range simple.Analyzers {
cs = append(cs, v)
}
for _, v := range staticcheck.Analyzers {
cs = append(cs, v)
}
for _, v := range stylecheck.Analyzers {
cs = append(cs, v)
}

cums := []lint.CumulativeChecker{unused.NewChecker()}

lintutil.ProcessFlagSet(checkers, fs)
lintutil.ProcessFlagSet(cs, cums, fs)
}
1 change: 0 additions & 1 deletion cmd/unused/README.md

This file was deleted.

57 changes: 0 additions & 57 deletions cmd/unused/main.go

This file was deleted.

28 changes: 26 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,34 @@ package config
import (
"os"
"path/filepath"
"reflect"

"github.com/BurntSushi/toml"
"golang.org/x/tools/go/analysis"
)

var Analyzer = &analysis.Analyzer{
Name: "config",
Doc: "loads configuration for the current package tree",
Run: func(pass *analysis.Pass) (interface{}, error) {
if len(pass.Files) == 0 {
cfg := DefaultConfig
return &cfg, nil
}
// FIXME(dh): this may yield the wrong path for generated files in the build cache
path := pass.Fset.PositionFor(pass.Files[0].Pos(), true).Filename
dir := filepath.Dir(path)
cfg, err := Load(dir)
return &cfg, err
},
RunDespiteErrors: true,
ResultType: reflect.TypeOf((*Config)(nil)),
}

func For(pass *analysis.Pass) *Config {
return pass.ResultOf[Analyzer].(*Config)
}

func mergeLists(a, b []string) []string {
out := make([]string, 0, len(a)+len(b))
for _, el := range b {
Expand Down Expand Up @@ -73,7 +97,7 @@ type Config struct {
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
}

var defaultConfig = Config{
var DefaultConfig = Config{
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"},
Initialisms: []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
Expand Down Expand Up @@ -120,7 +144,7 @@ func parseConfigs(dir string) ([]Config, error) {
}
dir = ndir
}
out = append(out, defaultConfig)
out = append(out, DefaultConfig)
if len(out) < 2 {
return out, nil
}
Expand Down
56 changes: 0 additions & 56 deletions functions/concrete.go

This file was deleted.

Loading

0 comments on commit c7b3dbf

Please sign in to comment.