1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package unitchecker
22
23
24
25
26
27
28
29 import (
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 "log"
42 "os"
43 "path/filepath"
44 "reflect"
45 "sort"
46 "strings"
47 "sync"
48 "time"
49
50 "golang.org/x/tools/go/analysis"
51 "golang.org/x/tools/go/analysis/internal/analysisflags"
52 "golang.org/x/tools/internal/analysisinternal"
53 "golang.org/x/tools/internal/facts"
54 )
55
56
57
58
59 type Config struct {
60 ID string
61 Compiler string
62 Dir string
63 ImportPath string
64 GoVersion string
65 GoFiles []string
66 NonGoFiles []string
67 IgnoredFiles []string
68 ModulePath string
69 ModuleVersion string
70 ImportMap map[string]string
71 PackageFile map[string]string
72 Standard map[string]bool
73 PackageVetx map[string]string
74 VetxOnly bool
75 VetxOutput string
76 SucceedOnTypecheckFailure bool
77 }
78
79
80
81
82
83
84
85
86
87
88 func Main(analyzers ...*analysis.Analyzer) {
89 progname := filepath.Base(os.Args[0])
90 log.SetFlags(0)
91 log.SetPrefix(progname + ": ")
92
93 if err := analysis.Validate(analyzers); err != nil {
94 log.Fatal(err)
95 }
96
97 flag.Usage = func() {
98 fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
99
100 Usage of %[1]s:
101 %.16[1]s unit.cfg # execute analysis specified by config file
102 %.16[1]s help # general help, including listing analyzers and flags
103 %.16[1]s help name # help on specific analyzer and its flags
104 `, progname)
105 os.Exit(1)
106 }
107
108 analyzers = analysisflags.Parse(analyzers, true)
109
110 args := flag.Args()
111 if len(args) == 0 {
112 flag.Usage()
113 }
114 if args[0] == "help" {
115 analysisflags.Help(progname, analyzers, args[1:])
116 os.Exit(0)
117 }
118 if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
119 log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
120 }
121 Run(args[0], analyzers)
122 }
123
124
125
126
127 func Run(configFile string, analyzers []*analysis.Analyzer) {
128 cfg, err := readConfig(configFile)
129 if err != nil {
130 log.Fatal(err)
131 }
132
133 fset := token.NewFileSet()
134 results, err := run(fset, cfg, analyzers)
135 if err != nil {
136 log.Fatal(err)
137 }
138
139
140 if !cfg.VetxOnly {
141 if analysisflags.JSON {
142
143 tree := make(analysisflags.JSONTree)
144 for _, res := range results {
145 tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err)
146 }
147 tree.Print(os.Stdout)
148 } else {
149
150 exit := 0
151 for _, res := range results {
152 if res.err != nil {
153 log.Println(res.err)
154 exit = 1
155 }
156 }
157 for _, res := range results {
158 for _, diag := range res.diagnostics {
159 analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
160 exit = 1
161 }
162 }
163 os.Exit(exit)
164 }
165 }
166
167 os.Exit(0)
168 }
169
170 func readConfig(filename string) (*Config, error) {
171 data, err := os.ReadFile(filename)
172 if err != nil {
173 return nil, err
174 }
175 cfg := new(Config)
176 if err := json.Unmarshal(data, cfg); err != nil {
177 return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
178 }
179 if len(cfg.GoFiles) == 0 {
180
181
182
183 return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
184 }
185 return cfg, nil
186 }
187
188 type factImporter = func(pkgPath string) ([]byte, error)
189
190
191
192
193
194
195
196 var (
197 makeTypesImporter = func(cfg *Config, fset *token.FileSet) types.Importer {
198 compilerImporter := importer.ForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
199
200 file, ok := cfg.PackageFile[path]
201 if !ok {
202 if cfg.Compiler == "gccgo" && cfg.Standard[path] {
203 return nil, nil
204 }
205 return nil, fmt.Errorf("no package file for %q", path)
206 }
207 return os.Open(file)
208 })
209 return importerFunc(func(importPath string) (*types.Package, error) {
210 path, ok := cfg.ImportMap[importPath]
211 if !ok {
212 return nil, fmt.Errorf("can't resolve import %q", path)
213 }
214 return compilerImporter.Import(path)
215 })
216 }
217
218 exportTypes = func(*Config, *token.FileSet, *types.Package) error {
219
220
221 return nil
222 }
223
224 makeFactImporter = func(cfg *Config) factImporter {
225 return func(pkgPath string) ([]byte, error) {
226 if vetx, ok := cfg.PackageVetx[pkgPath]; ok {
227 return os.ReadFile(vetx)
228 }
229 return nil, nil
230 }
231 }
232
233 exportFacts = func(cfg *Config, data []byte) error {
234 return os.WriteFile(cfg.VetxOutput, data, 0666)
235 }
236 )
237
238 func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
239
240 var files []*ast.File
241 for _, name := range cfg.GoFiles {
242 f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
243 if err != nil {
244 if cfg.SucceedOnTypecheckFailure {
245
246
247 err = nil
248 }
249 return nil, err
250 }
251 files = append(files, f)
252 }
253 tc := &types.Config{
254 Importer: makeTypesImporter(cfg, fset),
255 Sizes: types.SizesFor("gc", build.Default.GOARCH),
256 GoVersion: cfg.GoVersion,
257 }
258 info := &types.Info{
259 Types: make(map[ast.Expr]types.TypeAndValue),
260 Defs: make(map[*ast.Ident]types.Object),
261 Uses: make(map[*ast.Ident]types.Object),
262 Implicits: make(map[ast.Node]types.Object),
263 Instances: make(map[*ast.Ident]types.Instance),
264 Scopes: make(map[ast.Node]*types.Scope),
265 Selections: make(map[*ast.SelectorExpr]*types.Selection),
266 FileVersions: make(map[*ast.File]string),
267 }
268
269 pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
270 if err != nil {
271 if cfg.SucceedOnTypecheckFailure {
272
273
274 err = nil
275 }
276 return nil, err
277 }
278
279
280
281
282
283
284
285
286
287
288 type action struct {
289 once sync.Once
290 result interface{}
291 err error
292 usesFacts bool
293 diagnostics []analysis.Diagnostic
294 }
295 actions := make(map[*analysis.Analyzer]*action)
296 var registerFacts func(a *analysis.Analyzer) bool
297 registerFacts = func(a *analysis.Analyzer) bool {
298 act, ok := actions[a]
299 if !ok {
300 act = new(action)
301 var usesFacts bool
302 for _, f := range a.FactTypes {
303 usesFacts = true
304 gob.Register(f)
305 }
306 for _, req := range a.Requires {
307 if registerFacts(req) {
308 usesFacts = true
309 }
310 }
311 act.usesFacts = usesFacts
312 actions[a] = act
313 }
314 return act.usesFacts
315 }
316 var filtered []*analysis.Analyzer
317 for _, a := range analyzers {
318 if registerFacts(a) || !cfg.VetxOnly {
319 filtered = append(filtered, a)
320 }
321 }
322 analyzers = filtered
323
324
325 facts, err := facts.NewDecoder(pkg).Decode(makeFactImporter(cfg))
326 if err != nil {
327 return nil, err
328 }
329
330
331 var exec func(a *analysis.Analyzer) *action
332 var execAll func(analyzers []*analysis.Analyzer)
333 exec = func(a *analysis.Analyzer) *action {
334 act := actions[a]
335 act.once.Do(func() {
336 execAll(a.Requires)
337
338
339
340 inputs := make(map[*analysis.Analyzer]interface{})
341 var failed []string
342 for _, req := range a.Requires {
343 reqact := exec(req)
344 if reqact.err != nil {
345 failed = append(failed, req.String())
346 continue
347 }
348 inputs[req] = reqact.result
349 }
350
351
352 if failed != nil {
353 sort.Strings(failed)
354 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
355 return
356 }
357
358 factFilter := make(map[reflect.Type]bool)
359 for _, f := range a.FactTypes {
360 factFilter[reflect.TypeOf(f)] = true
361 }
362
363 module := &analysis.Module{
364 Path: cfg.ModulePath,
365 Version: cfg.ModuleVersion,
366 GoVersion: cfg.GoVersion,
367 }
368
369 pass := &analysis.Pass{
370 Analyzer: a,
371 Fset: fset,
372 Files: files,
373 OtherFiles: cfg.NonGoFiles,
374 IgnoredFiles: cfg.IgnoredFiles,
375 Pkg: pkg,
376 TypesInfo: info,
377 TypesSizes: tc.Sizes,
378 TypeErrors: nil,
379 ResultOf: inputs,
380 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
381 ImportObjectFact: facts.ImportObjectFact,
382 ExportObjectFact: facts.ExportObjectFact,
383 AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
384 ImportPackageFact: facts.ImportPackageFact,
385 ExportPackageFact: facts.ExportPackageFact,
386 AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
387 Module: module,
388 }
389 pass.ReadFile = analysisinternal.MakeReadFile(pass)
390
391 t0 := time.Now()
392 act.result, act.err = a.Run(pass)
393
394 if act.err == nil {
395 for i := range act.diagnostics {
396 if url, uerr := analysisflags.ResolveURL(a, act.diagnostics[i]); uerr == nil {
397 act.diagnostics[i].URL = url
398 } else {
399 act.err = uerr
400 }
401 }
402 }
403 if false {
404 log.Printf("analysis %s = %s", pass, time.Since(t0))
405 }
406 })
407 return act
408 }
409 execAll = func(analyzers []*analysis.Analyzer) {
410 var wg sync.WaitGroup
411 for _, a := range analyzers {
412 wg.Add(1)
413 go func(a *analysis.Analyzer) {
414 _ = exec(a)
415 wg.Done()
416 }(a)
417 }
418 wg.Wait()
419 }
420
421 execAll(analyzers)
422
423
424 results := make([]result, len(analyzers))
425 for i, a := range analyzers {
426 act := actions[a]
427 results[i].a = a
428 results[i].err = act.err
429 results[i].diagnostics = act.diagnostics
430 }
431
432 data := facts.Encode()
433 if err := exportFacts(cfg, data); err != nil {
434 return nil, fmt.Errorf("failed to export analysis facts: %v", err)
435 }
436 if err := exportTypes(cfg, fset, pkg); err != nil {
437 return nil, fmt.Errorf("failed to export type information: %v", err)
438 }
439
440 return results, nil
441 }
442
443 type result struct {
444 a *analysis.Analyzer
445 diagnostics []analysis.Diagnostic
446 err error
447 }
448
449 type importerFunc func(path string) (*types.Package, error)
450
451 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
452
View as plain text