analysis

package
v0.0.0-...-0c6a35b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2019 License: BSD-3-Clause Imports: 7 Imported by: 0

Documentation

Overview

The analysis package defines the interface between a modular static analysis and an analysis driver program.

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 doc comment contains a brief one-line summary, optionally followed by paragraphs of explanation. The vet 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 go.coder.com/go-tools/go/analysis/cmd/vet
$ ./vet help
vet is a tool for static analysis of Go programs.

Usage: vet [-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

$ ./vet 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 go.coder.com/go-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.

Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl and buildtag, inspect the raw text of Go source files or even non-Go files such as assembly. To report a diagnostic against a line of a raw text file, use the following sequence:

content, err := ioutil.ReadFile(filename)
if err != nil { ... }
tf := fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
...
pass.Reportf(tf.LineStart(line), "oops")

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: []analysis.Fact{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.

Some driver implementations (such as those based on Bazel and Blaze) do not currently apply analyzers to packages of the standard library. Therefore, for best results, analyzer authors should not rely on analysis facts being available for standard packages. For example, although the printf checker is capable of deducing during analysis of the log package that log.Printf is a printf-wrapper, this fact is built in to the analyzer so that it correctly checks calls to log.Printf even when run in a driver that does not apply it to standard packages. We plan to remove this limitation in future.

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 analyzers, 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 analyzer. 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 (
	"go.coder.com/go-tools/go/analysis/passes/findcall"
	"go.coder.com/go-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.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Validate

func Validate(analyzers []*Analyzer) error

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.

Types

type Analyzer

type Analyzer struct {
	// The Name of the analyzer must be a valid Go identifier
	// as it may appear in command-line flags, URLs, and so on.
	Name string

	// Doc is the documentation for the analyzer.
	// The part before the first "\n\n" is the title
	// (no capital or period, max ~60 letters).
	Doc string

	// Flags defines any flags accepted by the analyzer.
	// The manner in which these flags are exposed to the user
	// depends on the driver which runs the analyzer.
	Flags flag.FlagSet

	// Run applies the analyzer to a package.
	// It returns an error if the analyzer failed.
	//
	// On success, the Run function may return a result
	// computed by the Analyzer; its type must match ResultType.
	// The driver makes this result available as an input to
	// another Analyzer that depends directly on this one (see
	// Requires) when it analyzes the same package.
	//
	// To pass analysis results between packages (and thus
	// potentially between address spaces), use Facts, which are
	// serializable.
	Run func(*Pass) (interface{}, error)

	// RunDespiteErrors allows the driver to invoke
	// the Run method of this analyzer even on a
	// package that contains parse or type errors.
	RunDespiteErrors bool

	// Requires is a set of analyzers that must run successfully
	// before this one on a given package. This analyzer may inspect
	// the outputs produced by each analyzer in Requires.
	// The graph over analyzers implied by Requires edges must be acyclic.
	//
	// Requires establishes a "horizontal" dependency between
	// analysis passes (different analyzers, same package).
	Requires []*Analyzer

	// ResultType is the type of the optional result of the Run function.
	ResultType reflect.Type

	// FactTypes indicates that this analyzer imports and exports
	// Facts of the specified concrete types.
	// An analyzer that uses facts may assume that its import
	// dependencies have been similarly analyzed before it runs.
	// Facts must be pointers.
	//
	// FactTypes establishes a "vertical" dependency between
	// analysis passes (same analyzer, different packages).
	FactTypes []Fact
}

An Analyzer describes an analysis function and its options.

func (*Analyzer) String

func (a *Analyzer) String() string

type Diagnostic

type Diagnostic struct {
	Pos      token.Pos
	Category string // optional
	Message  string
}

A Diagnostic is a message associated with a source location.

An Analyzer may return a variety of diagnostics; the optional Category, which should be a constant, may be used to classify them. It is primarily intended to make it easy to look up documentation.

type Fact

type Fact interface {
	AFact() // dummy method to avoid type errors
}

A Fact is an intermediate fact produced during analysis.

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" 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 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, facts enable "separate analysis".

Each pass (a, p) starts with the set of facts produced by the same analyzer a applied to the packages directly imported by p. The analysis may add facts to the set, and they may be exported in turn. An analysis's Run function may retrieve facts by calling Pass.Import{Object,Package}Fact and update them using Pass.Export{Object,Package}Fact.

A fact is logically private to its Analysis. To pass values between different analyzers, use the results mechanism; see Analyzer.Requires, Analyzer.ResultType, and Pass.ResultOf.

A Fact type must be a pointer. Facts are encoded and decoded using encoding/gob. A Fact may implement the GobEncoder/GobDecoder interfaces to customize its encoding. Fact encoding should not fail.

A Fact should not be modified once exported.

type Pass

type Pass struct {
	Analyzer *Analyzer // the identity of the current analyzer

	// syntax and type information
	Fset       *token.FileSet // file position information
	Files      []*ast.File    // the abstract syntax tree of each file
	OtherFiles []string       // names of non-Go files of this package
	Pkg        *types.Package // type information about the package
	TypesInfo  *types.Info    // type information about the syntax trees
	TypesSizes types.Sizes    // function for computing sizes of types

	// 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,
	// and the type of each corresponding value is the required
	// analysis's ResultType.
	ResultOf map[*Analyzer]interface{}

	// ImportObjectFact retrieves a fact associated with obj.
	// Given a value ptr of type *T, where *T satisfies Fact,
	// ImportObjectFact copies the value to *ptr.
	//
	// ImportObjectFact panics if called after the pass is complete.
	// ImportObjectFact is not concurrency-safe.
	ImportObjectFact func(obj types.Object, fact Fact) bool

	// ImportPackageFact retrieves a fact associated with package pkg,
	// which must be this package or one of its dependencies.
	// See comments for ImportObjectFact.
	ImportPackageFact func(pkg *types.Package, fact Fact) bool

	// ExportObjectFact associates a fact of type *T with the obj,
	// replacing any previous fact of that type.
	//
	// ExportObjectFact panics if it is called after the pass is
	// complete, or if obj does not belong to the package being analyzed.
	// ExportObjectFact is not concurrency-safe.
	ExportObjectFact func(obj types.Object, fact Fact)

	// ExportPackageFact associates a fact with the current package.
	// See comments for ExportObjectFact.
	ExportPackageFact func(fact Fact)
}

A Pass provides information to the Run function that applies a specific analyzer to a single Go package.

It forms the interface between the analysis logic and the driver program, and has both input and an output components.

As in a compiler, one pass may depend on the result computed by another.

The Run function should not call any of the Pass functions concurrently.

func (*Pass) Reportf

func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{})

Reportf is a helper function that reports a Diagnostic using the specified position and formatted error message.

func (*Pass) String

func (pass *Pass) String() string

Directories

Path Synopsis
Package analysistest provides utilities for testing analyzers.
Package analysistest provides utilities for testing analyzers.
cmd
vet
The vet command is a static checker for Go programs.
The vet command is a static checker for Go programs.
internal
analysisflags
Package analysisflags defines helpers for processing flags of analysis driver tools.
Package analysisflags defines helpers for processing flags of analysis driver tools.
checker
Package checker defines the implementation of the checker commands.
Package checker defines the implementation of the checker commands.
facts
Package facts defines a serializable set of analysis.Fact.
Package facts defines a serializable set of analysis.Fact.
Package multichecker defines the main function for an analysis driver with several analyzers.
Package multichecker defines the main function for an analysis driver with several analyzers.
passes
asmdecl
Package asmdecl defines an Analyzer that reports mismatches between assembly files and Go declarations.
Package asmdecl defines an Analyzer that reports mismatches between assembly files and Go declarations.
assign
Package assign defines an Analyzer that detects useless assignments.
Package assign defines an Analyzer that detects useless assignments.
atomic
Package atomic defines an Analyzer that checks for common mistakes using the sync/atomic package.
Package atomic defines an Analyzer that checks for common mistakes using the sync/atomic package.
atomicalign
Package atomicalign defines an Analyzer that checks for non-64-bit-aligned arguments to sync/atomic functions.
Package atomicalign defines an Analyzer that checks for non-64-bit-aligned arguments to sync/atomic functions.
bools
Package bools defines an Analyzer that detects common mistakes involving boolean operators.
Package bools defines an Analyzer that detects common mistakes involving boolean operators.
buildssa
Package buildssa defines an Analyzer that constructs the SSA representation of an error-free package and returns the set of all functions within it.
Package buildssa defines an Analyzer that constructs the SSA representation of an error-free package and returns the set of all functions within it.
buildtag
Package buildtag defines an Analyzer that checks build tags.
Package buildtag defines an Analyzer that checks build tags.
cgocall
Package cgocall defines an Analyzer that detects some violations of the cgo pointer passing rules.
Package cgocall defines an Analyzer that detects some violations of the cgo pointer passing rules.
composite
Package composite defines an Analyzer that checks for unkeyed composite literals.
Package composite defines an Analyzer that checks for unkeyed composite literals.
copylock
Package copylock defines an Analyzer that checks for locks erroneously passed by value.
Package copylock defines an Analyzer that checks for locks erroneously passed by value.
ctrlflow
Package ctrlflow is an analysis that provides a syntactic control-flow graph (CFG) for the body of a function.
Package ctrlflow is an analysis that provides a syntactic control-flow graph (CFG) for the body of a function.
deepequalerrors
Package deepequalerrors defines an Analyzer that checks for the use of reflect.DeepEqual with error values.
Package deepequalerrors defines an Analyzer that checks for the use of reflect.DeepEqual with error values.
findcall
The findcall package defines an Analyzer that serves as a trivial example and test of the Analysis API.
The findcall package defines an Analyzer that serves as a trivial example and test of the Analysis API.
findcall/cmd/findcall
The findcall command runs the findcall analyzer.
The findcall command runs the findcall analyzer.
httpresponse
Package httpresponse defines an Analyzer that checks for mistakes using HTTP responses.
Package httpresponse defines an Analyzer that checks for mistakes using HTTP responses.
inspect
Package inspect defines an Analyzer that provides an AST inspector (go.coder.com/go-tools/go/ast/inspect.Inspect) for the syntax trees of a package.
Package inspect defines an Analyzer that provides an AST inspector (go.coder.com/go-tools/go/ast/inspect.Inspect) for the syntax trees of a package.
internal/analysisutil
Package analysisutil defines various helper functions used by two or more packages beneath go/analysis.
Package analysisutil defines various helper functions used by two or more packages beneath go/analysis.
loopclosure
Package loopclosure defines an Analyzer that checks for references to enclosing loop variables from within nested functions.
Package loopclosure defines an Analyzer that checks for references to enclosing loop variables from within nested functions.
lostcancel
Package lostcancel defines an Analyzer that checks for failure to call a context cancelation function.
Package lostcancel defines an Analyzer that checks for failure to call a context cancelation function.
lostcancel/cmd/lostcancel
The lostcancel command applies the go.coder.com/go-tools/go/analysis/passes/lostcancel analysis to the specified packages of Go source code.
The lostcancel command applies the go.coder.com/go-tools/go/analysis/passes/lostcancel analysis to the specified packages of Go source code.
nilfunc
Package nilfunc defines an Analyzer that checks for useless comparisons against nil.
Package nilfunc defines an Analyzer that checks for useless comparisons against nil.
nilness
Package nilness inspects the control-flow graph of an SSA function and reports errors such as nil pointer dereferences and degenerate nil pointer comparisons.
Package nilness inspects the control-flow graph of an SSA function and reports errors such as nil pointer dereferences and degenerate nil pointer comparisons.
nilness/cmd/nilness
The nilness command applies the go.coder.com/go-tools/go/analysis/passes/nilness analysis to the specified packages of Go source code.
The nilness command applies the go.coder.com/go-tools/go/analysis/passes/nilness analysis to the specified packages of Go source code.
pkgfact
The pkgfact package is a demonstration and test of the package fact mechanism.
The pkgfact package is a demonstration and test of the package fact mechanism.
shadow/cmd/shadow
The shadow command runs the shadow analyzer.
The shadow command runs the shadow analyzer.
shift
Package shift defines an Analyzer that checks for shifts that exceed the width of an integer.
Package shift defines an Analyzer that checks for shifts that exceed the width of an integer.
stdmethods
Package stdmethods defines an Analyzer that checks for misspellings in the signatures of methods similar to well-known interfaces.
Package stdmethods defines an Analyzer that checks for misspellings in the signatures of methods similar to well-known interfaces.
structtag
Package structtag defines an Analyzer that checks struct field tags are well formed.
Package structtag defines an Analyzer that checks struct field tags are well formed.
tests
Package tests defines an Analyzer that checks for common mistaken usages of tests and examples.
Package tests defines an Analyzer that checks for common mistaken usages of tests and examples.
unmarshal
The unmarshal package defines an Analyzer that checks for passing non-pointer or non-interface types to unmarshal and decode functions.
The unmarshal package defines an Analyzer that checks for passing non-pointer or non-interface types to unmarshal and decode functions.
unmarshal/cmd/unmarshal
The unmarshal command runs the unmarshal analyzer.
The unmarshal command runs the unmarshal analyzer.
unreachable
Package unreachable defines an Analyzer that checks for unreachable code.
Package unreachable defines an Analyzer that checks for unreachable code.
unsafeptr
Package unsafeptr defines an Analyzer that checks for invalid conversions of uintptr to unsafe.Pointer.
Package unsafeptr defines an Analyzer that checks for invalid conversions of uintptr to unsafe.Pointer.
unusedresult
Package unusedresult defines an analyzer that checks for unused results of calls to certain pure functions.
Package unusedresult defines an analyzer that checks for unused results of calls to certain pure functions.
Package singlechecker defines the main function for an analysis driver with only a single analysis.
Package singlechecker defines the main function for an analysis driver with only a single analysis.
The unitchecker package defines the main function for an analysis driver that analyzes a single compilation unit during a build.
The unitchecker package defines the main function for an analysis driver that analyzes a single compilation unit during a build.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL