213 lines
8.0 KiB
Go
213 lines
8.0 KiB
Go
|
// The analysis package defines a uniform interface for static checkers
|
||
|
// 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 analysis 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.
|
||
|
package analysis
|
||
|
|
||
|
import (
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
"go/types"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
// An Analysis describes an analysis function and its options.
|
||
|
type Analysis struct {
|
||
|
// The Name of the analysis 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 analysis.
|
||
|
Doc string
|
||
|
|
||
|
// Flags defines any flags accepted by the analysis.
|
||
|
// The manner in which these flags are exposed to the user
|
||
|
// depends on the driver which runs the analysis.
|
||
|
Flags flag.FlagSet
|
||
|
|
||
|
// Run applies the analysis to a package.
|
||
|
// It returns an error if the analysis failed.
|
||
|
Run func(*Unit) error
|
||
|
|
||
|
// RunDespiteErrors allows the driver to invoke
|
||
|
// the Run method of this analysis even on a
|
||
|
// package that contains parse or type errors.
|
||
|
RunDespiteErrors bool
|
||
|
|
||
|
// Requires is a set of analyses that must run successfully
|
||
|
// before this one on a given package. This analysis may inspect
|
||
|
// the outputs produced by each analysis in Requires.
|
||
|
// The graph over analyses implied by Requires edges must be acyclic.
|
||
|
//
|
||
|
// Requires establishes a "horizontal" dependency between
|
||
|
// analysis units (different analyses, same package).
|
||
|
Requires []*Analysis
|
||
|
|
||
|
// OutputType is the type of the optional Output value
|
||
|
// computed by this analysis and stored in Unit.Output.
|
||
|
// (The Output is provided as an Input to
|
||
|
// each analysis that Requires this one.)
|
||
|
OutputType reflect.Type
|
||
|
|
||
|
// LemmaTypes is the set of types of lemmas produced and
|
||
|
// consumed by this analysis. An analysis that uses lemmas
|
||
|
// may assume that its import dependencies have been
|
||
|
// similarly analyzed before it runs. Lemmas are pointers.
|
||
|
//
|
||
|
// LemmaTypes establishes a "vertical" dependency between
|
||
|
// analysis units (same analysis, different packages).
|
||
|
LemmaTypes []reflect.Type
|
||
|
}
|
||
|
|
||
|
func (a *Analysis) String() string { return a.Name }
|
||
|
|
||
|
// A Unit provides information to the Run function that
|
||
|
// applies a specific analysis 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.
|
||
|
type Unit struct {
|
||
|
// -- inputs --
|
||
|
|
||
|
Analysis *Analysis // the identity of the current analysis
|
||
|
|
||
|
// syntax and type information
|
||
|
Fset *token.FileSet // file position information
|
||
|
Syntax []*ast.File // the abstract syntax tree of each file
|
||
|
Pkg *types.Package // type information about the package
|
||
|
Info *types.Info // type information about the syntax trees
|
||
|
|
||
|
// Inputs provides the inputs to this analysis unit, which are
|
||
|
// the corresponding outputs of its prerequisite analysis.
|
||
|
// The map keys are the elements of Analysis.Required,
|
||
|
// and the type of each corresponding value is the required
|
||
|
// analysis's OutputType.
|
||
|
Inputs map[*Analysis]interface{}
|
||
|
|
||
|
// ObjectLemma retrieves a lemma associated with obj.
|
||
|
// Given a value ptr of type *T, where *T satisfies Lemma,
|
||
|
// ObjectLemma copies the value to *ptr.
|
||
|
//
|
||
|
// ObjectLemma may panic if applied to a lemma type that
|
||
|
// the analysis did not declare among its LemmaTypes,
|
||
|
// or if called after analysis of the unit is complete.
|
||
|
//
|
||
|
// ObjectLemma is not concurrency-safe.
|
||
|
ObjectLemma func(obj types.Object, lemma Lemma) bool
|
||
|
|
||
|
// PackageLemma retrives a lemma associated with package pkg,
|
||
|
// which must be this package or one if its dependencies.
|
||
|
// See comments for ObjectLemma.
|
||
|
PackageLemma func(pkg *types.Package, lemma Lemma) bool
|
||
|
|
||
|
// -- outputs --
|
||
|
|
||
|
// Findings is a list of findings about specific locations
|
||
|
// in the analyzed source code, such as potential mistakes.
|
||
|
// It is populated by the Run function.
|
||
|
Findings []*Finding
|
||
|
|
||
|
// SetObjectLemma associates a lemma of type *T with the obj,
|
||
|
// replacing any previous lemma of that type.
|
||
|
//
|
||
|
// SetObjectLemma panics if the lemma's type is not among
|
||
|
// Analysis.LemmaTypes, or if obj does not belong to the package
|
||
|
// being analyzed, or if it is called after analysis of the unit
|
||
|
// is complete.
|
||
|
//
|
||
|
// SetObjectLemma is not concurrency-safe.
|
||
|
SetObjectLemma func(obj types.Object, lemma Lemma)
|
||
|
|
||
|
// SetPackageLemma associates a lemma with the current package.
|
||
|
// See comments for SetObjectLemma.
|
||
|
SetPackageLemma func(lemma Lemma)
|
||
|
|
||
|
// Output is an immutable result computed by this analysis unit
|
||
|
// and set by the Run function.
|
||
|
// It will be made available as an input to any analysis that
|
||
|
// depends directly on this one; see Analysis.Requires.
|
||
|
// Its type must match Analysis.OutputType.
|
||
|
//
|
||
|
// Outputs are available as Inputs to later analyses of the
|
||
|
// same package. To pass analysis results between packages (and
|
||
|
// thus potentially between address spaces), use Lemmas, which
|
||
|
// are serializable.
|
||
|
Output interface{}
|
||
|
|
||
|
/* Further fields may be added in future. */
|
||
|
// For example, suggested or applied refactorings.
|
||
|
}
|
||
|
|
||
|
// Findingf is a helper function that creates a new Finding using the
|
||
|
// specified position and formatted error message, appends it to
|
||
|
// unit.Findings, and returns it.
|
||
|
func (unit *Unit) Findingf(pos token.Pos, format string, args ...interface{}) *Finding {
|
||
|
msg := fmt.Sprintf(format, args...)
|
||
|
f := &Finding{Pos: pos, Message: msg}
|
||
|
unit.Findings = append(unit.Findings, f)
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
func (unit *Unit) String() string {
|
||
|
return fmt.Sprintf("%s@%s", unit.Analysis.Name, unit.Pkg.Path())
|
||
|
}
|
||
|
|
||
|
// A Lemma is an intermediate fact produced during analysis.
|
||
|
//
|
||
|
// Each lemma is associated with a named declaration (a types.Object).
|
||
|
// A single object may have multiple associated lemmas, but only one of
|
||
|
// any particular lemma type.
|
||
|
//
|
||
|
// A Lemma represents a predicate such as "never returns", but does not
|
||
|
// represent the subject of the predicate such as "function F".
|
||
|
//
|
||
|
// Lemmas may be produced in one analysis unit and consumed by another
|
||
|
// analysis unit even if these are in different address spaces.
|
||
|
// If package P imports Q, all lemmas about objects of Q produced during
|
||
|
// analysis of that package will be available during later analysis of P.
|
||
|
// Lemmas are analogous to type export data in a build system:
|
||
|
// just as export data enables separate compilation of several units,
|
||
|
// lemmas enable "separate analysis".
|
||
|
//
|
||
|
// Each unit of analysis starts with the set of lemmas produced by the
|
||
|
// same analysis applied to the packages directly imported by the
|
||
|
// current one. The analysis may add additional lemmas to the set, and
|
||
|
// they may be exported in turn. An analysis's Run function may retrieve
|
||
|
// lemmas by calling Unit.Lemma and set them using Unit.SetLemma.
|
||
|
//
|
||
|
// Each type of Lemma may be produced by at most one Analysis.
|
||
|
// Lemmas are logically private to their Analysis; to pass values
|
||
|
// between different analysis, use the Input/Output mechanism.
|
||
|
//
|
||
|
// A Lemma type must be a pointer. (Unit.GetLemma relies on it.)
|
||
|
// Lemmas are encoded and decoded using encoding/gob.
|
||
|
// A Lemma may implement the GobEncoder/GobDecoder interfaces
|
||
|
// to customize its encoding; Lemma encoding should not fail.
|
||
|
//
|
||
|
// A Lemma should not be modified once passed to SetLemma.
|
||
|
type Lemma interface {
|
||
|
IsLemma() // dummy method to avoid type errors
|
||
|
}
|
||
|
|
||
|
// A Finding is a message associated with a source location.
|
||
|
//
|
||
|
// An Analysis may return a variety of findings; 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 Finding struct {
|
||
|
Pos token.Pos
|
||
|
Category string // optional
|
||
|
Message string
|
||
|
}
|