From 4601f5dabaf2773cafa49a4d3919986823b509b8 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 4 Oct 2018 13:56:41 -0400 Subject: [PATCH] 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 Run-TryBot: Michael Matloob --- go/analysis/analysis.go | 42 ++---- go/analysis/doc.go | 316 ++++++++++++++++++++++++++++++++++++++++ go/analysis/validate.go | 10 +- 3 files changed, 333 insertions(+), 35 deletions(-) create mode 100644 go/analysis/doc.go diff --git a/go/analysis/analysis.go b/go/analysis/analysis.go index 84a76f4f..21baa02a 100644 --- a/go/analysis/analysis.go +++ b/go/analysis/analysis.go @@ -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 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. type Pass struct { - // -- inputs -- - Analyzer *Analyzer // the identity of the current analyzer // syntax and type information @@ -106,6 +88,11 @@ type Pass struct { Pkg *types.Package // type information about the package 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 // the corresponding results of its prerequisite analyzers. // The map keys are the elements of Analysis.Required, @@ -113,6 +100,8 @@ type Pass struct { // analysis's ResultType. ResultOf map[*Analyzer]interface{} + // -- facts -- + // ImportObjectFact retrieves a fact associated with obj. // Given a value ptr of type *T, where *T satisfies Fact, // ImportObjectFact copies the value to *ptr. @@ -126,13 +115,6 @@ type Pass struct { // See comments for ImportObjectFact. 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, // 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. // -// Each fact is associated with a named declaration (a types.Object). -// A single object may have multiple associated facts, but only one of -// any particular fact type. +// Each fact is associated with a named declaration (a types.Object) or +// with a package as a whole. A single object or package may have +// multiple associated facts, but only one of any particular fact type. // // 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 // 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. // Facts are analogous to type export data in a build system: // just as export data enables separate compilation of several passes, diff --git a/go/analysis/doc.go b/go/analysis/doc.go new file mode 100644 index 00000000..5f96774a --- /dev/null +++ b/go/analysis/doc.go @@ -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 diff --git a/go/analysis/validate.go b/go/analysis/validate.go index ff176e48..dd9d25b6 100644 --- a/go/analysis/validate.go +++ b/go/analysis/validate.go @@ -8,11 +8,11 @@ import ( // Validate reports an error if any of the analyzers are misconfigured. // Checks include: -// - that the name is a valid identifier; -// - that analyzer names are unique; -// - that the Requires graph is acylic; -// - that analyzer fact types are unique; -// - that each fact type is a pointer. +// that the name is a valid identifier; +// that analyzer names are unique; +// that the Requires graph is acylic; +// that analyzer fact types are unique; +// that each fact type is a pointer. func Validate(analyzers []*Analyzer) error { names := make(map[string]bool)