go/analysis: how Go's modular static-analysis system is built
Translated by a model; the original is in Russian.
The golang.org/x/tools/go/analysis package describes the interface that connects an individual static analyzer with the driver program that runs it. The idea is simple: an analyzer written once can be plugged in anywhere — into a command-line utility like go vet, into an editor or IDE, into build systems (go build, Bazel, Buck), into review tools and source indexers. All the author has to do is implement a common contract, rather than adapt to each tool separately.
The analyzer and the pass
The central type is Analyzer. In essence it's a declarative description of a check: a name, documentation, flags, dependencies on other analyzers, and a Run function. The classic example is the checker for fmt.Printf, which catches mismatches in format strings. To add a new check to an existing driver, it's enough to append an analyzer to the list.
The actual work is done by the Run function, which receives a Pass object — the unit of work meaning “this analyzer on this package.” Through Pass you get access to syntax trees, type information, and source positions, and you send back diagnostics via Report. Tellingly, a diagnostic deliberately has no severity field: the framework's authors hold that the priority of a message is a matter of user taste, so they leave filtering and ranking to the driver.
Modularity through “facts”
The most interesting part is modularity. Analysis, like compilation, can be carried out separately: packages are processed one at a time, and the results of lower levels are reused at higher ones. To this end, the notion of a Fact is introduced — an intermediate piece of knowledge such as “the function log.Fatalf is a wrapper around Printf.” On its own it's uninteresting, but it serves as a stepping stone to a real diagnostic: having discovered such a wrapper, the checker starts scrutinizing its calls more strictly too, including from other packages.
Facts are serialized via encoding/gob and can be passed between packages and even between address spaces. The requirement for the encoding is determinism: serialized facts end up in build artifacts, and nondeterminism would lead to false cache misses in content-addressable systems.
Testing and ready-made commands
To test analyzers there's the analysistest subpackage: the expected diagnostics are specified right in the test files with // want ... comments. And to turn an analyzer into a standalone utility, there are singlechecker (for a single check) and multichecker (for a set) — a full-fledged command fits in a few lines.
The distribution already ships with an extensive set of ready-made checks: from printf, shadow, and nilness to fieldalignment, loopclosure, and modernize, which offers to rewrite code to use modern features of the language.
Source: Hacker News
Comments
Sign in, to comment.