// 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 }