1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // The unitchecker package defines the main function for an analysis
6 // driver that analyzes a single compilation unit during a build.
7 // It is invoked by a build system such as "go vet":
9 // $ go vet -vettool=$(which vet)
11 // It supports the following command-line protocol:
13 // -V=full describe executable (to the build tool)
14 // -flags describe flags (to the build tool)
15 // foo.cfg description of compilation unit (from the build tool)
17 // This package does not depend on go/packages.
18 // If you need a standalone tool, use multichecker,
19 // which supports this mode but can also load packages
20 // from source using go/packages.
24 // - with gccgo, go build does not build standard library,
25 // so we will not get to analyze it. Yet we must in order
26 // to create base facts for, say, the fmt package for the
51 "golang.org/x/tools/go/analysis"
52 "golang.org/x/tools/go/analysis/internal/analysisflags"
53 "golang.org/x/tools/go/analysis/internal/facts"
56 // A Config describes a compilation unit to be analyzed.
57 // It is provided to the tool in a JSON-encoded file
58 // whose name ends with ".cfg".
60 ID string // e.g. "fmt [fmt.test]"
67 ImportMap map[string]string
68 PackageFile map[string]string
69 Standard map[string]bool
70 PackageVetx map[string]string
73 SucceedOnTypecheckFailure bool
76 // Main is the main function of a vet-like analysis tool that must be
77 // invoked by a build system to analyze a single package.
79 // The protocol required by 'go vet -vettool=...' is that the tool must support:
81 // -flags describe flags in JSON
82 // -V=full describe executable for build caching
83 // foo.cfg perform separate modular analyze on the single
84 // unit described by a JSON config file foo.cfg.
86 func Main(analyzers ...*analysis.Analyzer) {
87 progname := filepath.Base(os.Args[0])
89 log.SetPrefix(progname + ": ")
91 if err := analysis.Validate(analyzers); err != nil {
96 fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
99 %.16[1]s unit.cfg # execute analysis specified by config file
100 %.16[1]s help # general help
101 %.16[1]s help name # help on specific analyzer and its flags
106 analyzers = analysisflags.Parse(analyzers, true)
112 if args[0] == "help" {
113 analysisflags.Help(progname, analyzers, args[1:])
116 if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
117 log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
119 Run(args[0], analyzers)
122 // Run reads the *.cfg file, runs the analysis,
123 // and calls os.Exit with an appropriate error code.
124 // It assumes flags have already been set.
125 func Run(configFile string, analyzers []*analysis.Analyzer) {
126 cfg, err := readConfig(configFile)
131 fset := token.NewFileSet()
132 results, err := run(fset, cfg, analyzers)
137 // In VetxOnly mode, the analysis is run only for facts.
139 if analysisflags.JSON {
141 tree := make(analysisflags.JSONTree)
142 for _, res := range results {
143 tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err)
149 for _, res := range results {
155 for _, res := range results {
156 for _, diag := range res.diagnostics {
157 analysisflags.PrintPlain(fset, diag)
168 func readConfig(filename string) (*Config, error) {
169 data, err := ioutil.ReadFile(filename)
174 if err := json.Unmarshal(data, cfg); err != nil {
175 return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
177 if len(cfg.GoFiles) == 0 {
178 // The go command disallows packages with no files.
179 // The only exception is unsafe, but the go command
180 // doesn't call vet on it.
181 return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
186 var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
187 // broken legacy implementation (https://golang.org/issue/28995)
188 return importer.For(compiler, lookup)
191 func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
192 // Load, parse, typecheck.
193 var files []*ast.File
194 for _, name := range cfg.GoFiles {
195 f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
197 if cfg.SucceedOnTypecheckFailure {
198 // Silently succeed; let the compiler
199 // report parse errors.
204 files = append(files, f)
206 compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
207 // path is a resolved package path, not an import path.
208 file, ok := cfg.PackageFile[path]
210 if cfg.Compiler == "gccgo" && cfg.Standard[path] {
211 return nil, nil // fall back to default gccgo lookup
213 return nil, fmt.Errorf("no package file for %q", path)
217 importer := importerFunc(func(importPath string) (*types.Package, error) {
218 path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
220 return nil, fmt.Errorf("can't resolve import %q", path)
222 return compilerImporter.Import(path)
226 Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
229 Types: make(map[ast.Expr]types.TypeAndValue),
230 Defs: make(map[*ast.Ident]types.Object),
231 Uses: make(map[*ast.Ident]types.Object),
232 Implicits: make(map[ast.Node]types.Object),
233 Scopes: make(map[ast.Node]*types.Scope),
234 Selections: make(map[*ast.SelectorExpr]*types.Selection),
236 pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
238 if cfg.SucceedOnTypecheckFailure {
239 // Silently succeed; let the compiler
240 // report type errors.
246 // Register fact types with gob.
247 // In VetxOnly mode, analyzers are only for their facts,
248 // so we can skip any analysis that neither produces facts
249 // nor depends on any analysis that produces facts.
250 // Also build a map to hold working state and result.
255 usesFacts bool // (transitively uses)
256 diagnostics []analysis.Diagnostic
258 actions := make(map[*analysis.Analyzer]*action)
259 var registerFacts func(a *analysis.Analyzer) bool
260 registerFacts = func(a *analysis.Analyzer) bool {
261 act, ok := actions[a]
265 for _, f := range a.FactTypes {
269 for _, req := range a.Requires {
270 if registerFacts(req) {
274 act.usesFacts = usesFacts
279 var filtered []*analysis.Analyzer
280 for _, a := range analyzers {
281 if registerFacts(a) || !cfg.VetxOnly {
282 filtered = append(filtered, a)
287 // Read facts from imported packages.
288 read := func(path string) ([]byte, error) {
289 if vetx, ok := cfg.PackageVetx[path]; ok {
290 return ioutil.ReadFile(vetx)
292 return nil, nil // no .vetx file, no facts
294 facts, err := facts.Decode(pkg, read)
299 // In parallel, execute the DAG of analyzers.
300 var exec func(a *analysis.Analyzer) *action
301 var execAll func(analyzers []*analysis.Analyzer)
302 exec = func(a *analysis.Analyzer) *action {
305 execAll(a.Requires) // prefetch dependencies in parallel
307 // The inputs to this analysis are the
308 // results of its prerequisites.
309 inputs := make(map[*analysis.Analyzer]interface{})
311 for _, req := range a.Requires {
313 if reqact.err != nil {
314 failed = append(failed, req.String())
317 inputs[req] = reqact.result
320 // Report an error if any dependency failed.
323 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
327 factFilter := make(map[reflect.Type]bool)
328 for _, f := range a.FactTypes {
329 factFilter[reflect.TypeOf(f)] = true
332 pass := &analysis.Pass{
336 OtherFiles: cfg.NonGoFiles,
337 IgnoredFiles: cfg.IgnoredFiles,
340 TypesSizes: tc.Sizes,
342 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
343 ImportObjectFact: facts.ImportObjectFact,
344 ExportObjectFact: facts.ExportObjectFact,
345 AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
346 ImportPackageFact: facts.ImportPackageFact,
347 ExportPackageFact: facts.ExportPackageFact,
348 AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
352 act.result, act.err = a.Run(pass)
354 log.Printf("analysis %s = %s", pass, time.Since(t0))
359 execAll = func(analyzers []*analysis.Analyzer) {
360 var wg sync.WaitGroup
361 for _, a := range analyzers {
363 go func(a *analysis.Analyzer) {
373 // Return diagnostics and errors from root analyzers.
374 results := make([]result, len(analyzers))
375 for i, a := range analyzers {
378 results[i].err = act.err
379 results[i].diagnostics = act.diagnostics
382 data := facts.Encode()
383 if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil {
384 return nil, fmt.Errorf("failed to write analysis facts: %v", err)
392 diagnostics []analysis.Diagnostic
396 type importerFunc func(path string) (*types.Package, error)
398 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }