GoPLS Viewer

Home|gopls/go/analysis/unitchecker/unitchecker.go
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.
4
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":
8//
9//    $ go vet -vettool=$(which vet)
10//
11// It supports the following command-line protocol:
12//
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)
16//
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.
21package unitchecker
22
23// TODO(adonovan):
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
27//   printf checker.
28
29import (
30    "encoding/gob"
31    "encoding/json"
32    "flag"
33    "fmt"
34    "go/ast"
35    "go/build"
36    "go/importer"
37    "go/parser"
38    "go/token"
39    "go/types"
40    "io"
41    "io/ioutil"
42    "log"
43    "os"
44    "path/filepath"
45    "reflect"
46    "sort"
47    "strings"
48    "sync"
49    "time"
50
51    "golang.org/x/tools/go/analysis"
52    "golang.org/x/tools/go/analysis/internal/analysisflags"
53    "golang.org/x/tools/internal/facts"
54    "golang.org/x/tools/internal/typeparams"
55)
56
57// A Config describes a compilation unit to be analyzed.
58// It is provided to the tool in a JSON-encoded file
59// whose name ends with ".cfg".
60type Config struct {
61    ID                        string // e.g. "fmt [fmt.test]"
62    Compiler                  string
63    Dir                       string
64    ImportPath                string
65    GoFiles                   []string
66    NonGoFiles                []string
67    IgnoredFiles              []string
68    ImportMap                 map[string]string
69    PackageFile               map[string]string
70    Standard                  map[string]bool
71    PackageVetx               map[string]string
72    VetxOnly                  bool
73    VetxOutput                string
74    SucceedOnTypecheckFailure bool
75}
76
77// Main is the main function of a vet-like analysis tool that must be
78// invoked by a build system to analyze a single package.
79//
80// The protocol required by 'go vet -vettool=...' is that the tool must support:
81//
82//    -flags          describe flags in JSON
83//    -V=full         describe executable for build caching
84//    foo.cfg         perform separate modular analyze on the single
85//                    unit described by a JSON config file foo.cfg.
86func Main(analyzers ...*analysis.Analyzer) {
87    progname := filepath.Base(os.Args[0])
88    log.SetFlags(0)
89    log.SetPrefix(progname + ": ")
90
91    if err := analysis.Validate(analyzers); err != nil {
92        log.Fatal(err)
93    }
94
95    flag.Usage = func() {
96        fmt.Fprintf(os.Stderr`%[1]s is a tool for static analysis of Go programs.
97
98Usage of %[1]s:
99    %.16[1]s unit.cfg    # execute analysis specified by config file
100    %.16[1]s help        # general help, including listing analyzers and flags
101    %.16[1]s help name    # help on specific analyzer and its flags
102`progname)
103        os.Exit(1)
104    }
105
106    analyzers = analysisflags.Parse(analyzerstrue)
107
108    args := flag.Args()
109    if len(args) == 0 {
110        flag.Usage()
111    }
112    if args[0] == "help" {
113        analysisflags.Help(prognameanalyzersargs[1:])
114        os.Exit(0)
115    }
116    if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
117        log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
118    }
119    Run(args[0], analyzers)
120}
121
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.
125func Run(configFile stringanalyzers []*analysis.Analyzer) {
126    cfgerr := readConfig(configFile)
127    if err != nil {
128        log.Fatal(err)
129    }
130
131    fset := token.NewFileSet()
132    resultserr := run(fsetcfganalyzers)
133    if err != nil {
134        log.Fatal(err)
135    }
136
137    // In VetxOnly mode, the analysis is run only for facts.
138    if !cfg.VetxOnly {
139        if analysisflags.JSON {
140            // JSON output
141            tree := make(analysisflags.JSONTree)
142            for _res := range results {
143                tree.Add(fsetcfg.IDres.a.Nameres.diagnosticsres.err)
144            }
145            tree.Print()
146        } else {
147            // plain text
148            exit := 0
149            for _res := range results {
150                if res.err != nil {
151                    log.Println(res.err)
152                    exit = 1
153                }
154            }
155            for _res := range results {
156                for _diag := range res.diagnostics {
157                    analysisflags.PrintPlain(fsetdiag)
158                    exit = 1
159                }
160            }
161            os.Exit(exit)
162        }
163    }
164
165    os.Exit(0)
166}
167
168func readConfig(filename string) (*Configerror) {
169    dataerr := ioutil.ReadFile(filename)
170    if err != nil {
171        return nilerr
172    }
173    cfg := new(Config)
174    if err := json.Unmarshal(datacfg); err != nil {
175        return nilfmt.Errorf("cannot decode JSON config file %s: %v"filenameerr)
176    }
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 nilfmt.Errorf("package has no files: %s"cfg.ImportPath)
182    }
183    return cfgnil
184}
185
186var importerForCompiler = func(_ *token.FileSetcompiler stringlookup importer.Lookuptypes.Importer {
187    // broken legacy implementation (https://golang.org/issue/28995)
188    return importer.For(compilerlookup)
189}
190
191func run(fset *token.FileSetcfg *Configanalyzers []*analysis.Analyzer) ([]resulterror) {
192    // Load, parse, typecheck.
193    var files []*ast.File
194    for _name := range cfg.GoFiles {
195        ferr := parser.ParseFile(fsetnamenilparser.ParseComments)
196        if err != nil {
197            if cfg.SucceedOnTypecheckFailure {
198                // Silently succeed; let the compiler
199                // report parse errors.
200                err = nil
201            }
202            return nilerr
203        }
204        files = append(filesf)
205    }
206    compilerImporter := importerForCompiler(fsetcfg.Compiler, func(path string) (io.ReadClosererror) {
207        // path is a resolved package path, not an import path.
208        fileok := cfg.PackageFile[path]
209        if !ok {
210            if cfg.Compiler == "gccgo" && cfg.Standard[path] {
211                return nilnil // fall back to default gccgo lookup
212            }
213            return nilfmt.Errorf("no package file for %q"path)
214        }
215        return os.Open(file)
216    })
217    importer := importerFunc(func(importPath string) (*types.Packageerror) {
218        pathok := cfg.ImportMap[importPath// resolve vendoring, etc
219        if !ok {
220            return nilfmt.Errorf("can't resolve import %q"path)
221        }
222        return compilerImporter.Import(path)
223    })
224    tc := &types.Config{
225        Importerimporter,
226        Sizes:    types.SizesFor("gc"build.Default.GOARCH), // assume gccgo ≡ gc?
227    }
228    info := &types.Info{
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        Selectionsmake(map[*ast.SelectorExpr]*types.Selection),
235    }
236    typeparams.InitInstanceInfo(info)
237
238    pkgerr := tc.Check(cfg.ImportPathfsetfilesinfo)
239    if err != nil {
240        if cfg.SucceedOnTypecheckFailure {
241            // Silently succeed; let the compiler
242            // report type errors.
243            err = nil
244        }
245        return nilerr
246    }
247
248    // Register fact types with gob.
249    // In VetxOnly mode, analyzers are only for their facts,
250    // so we can skip any analysis that neither produces facts
251    // nor depends on any analysis that produces facts.
252    //
253    // TODO(adonovan): fix: the command (and logic!) here are backwards.
254    // It should say "...nor is required by any...". (Issue 443099)
255    //
256    // Also build a map to hold working state and result.
257    type action struct {
258        once        sync.Once
259        result      interface{}
260        err         error
261        usesFacts   bool // (transitively uses)
262        diagnostics []analysis.Diagnostic
263    }
264    actions := make(map[*analysis.Analyzer]*action)
265    var registerFacts func(a *analysis.Analyzerbool
266    registerFacts = func(a *analysis.Analyzerbool {
267        actok := actions[a]
268        if !ok {
269            act = new(action)
270            var usesFacts bool
271            for _f := range a.FactTypes {
272                usesFacts = true
273                gob.Register(f)
274            }
275            for _req := range a.Requires {
276                if registerFacts(req) {
277                    usesFacts = true
278                }
279            }
280            act.usesFacts = usesFacts
281            actions[a] = act
282        }
283        return act.usesFacts
284    }
285    var filtered []*analysis.Analyzer
286    for _a := range analyzers {
287        if registerFacts(a) || !cfg.VetxOnly {
288            filtered = append(filtereda)
289        }
290    }
291    analyzers = filtered
292
293    // Read facts from imported packages.
294    read := func(imp *types.Package) ([]byteerror) {
295        if vetxok := cfg.PackageVetx[imp.Path()]; ok {
296            return ioutil.ReadFile(vetx)
297        }
298        return nilnil // no .vetx file, no facts
299    }
300    factserr := facts.NewDecoder(pkg).Decode(read)
301    if err != nil {
302        return nilerr
303    }
304
305    // In parallel, execute the DAG of analyzers.
306    var exec func(a *analysis.Analyzer) *action
307    var execAll func(analyzers []*analysis.Analyzer)
308    exec = func(a *analysis.Analyzer) *action {
309        act := actions[a]
310        act.once.Do(func() {
311            execAll(a.Requires// prefetch dependencies in parallel
312
313            // The inputs to this analysis are the
314            // results of its prerequisites.
315            inputs := make(map[*analysis.Analyzer]interface{})
316            var failed []string
317            for _req := range a.Requires {
318                reqact := exec(req)
319                if reqact.err != nil {
320                    failed = append(failedreq.String())
321                    continue
322                }
323                inputs[req] = reqact.result
324            }
325
326            // Report an error if any dependency failed.
327            if failed != nil {
328                sort.Strings(failed)
329                act.err = fmt.Errorf("failed prerequisites: %s"strings.Join(failed", "))
330                return
331            }
332
333            factFilter := make(map[reflect.Type]bool)
334            for _f := range a.FactTypes {
335                factFilter[reflect.TypeOf(f)] = true
336            }
337
338            pass := &analysis.Pass{
339                Analyzer:          a,
340                Fset:              fset,
341                Files:             files,
342                OtherFiles:        cfg.NonGoFiles,
343                IgnoredFiles:      cfg.IgnoredFiles,
344                Pkg:               pkg,
345                TypesInfo:         info,
346                TypesSizes:        tc.Sizes,
347                TypeErrors:        nil// unitchecker doesn't RunDespiteErrors
348                ResultOf:          inputs,
349                Report:            func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnosticsd) },
350                ImportObjectFact:  facts.ImportObjectFact,
351                ExportObjectFact:  facts.ExportObjectFact,
352                AllObjectFacts:    func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
353                ImportPackageFactfacts.ImportPackageFact,
354                ExportPackageFactfacts.ExportPackageFact,
355                AllPackageFacts:   func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
356            }
357
358            t0 := time.Now()
359            act.resultact.err = a.Run(pass)
360            if false {
361                log.Printf("analysis %s = %s"passtime.Since(t0))
362            }
363        })
364        return act
365    }
366    execAll = func(analyzers []*analysis.Analyzer) {
367        var wg sync.WaitGroup
368        for _a := range analyzers {
369            wg.Add(1)
370            go func(a *analysis.Analyzer) {
371                _ = exec(a)
372                wg.Done()
373            }(a)
374        }
375        wg.Wait()
376    }
377
378    execAll(analyzers)
379
380    // Return diagnostics and errors from root analyzers.
381    results := make([]resultlen(analyzers))
382    for ia := range analyzers {
383        act := actions[a]
384        results[i].a = a
385        results[i].err = act.err
386        results[i].diagnostics = act.diagnostics
387    }
388
389    data := facts.Encode()
390    if err := ioutil.WriteFile(cfg.VetxOutputdata0666); err != nil {
391        return nilfmt.Errorf("failed to write analysis facts: %v"err)
392    }
393
394    return resultsnil
395}
396
397type result struct {
398    a           *analysis.Analyzer
399    diagnostics []analysis.Diagnostic
400    err         error
401}
402
403type importerFunc func(path string) (*types.Packageerror)
404
405func (f importerFuncImport(path string) (*types.Packageerror) { return f(path) }
406
MembersX
time
Main.progname
gob
readConfig.data
json
Config.PackageVetx
Run.err
Run.BlockStmt.BlockStmt.tree
run.compilerImporter
Config.SucceedOnTypecheckFailure
Config.GoFiles
run.analyzers
typeparams
Run.BlockStmt.BlockStmt.RangeStmt_4292.res
io
run.pkg
run.BlockStmt.BlockStmt.RangeStmt_9591.f
Run.BlockStmt.BlockStmt.RangeStmt_4400.BlockStmt.RangeStmt_4434.diag
readConfig.err
result.diagnostics
run.RangeStmt_5484.name
run.BlockStmt.RangeStmt_10774.a
Main.args
run.BlockStmt.BlockStmt.RangeStmt_9165.req
os
facts
Run.configFile
readConfig.cfg
run.actions
importerFunc.Import.f
ast
Config.Compiler
run.fset
run.data
reflect
Config.Dir
Config.ImportPath
importer
parser
strings
Config.NonGoFiles
run.err
run.BlockStmt.BlockStmt.RangeStmt_8057.req
run.RangeStmt_8266.a
run.BlockStmt.BlockStmt.t0
Main.err
run.info
run.action.usesFacts
analysisflags
Run.BlockStmt.BlockStmt.exit
run
run.filtered
run.BlockStmt.BlockStmt.pass
Run.cfg
Run.fset
types
Config.PackageFile
flag
token
run.BlockStmt.BlockStmt.RangeStmt_9165.BlockStmt.reqact
result.err
Config.ID
run.action.err
Run.results
run.RangeStmt_11030.a
result
log
ioutil
Config
Config.ImportMap
Config.VetxOutput
Main
Run.BlockStmt.BlockStmt.RangeStmt_4400.res
filepath
run.BlockStmt.BlockStmt.usesFacts
run.BlockStmt.BlockStmt.RangeStmt_7976.f
run.BlockStmt.BlockStmt.factFilter
fmt
run.action.result
run.RangeStmt_11030.i
importerFunc.Import.path
Config.Standard
run.results
run.action.diagnostics
run.files
run.importer
analysis
result.a
Config.VetxOnly
readConfig
run.RangeStmt_5484.BlockStmt.f
importerFunc.Import
sort
Config.IgnoredFiles
run.tc
run.BlockStmt.wg
Run.analyzers
Run.BlockStmt.BlockStmt.RangeStmt_4132.res
run.BlockStmt.BlockStmt.inputs
Run
readConfig.filename
run.action
importerFunc
build
sync
run.cfg
run.RangeStmt_5484.BlockStmt.err
run.action.once
run.facts
run.BlockStmt.BlockStmt.failed
Main.analyzers
Members
X