go/analysis: write package documentation
godoc: http://100.101.181.83:8000/pkg/golang.org/x/tools/go/analysis/ (Apologies to those not on Google corp network.) Change-Id: I9e21e551443d3048cf247696367d6d06aa7da13e Reviewed-on: https://go-review.googlesource.com/c/139678 Reviewed-by: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
f60d9635b1
commit
4601f5daba
|
@ -1,19 +1,3 @@
|
||||||
// The analysis package defines a uniform interface for static checkers ("Analyzers")
|
|
||||||
// of Go source code. By implementing a common interface, checkers from
|
|
||||||
// a variety of sources can be easily selected, incorporated, and reused
|
|
||||||
// in a wide range of programs including command-line tools, text
|
|
||||||
// editors and IDEs, build systems, test frameworks, code review tools,
|
|
||||||
// and batch pipelines for large code bases. For the design, see
|
|
||||||
// https://docs.google.com/document/d/1-azPLXaLgTCKeKDNg0HVMq2ovMlD-e7n1ZHzZVzOlJk
|
|
||||||
//
|
|
||||||
// Each analyzer is invoked once per Go package, and is provided the
|
|
||||||
// abstract syntax trees (ASTs) and type information for that package.
|
|
||||||
//
|
|
||||||
// The principal data types of this package are structs, not interfaces,
|
|
||||||
// to permit later addition of optional fields as the API evolves.
|
|
||||||
//
|
|
||||||
// THIS INTERFACE IS EXPERIMENTAL AND SUBJECT TO CHANGE.
|
|
||||||
// We aim to finalize it by November 2018.
|
|
||||||
package analysis
|
package analysis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -95,8 +79,6 @@ func (a *Analyzer) String() string { return a.Name }
|
||||||
//
|
//
|
||||||
// The Run function should not call any of the Pass functions concurrently.
|
// The Run function should not call any of the Pass functions concurrently.
|
||||||
type Pass struct {
|
type Pass struct {
|
||||||
// -- inputs --
|
|
||||||
|
|
||||||
Analyzer *Analyzer // the identity of the current analyzer
|
Analyzer *Analyzer // the identity of the current analyzer
|
||||||
|
|
||||||
// syntax and type information
|
// syntax and type information
|
||||||
|
@ -106,6 +88,11 @@ type Pass struct {
|
||||||
Pkg *types.Package // type information about the package
|
Pkg *types.Package // type information about the package
|
||||||
TypesInfo *types.Info // type information about the syntax trees
|
TypesInfo *types.Info // type information about the syntax trees
|
||||||
|
|
||||||
|
// Report reports a Diagnostic, a finding about a specific location
|
||||||
|
// in the analyzed source code such as a potential mistake.
|
||||||
|
// It may be called by the Run function.
|
||||||
|
Report func(Diagnostic)
|
||||||
|
|
||||||
// ResultOf provides the inputs to this analysis pass, which are
|
// ResultOf provides the inputs to this analysis pass, which are
|
||||||
// the corresponding results of its prerequisite analyzers.
|
// the corresponding results of its prerequisite analyzers.
|
||||||
// The map keys are the elements of Analysis.Required,
|
// The map keys are the elements of Analysis.Required,
|
||||||
|
@ -113,6 +100,8 @@ type Pass struct {
|
||||||
// analysis's ResultType.
|
// analysis's ResultType.
|
||||||
ResultOf map[*Analyzer]interface{}
|
ResultOf map[*Analyzer]interface{}
|
||||||
|
|
||||||
|
// -- facts --
|
||||||
|
|
||||||
// ImportObjectFact retrieves a fact associated with obj.
|
// ImportObjectFact retrieves a fact associated with obj.
|
||||||
// Given a value ptr of type *T, where *T satisfies Fact,
|
// Given a value ptr of type *T, where *T satisfies Fact,
|
||||||
// ImportObjectFact copies the value to *ptr.
|
// ImportObjectFact copies the value to *ptr.
|
||||||
|
@ -126,13 +115,6 @@ type Pass struct {
|
||||||
// See comments for ImportObjectFact.
|
// See comments for ImportObjectFact.
|
||||||
ImportPackageFact func(pkg *types.Package, fact Fact) bool
|
ImportPackageFact func(pkg *types.Package, fact Fact) bool
|
||||||
|
|
||||||
// -- outputs --
|
|
||||||
|
|
||||||
// Report reports a Diagnostic, a finding about a specific location
|
|
||||||
// in the analyzed source code such as a potential mistake.
|
|
||||||
// It may be called by the Run function.
|
|
||||||
Report func(Diagnostic)
|
|
||||||
|
|
||||||
// ExportObjectFact associates a fact of type *T with the obj,
|
// ExportObjectFact associates a fact of type *T with the obj,
|
||||||
// replacing any previous fact of that type.
|
// replacing any previous fact of that type.
|
||||||
//
|
//
|
||||||
|
@ -162,16 +144,16 @@ func (pass *Pass) String() string {
|
||||||
|
|
||||||
// A Fact is an intermediate fact produced during analysis.
|
// A Fact is an intermediate fact produced during analysis.
|
||||||
//
|
//
|
||||||
// Each fact is associated with a named declaration (a types.Object).
|
// Each fact is associated with a named declaration (a types.Object) or
|
||||||
// A single object may have multiple associated facts, but only one of
|
// with a package as a whole. A single object or package may have
|
||||||
// any particular fact type.
|
// multiple associated facts, but only one of any particular fact type.
|
||||||
//
|
//
|
||||||
// A Fact represents a predicate such as "never returns", but does not
|
// A Fact represents a predicate such as "never returns", but does not
|
||||||
// represent the subject of the predicate such as "function F".
|
// represent the subject of the predicate such as "function F" or "package P".
|
||||||
//
|
//
|
||||||
// Facts may be produced in one analysis pass and consumed by another
|
// Facts may be produced in one analysis pass and consumed by another
|
||||||
// analysis pass even if these are in different address spaces.
|
// analysis pass even if these are in different address spaces.
|
||||||
// If package P imports Q, all facts about objects of Q produced during
|
// If package P imports Q, all facts about Q produced during
|
||||||
// analysis of that package will be available during later analysis of P.
|
// analysis of that package will be available during later analysis of P.
|
||||||
// Facts are analogous to type export data in a build system:
|
// Facts are analogous to type export data in a build system:
|
||||||
// just as export data enables separate compilation of several passes,
|
// just as export data enables separate compilation of several passes,
|
||||||
|
|
|
@ -0,0 +1,316 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
The analysis package defines the interface between a modular static
|
||||||
|
analysis and an analysis driver program.
|
||||||
|
|
||||||
|
|
||||||
|
THIS INTERFACE IS EXPERIMENTAL AND SUBJECT TO CHANGE.
|
||||||
|
We aim to finalize it by November 2018.
|
||||||
|
|
||||||
|
Background
|
||||||
|
|
||||||
|
A static analysis is a function that inspects a package of Go code and
|
||||||
|
reports a set of diagnostics (typically mistakes in the code), and
|
||||||
|
perhaps produces other results as well, such as suggested refactorings
|
||||||
|
or other facts. An analysis that reports mistakes is informally called a
|
||||||
|
"checker". For example, the printf checker reports mistakes in
|
||||||
|
fmt.Printf format strings.
|
||||||
|
|
||||||
|
A "modular" analysis is one that inspects one package at a time but can
|
||||||
|
save information from a lower-level package and use it when inspecting a
|
||||||
|
higher-level package, analogous to separate compilation in a toolchain.
|
||||||
|
The printf checker is modular: when it discovers that a function such as
|
||||||
|
log.Fatalf delegates to fmt.Printf, it records this fact, and checks
|
||||||
|
calls to that function too, including calls made from another package.
|
||||||
|
|
||||||
|
By implementing a common interface, checkers from a variety of sources
|
||||||
|
can be easily selected, incorporated, and reused in a wide range of
|
||||||
|
driver programs including command-line tools (such as vet), text editors and
|
||||||
|
IDEs, build and test systems (such as go build, Bazel, or Buck), test
|
||||||
|
frameworks, code review tools, code-base indexers (such as SourceGraph),
|
||||||
|
documentation viewers (such as godoc), batch pipelines for large code
|
||||||
|
bases, and so on.
|
||||||
|
|
||||||
|
|
||||||
|
Analyzer
|
||||||
|
|
||||||
|
The primary type in the API is Analyzer. An Analyzer statically
|
||||||
|
describes an analysis function: its name, documentation, flags,
|
||||||
|
relationship to other analyzers, and of course, its logic.
|
||||||
|
|
||||||
|
To define an analysis, a user declares a (logically constant) variable
|
||||||
|
of type Analyzer. Here is a typical example from one of the analyzers in
|
||||||
|
the go/analysis/passes/ subdirectory:
|
||||||
|
|
||||||
|
package unusedresult
|
||||||
|
|
||||||
|
var Analyzer = &analysis.Analyzer{
|
||||||
|
Name: "unusedresult",
|
||||||
|
Doc: "check for unused results of calls to some functions",
|
||||||
|
Run: run,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(pass *analysis.Pass) (interface{}, error) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
An analysis driver is a program such as vet that runs a set of
|
||||||
|
analyses and prints the diagnostics that they report.
|
||||||
|
The driver program must import the list of Analyzers it needs.
|
||||||
|
Typically each Analyzer resides in a separate package.
|
||||||
|
To add a new Analyzer to an existing driver, add another item to the list:
|
||||||
|
|
||||||
|
import ( "unusedresult"; "nilness"; "printf" )
|
||||||
|
|
||||||
|
var analyses = []*analysis.Analyzer{
|
||||||
|
unusedresult.Analyzer,
|
||||||
|
nilness.Analyzer,
|
||||||
|
printf.Analyzer,
|
||||||
|
}
|
||||||
|
|
||||||
|
A driver may use the name, flags, and documentation to provide on-line
|
||||||
|
help that describes the analyses its performs.
|
||||||
|
The "analyze" command, shown below, is an example of a driver that runs
|
||||||
|
multiple analyzers. It is based on the multichecker package
|
||||||
|
(see the "Standalone commands" section for details).
|
||||||
|
|
||||||
|
$ go build golang.org/x/tools/cmd/analyze
|
||||||
|
$ ./analyze help
|
||||||
|
Analyze is a tool for static analysis of Go programs.
|
||||||
|
|
||||||
|
Usage: analyze [-flag] [package]
|
||||||
|
|
||||||
|
Registered analyzers:
|
||||||
|
|
||||||
|
asmdecl report mismatches between assembly files and Go declarations
|
||||||
|
assign check for useless assignments
|
||||||
|
atomic check for common mistakes using the sync/atomic package
|
||||||
|
...
|
||||||
|
unusedresult check for unused results of calls to some functions
|
||||||
|
|
||||||
|
$ ./analyze help unusedresult
|
||||||
|
unusedresult: check for unused results of calls to some functions
|
||||||
|
|
||||||
|
Analyzer flags:
|
||||||
|
|
||||||
|
-unusedresult.funcs value
|
||||||
|
comma-separated list of functions whose results must be used (default Error,String)
|
||||||
|
-unusedresult.stringmethods value
|
||||||
|
comma-separated list of names of methods of type func() string whose results must be used
|
||||||
|
|
||||||
|
Some functions like fmt.Errorf return a result and have no side effects,
|
||||||
|
so it is always a mistake to discard the result. This analyzer reports
|
||||||
|
calls to certain functions in which the result of the call is ignored.
|
||||||
|
|
||||||
|
The set of functions may be controlled using flags.
|
||||||
|
|
||||||
|
The Analyzer type has more fields besides those shown above:
|
||||||
|
|
||||||
|
type Analyzer struct {
|
||||||
|
Name string
|
||||||
|
Doc string
|
||||||
|
Flags flag.FlagSet
|
||||||
|
Run func(*Pass) (interface{}, error)
|
||||||
|
RunDespiteErrors bool
|
||||||
|
ResultType reflect.Type
|
||||||
|
Requires []*Analyzer
|
||||||
|
FactTypes []Fact
|
||||||
|
}
|
||||||
|
|
||||||
|
The Flags field declares a set of named (global) flag variables that
|
||||||
|
control analysis behavior. Unlike vet, analysis flags are not declared
|
||||||
|
directly in the command line FlagSet; it is up to the driver to set the
|
||||||
|
flag variables. A driver for a single analysis, a, might expose its flag
|
||||||
|
f directly on the command line as -f, whereas a driver for multiple
|
||||||
|
analyses might prefix the flag name by the analysis name (-a.f) to avoid
|
||||||
|
ambiguity. An IDE might expose the flags through a graphical interface,
|
||||||
|
and a batch pipeline might configure them from a config file.
|
||||||
|
See the "findcall" analyzer for an example of flags in action.
|
||||||
|
|
||||||
|
The RunDespiteErrors flag indicates whether the analysis is equipped to
|
||||||
|
handle ill-typed code. If not, the driver will skip the analysis if
|
||||||
|
there were parse or type errors.
|
||||||
|
The optional ResultType field specifies the type of the result value
|
||||||
|
computed by this analysis and made available to other analyses.
|
||||||
|
The Requires field specifies a list of analyses upon which
|
||||||
|
this one depends and whose results it may access, and it constrains the
|
||||||
|
order in which a driver may run analyses.
|
||||||
|
The FactTypes field is discussed in the section on Modularity.
|
||||||
|
The analysis package provides a Validate function to perform basic
|
||||||
|
sanity checks on an Analyzer, such as that its Requires graph is
|
||||||
|
acyclic, its fact and result types are unique, and so on.
|
||||||
|
|
||||||
|
Finally, the Run field contains a function to be called by the driver to
|
||||||
|
execute the analysis on a single package. The driver passes it an
|
||||||
|
instance of the Pass type.
|
||||||
|
|
||||||
|
|
||||||
|
Pass
|
||||||
|
|
||||||
|
A Pass describes a single unit of work: the application of a particular
|
||||||
|
Analyzer to a particular package of Go code.
|
||||||
|
The Pass provides information to the Analyzer's Run function about the
|
||||||
|
package being analyzed, and provides operations to the Run function for
|
||||||
|
reporting diagnostics and other information back to the driver.
|
||||||
|
|
||||||
|
type Pass struct {
|
||||||
|
Fset *token.FileSet
|
||||||
|
Files []*ast.File
|
||||||
|
OtherFiles []string
|
||||||
|
Pkg *types.Package
|
||||||
|
TypesInfo *types.Info
|
||||||
|
ResultOf map[*Analyzer]interface{}
|
||||||
|
Report func(Diagnostic)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
The Fset, Files, Pkg, and TypesInfo fields provide the syntax trees,
|
||||||
|
type information, and source positions for a single package of Go code.
|
||||||
|
|
||||||
|
The OtherFiles field provides the names, but not the contents, of non-Go
|
||||||
|
files such as assembly that are part of this package. See the "asmdecl"
|
||||||
|
or "buildtags" analyzers for examples of loading non-Go files and report
|
||||||
|
diagnostics against them.
|
||||||
|
|
||||||
|
The ResultOf field provides the results computed by the analyzers
|
||||||
|
required by this one, as expressed in its Analyzer.Requires field. The
|
||||||
|
driver runs the required analyzers first and makes their results
|
||||||
|
available in this map. Each Analyzer must return a value of the type
|
||||||
|
described in its Analyzer.ResultType field.
|
||||||
|
For example, the "ctrlflow" analyzer returns a *ctrlflow.CFGs, which
|
||||||
|
provides a control-flow graph for each function in the package (see
|
||||||
|
golang.org/x/tools/go/cfg); the "inspect" analyzer returns a value that
|
||||||
|
enables other Analyzers to traverse the syntax trees of the package more
|
||||||
|
efficiently; and the "buildssa" analyzer constructs an SSA-form
|
||||||
|
intermediate representation.
|
||||||
|
Each of these Analyzers extends the capabilities of later Analyzers
|
||||||
|
without adding a dependency to the core API, so an analysis tool pays
|
||||||
|
only for the extensions it needs.
|
||||||
|
|
||||||
|
The Report function emits a diagnostic, a message associated with a
|
||||||
|
source position. For most analyses, diagnostics are their primary
|
||||||
|
result.
|
||||||
|
For convenience, Pass provides a helper method, Reportf, to report a new
|
||||||
|
diagnostic by formatting a string.
|
||||||
|
Diagnostic is defined as:
|
||||||
|
|
||||||
|
type Diagnostic struct {
|
||||||
|
Pos token.Pos
|
||||||
|
Category string // optional
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
The optional Category field is a short identifier that classifies the
|
||||||
|
kind of message when an analysis produces several kinds of diagnostic.
|
||||||
|
|
||||||
|
|
||||||
|
Modular analysis with Facts
|
||||||
|
|
||||||
|
To improve efficiency and scalability, large programs are routinely
|
||||||
|
built using separate compilation: units of the program are compiled
|
||||||
|
separately, and recompiled only when one of their dependencies changes;
|
||||||
|
independent modules may be compiled in parallel. The same technique may
|
||||||
|
be applied to static analyses, for the same benefits. Such analyses are
|
||||||
|
described as "modular".
|
||||||
|
|
||||||
|
A compiler’s type checker is an example of a modular static analysis.
|
||||||
|
Many other checkers we would like to apply to Go programs can be
|
||||||
|
understood as alternative or non-standard type systems. For example,
|
||||||
|
vet's printf checker infers whether a function has the "printf wrapper"
|
||||||
|
type, and it applies stricter checks to calls of such functions. In
|
||||||
|
addition, it records which functions are printf wrappers for use by
|
||||||
|
later analysis units to identify other printf wrappers by induction.
|
||||||
|
A result such as “f is a printf wrapper” that is not interesting by
|
||||||
|
itself but serves as a stepping stone to an interesting result (such as
|
||||||
|
a diagnostic) is called a "fact".
|
||||||
|
|
||||||
|
The analysis API allows an analysis to define new types of facts, to
|
||||||
|
associate facts of these types with objects (named entities) declared
|
||||||
|
within the current package, or with the package as a whole, and to query
|
||||||
|
for an existing fact of a given type associated with an object or
|
||||||
|
package.
|
||||||
|
|
||||||
|
An Analyzer that uses facts must declare their types:
|
||||||
|
|
||||||
|
var Analyzer = &analysis.Analyzer{
|
||||||
|
Name: "printf",
|
||||||
|
FactTypes: []reflect.Type{reflect.TypeOf(new(isWrapper))},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
type isWrapper struct{} // => *types.Func f “is a printf wrapper”
|
||||||
|
|
||||||
|
A driver program ensures that facts for a pass’s dependencies are
|
||||||
|
generated before analyzing the pass and are responsible for propagating
|
||||||
|
facts between from one pass to another, possibly across address spaces.
|
||||||
|
Consequently, Facts must be serializable. The API requires that drivers
|
||||||
|
use the gob encoding, an efficient, robust, self-describing binary
|
||||||
|
protocol. A fact type may implement the GobEncoder/GobDecoder interfaces
|
||||||
|
if the default encoding is unsuitable. Facts should be stateless.
|
||||||
|
|
||||||
|
The Pass type has functions to import and export facts,
|
||||||
|
associated either with an object or with a package:
|
||||||
|
|
||||||
|
type Pass struct {
|
||||||
|
...
|
||||||
|
ExportObjectFact func(types.Object, Fact)
|
||||||
|
ImportObjectFact func(types.Object, Fact) bool
|
||||||
|
|
||||||
|
ExportPackageFact func(fact Fact)
|
||||||
|
ImportPackageFact func(*types.Package, Fact) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
An Analyzer may only export facts associated with the current package or
|
||||||
|
its objects, though it may import facts from any package or object that
|
||||||
|
is an import dependency of the current package.
|
||||||
|
|
||||||
|
Conceptually, ExportObjectFact(obj, fact) inserts fact into a hidden map keyed by
|
||||||
|
the pair (obj, TypeOf(fact)), and the ImportObjectFact function
|
||||||
|
retrieves the entry from this map and copies its value into the variable
|
||||||
|
pointed to by fact. This scheme assumes that the concrete type of fact
|
||||||
|
is a pointer; this assumption is checked by the Validate function.
|
||||||
|
See the "printf" analyzer for an example of object facts in action.
|
||||||
|
|
||||||
|
|
||||||
|
Testing an Analyzer
|
||||||
|
|
||||||
|
The analysistest subpackage provides utilities for testing an Analyzer.
|
||||||
|
In a few lines of code, it is possible to run an analyzer on a package
|
||||||
|
of testdata files and check that it reported all the expected
|
||||||
|
diagnostics and facts (and no more). Expectations are expressed using
|
||||||
|
"// want ..." comments in the input code.
|
||||||
|
|
||||||
|
|
||||||
|
Standalone commands
|
||||||
|
|
||||||
|
Analyzers are provided in the form of packages that a driver program is
|
||||||
|
expected to import. The vet command imports a set of several analyses,
|
||||||
|
but users may wish to define their own analysis commands that perform
|
||||||
|
additional checks. To simplify the task of creating an analysis command,
|
||||||
|
either for a single analyzer or for a whole suite, we provide the
|
||||||
|
singlechecker and multichecker subpackages.
|
||||||
|
|
||||||
|
The singlechecker package provides the main function for a command that
|
||||||
|
runs one analysis. By convention, each analyzer such as
|
||||||
|
go/passes/findcall should be accompanied by a singlechecker-based
|
||||||
|
command such as go/analysis/passes/findcall/cmd/findcall, defined in its
|
||||||
|
entirety as:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/tools/go/analysis/passes/findcall"
|
||||||
|
"golang.org/x/tools/go/analysis/singlechecker"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() { singlechecker.Main(findcall.Analyzer) }
|
||||||
|
|
||||||
|
A tool that provides multiple analyzers can use multichecker in a
|
||||||
|
similar way, giving it the list of Analyzers.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package analysis
|
|
@ -8,11 +8,11 @@ import (
|
||||||
|
|
||||||
// Validate reports an error if any of the analyzers are misconfigured.
|
// Validate reports an error if any of the analyzers are misconfigured.
|
||||||
// Checks include:
|
// Checks include:
|
||||||
// - that the name is a valid identifier;
|
// that the name is a valid identifier;
|
||||||
// - that analyzer names are unique;
|
// that analyzer names are unique;
|
||||||
// - that the Requires graph is acylic;
|
// that the Requires graph is acylic;
|
||||||
// - that analyzer fact types are unique;
|
// that analyzer fact types are unique;
|
||||||
// - that each fact type is a pointer.
|
// that each fact type is a pointer.
|
||||||
func Validate(analyzers []*Analyzer) error {
|
func Validate(analyzers []*Analyzer) error {
|
||||||
names := make(map[string]bool)
|
names := make(map[string]bool)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue