Go Analysis Framework: modular static analysis by go team
Go Analysis Framework: Modular Static Analysis by the Go Team
The analysis package, located at golang.org/x/tools/go/analysis, provides a standardized framework for building modular static analysis tools for the Go language.
📦 Package Metadata
| Attribute | Detail |
|---|---|
| Current Version | v0.48.0 (Note: Not the latest version in module) |
| Release Date | July 9, 2026 |
| License | BSD-3-Clause (Redistributable) |
| Adoption | Imported by 6,564 projects |
| Repository | cs.opensource.google/go/x/tools |
📖 Overview
The analysis package acts as the bridge between a modular static analysis (the logic) and an analysis driver (the program that executes that logic).
Background Concepts
At its core, a static analysis is a function that examines a Go package to identify diagnostics—usually bugs or stylistic errors—and may generate suggested fixes or "facts" about the code.
What is a "Checker"? An analysis specifically designed to find mistakes is informally known as a checker. For instance, the
printfchecker validates that format strings infmt.Printfare used correctly.
What is "Modular" Analysis? A modular analysis processes one package at a time but can persist information from lower-level packages to be utilized when analyzing higher-level packages. This is conceptually similar to how separate compilation works in a traditional toolchain.
Example of Modularity:
The printf checker is modular. If it detects that log.Fatalf internally calls fmt.Printf, it records this as a fact. Later, when analyzing other packages that call log.Fatalf, it knows to apply the same format string checks.
Ecosystem Integration
By utilizing a shared interface, checkers can be integrated into various environments:
- CLI Tools: Such as
go vet. - Editors/IDEs: Real-time linting in VS Code or GoLand.
- Build Systems: Integration with
go build,Bazel, orBuck. - Code Review & Indexing: Tools like
SourceGraphorgodoc. - CI/CD: Batch pipelines for massive monorepos.
🛠 The Analyzer
An Analyzer is a static description of an analysis function. It encapsulates the logic, documentation, configuration flags, and dependencies.
Defining an Analyzer
Users typically define an analysis by declaring a constant variable of type analysis.Analyzer.
package unusedresult
import "golang.org/x/tools/go/analysis"
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
Doc: "check for unused results of calls to some functions",
Run: run,
// ... other fields
}
The Analyzer Structure
The Analyzer struct contains the following fields:
| Field | Type | Description |
|---|---|---|
Name | string | The unique identifier of the analysis. |
Doc | string | A brief summary and optional detailed explanation. |
Flags | flag.FlagSet | Global flags to control behavior. |
Run | func(*Pass) (interface{}, error) | The core logic executed per package. |
RunDespiteErrors | bool | If true, runs even on ill-typed code. |
ResultType | reflect.Type | The type of value returned for other analyzers. |
Requires | []*Analyzer | A list of dependencies that must run first. |
FactTypes | []Fact | Definitions for modular facts. |
Key Technical Details
- Flags: Unlike
vet, flags aren't hardcoded into the CLI. The driver decides how to expose them (e.g., as-f,-analysis.f, or via a GUI/config file). - Dependencies: The
Requiresfield creates a dependency graph. To ensure the driver can execute these, the graph must be a Directed Acyclic Graph (DAG), represented mathematically as: - Validation: The
analysis.Validatefunction performs sanity checks:- Verify the
Requiresgraph is acyclic. - Ensure
FactTypesandResultTypeare unique.
- Verify the
🚀 The Driver
The driver is the execution engine (e.g., vet). It imports the necessary Analyzers and manages their lifecycle.
Adding analyzers to a driver:
import (
"unusedresult"
"nilness"
"printf"
)
var analyses = []*analysis.Analyzer{
unusedresult.Analyzer,
nilness.Analyzer,
printf.Analyzer,
}
The driver is responsible for parsing the Doc strings to provide help menus and mapping the Flags to the user's input method.
📦 The Pass
A Pass represents a single unit of work: one Analyzer applied to one package. It provides the Run function with the necessary context and a way to report findings.
Pass Structure
type Pass struct {
Fset *token.FileSet
Files []*ast.File
OtherFiles []string
IgnoredFiles []string
Pkg *types.Package
TypesInfo *types.Info
ResultOf map[*Analyzer]interface{}
Report func(Diagnostic)
}
Workflow Visualization
Summary of Pass Fields
- Code Access:
FsetandFilesprovide the AST (Abstract Syntax Tree). - Type Information:
PkgandTypesInfoallow for deep type analysis. - Inter-Analyzer Communication:
ResultOfallows an analyzer to access the output of its dependencies. - Feedback Loop: The
Reportfunction is used to send diagnostics back to the driver.
Static analysis is just a simple script Static analysis is a sophisticated, modular framework.