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:
Alan Donovan 2018-10-04 13:56:41 -04:00
parent f60d9635b1
commit 4601f5daba
3 changed files with 333 additions and 35 deletions

View File

@ -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,

316
go/analysis/doc.go Normal file
View File

@ -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 compilers 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 passs 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

View File

@ -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)