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 "archive/zip"
31 "encoding/gob"
32 "encoding/json"
33 "flag"
34 "fmt"
35 "go/ast"
36 "go/build"
37 "go/importer"
38 "go/parser"
39 "go/token"
40 "go/types"
41 "io"
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/analysis/driverutil"
54 "golang.org/x/tools/internal/facts"
55 )
56
57
58
59
60 type Config struct {
61 ID string
62 Compiler string
63 Dir string
64 ImportPath string
65 GoVersion string
66 GoFiles []string
67 NonGoFiles []string
68 IgnoredFiles []string
69 ModulePath string
70 ModuleVersion string
71 ImportMap map[string]string
72 PackageFile map[string]string
73 Standard map[string]bool
74 PackageVetx map[string]string
75 VetxOnly bool
76 VetxOutput string
77 Stdout string
78 FixArchive string
79 SucceedOnTypecheckFailure bool
80 }
81
82
83
84
85
86
87
88
89
90
91
92
93
94 func Main(analyzers ...*analysis.Analyzer) {
95 progname := filepath.Base(os.Args[0])
96 log.SetFlags(0)
97 log.SetPrefix(progname + ": ")
98
99 if err := analysis.Validate(analyzers); err != nil {
100 log.Fatal(err)
101 }
102
103 flag.Usage = func() {
104 fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
105
106 Usage of %[1]s:
107 %.16[1]s unit.cfg # execute analysis specified by config file
108 %.16[1]s help # general help, including listing analyzers and flags
109 %.16[1]s help name # help on specific analyzer and its flags
110 `, progname)
111 os.Exit(1)
112 }
113
114 analyzers = analysisflags.Parse(analyzers, true)
115
116 args := flag.Args()
117 if len(args) == 0 {
118 flag.Usage()
119 }
120 if args[0] == "help" {
121 analysisflags.Help(progname, analyzers, args[1:])
122 os.Exit(0)
123 }
124 if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
125 log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
126 }
127 Run(args[0], analyzers)
128 }
129
130
131
132
133 func Run(configFile string, analyzers []*analysis.Analyzer) {
134 cfg, err := readConfig(configFile)
135 if err != nil {
136 log.Fatal(err)
137 }
138
139
140 if cfg.Stdout != "" {
141 f, err := os.Create(cfg.Stdout)
142 if err != nil {
143 log.Fatal(err)
144 }
145 os.Stdout = f
146 }
147
148 fset := token.NewFileSet()
149 results, err := run(fset, cfg, analyzers)
150 if err != nil {
151 log.Fatal(err)
152 }
153
154 code := 0
155
156
157 if !cfg.VetxOnly {
158 code = processResults(fset, cfg.ID, cfg.FixArchive, results)
159 }
160
161 os.Exit(code)
162 }
163
164 func readConfig(filename string) (*Config, error) {
165 data, err := os.ReadFile(filename)
166 if err != nil {
167 return nil, err
168 }
169 cfg := new(Config)
170 if err := json.Unmarshal(data, cfg); err != nil {
171 return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
172 }
173 if len(cfg.GoFiles) == 0 {
174
175
176
177 return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
178 }
179 return cfg, nil
180 }
181
182 func processResults(fset *token.FileSet, id, fixArchive string, results []result) (exit int) {
183 if analysisflags.Fix {
184
185
186
187
188 fixActions := make([]driverutil.FixAction, len(results))
189 for i, res := range results {
190 fixActions[i] = driverutil.FixAction{
191 Name: res.a.Name,
192 Pkg: res.pkg,
193 Files: res.files,
194 FileSet: fset,
195 ReadFileFunc: os.ReadFile,
196 Diagnostics: res.diagnostics,
197 }
198 }
199
200
201
202
203
204 write := func(filename string, content []byte) error {
205 return os.WriteFile(filename, content, 0644)
206 }
207 if fixArchive != "" {
208 f, err := os.Create(fixArchive)
209 if err != nil {
210 log.Fatalf("can't create -fix archive: %v", err)
211 }
212 zw := zip.NewWriter(f)
213 zw.SetComment(id)
214 defer func() {
215 if err := zw.Close(); err != nil {
216 log.Fatalf("closing -fix archive zip writer: %v", err)
217 }
218 if err := f.Close(); err != nil {
219 log.Fatalf("closing -fix archive file: %v", err)
220 }
221 }()
222 write = func(filename string, content []byte) error {
223 f, err := zw.Create(filename)
224 if err != nil {
225 return err
226 }
227 _, err = f.Write(content)
228 return err
229 }
230 }
231
232 if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, false); err != nil {
233
234 log.Print(err)
235 exit = 1
236 }
237
238
239
240
241 return
242 }
243
244
245
246
247 if analysisflags.JSON {
248
249 tree := make(driverutil.JSONTree)
250 for _, res := range results {
251 tree.Add(fset, id, res.a.Name, res.diagnostics, res.err)
252 }
253 tree.Print(os.Stdout)
254
255 } else {
256
257 for _, res := range results {
258 if res.err != nil {
259 log.Println(res.err)
260 exit = 1
261 }
262 }
263 for _, res := range results {
264 for _, diag := range res.diagnostics {
265 driverutil.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
266 exit = 1
267 }
268 }
269 }
270
271 return
272 }
273
274 type factImporter = func(pkgPath string) ([]byte, error)
275
276
277
278
279
280
281
282 var (
283 makeTypesImporter = func(cfg *Config, fset *token.FileSet) types.Importer {
284 compilerImporter := importer.ForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
285
286 file, ok := cfg.PackageFile[path]
287 if !ok {
288 if cfg.Compiler == "gccgo" && cfg.Standard[path] {
289 return nil, nil
290 }
291 return nil, fmt.Errorf("no package file for %q", path)
292 }
293 return os.Open(file)
294 })
295 return importerFunc(func(importPath string) (*types.Package, error) {
296 path, ok := cfg.ImportMap[importPath]
297 if !ok {
298 return nil, fmt.Errorf("can't resolve import %q", path)
299 }
300 return compilerImporter.Import(path)
301 })
302 }
303
304 exportTypes = func(*Config, *token.FileSet, *types.Package) error {
305
306
307 return nil
308 }
309
310 makeFactImporter = func(cfg *Config) factImporter {
311 return func(pkgPath string) ([]byte, error) {
312 if vetx, ok := cfg.PackageVetx[pkgPath]; ok {
313 return os.ReadFile(vetx)
314 }
315 return nil, nil
316 }
317 }
318
319 exportFacts = func(cfg *Config, data []byte) error {
320 return os.WriteFile(cfg.VetxOutput, data, 0666)
321 }
322 )
323
324 func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
325
326 var files []*ast.File
327 for _, name := range cfg.GoFiles {
328 f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
329 if err != nil {
330 if cfg.SucceedOnTypecheckFailure {
331
332
333 err = nil
334 }
335 return nil, err
336 }
337 files = append(files, f)
338 }
339 tc := &types.Config{
340 Importer: makeTypesImporter(cfg, fset),
341 Sizes: types.SizesFor("gc", build.Default.GOARCH),
342 GoVersion: cfg.GoVersion,
343 }
344 info := &types.Info{
345 Types: make(map[ast.Expr]types.TypeAndValue),
346 Defs: make(map[*ast.Ident]types.Object),
347 Uses: make(map[*ast.Ident]types.Object),
348 Implicits: make(map[ast.Node]types.Object),
349 Instances: make(map[*ast.Ident]types.Instance),
350 Scopes: make(map[ast.Node]*types.Scope),
351 Selections: make(map[*ast.SelectorExpr]*types.Selection),
352 FileVersions: make(map[*ast.File]string),
353 }
354
355 pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
356 if err != nil {
357 if cfg.SucceedOnTypecheckFailure {
358
359
360 err = nil
361 }
362 return nil, err
363 }
364
365
366
367
368
369
370
371
372
373
374 type action struct {
375 once sync.Once
376 result any
377 err error
378 usesFacts bool
379 diagnostics []analysis.Diagnostic
380 }
381 actions := make(map[*analysis.Analyzer]*action)
382 var registerFacts func(a *analysis.Analyzer) bool
383 registerFacts = func(a *analysis.Analyzer) bool {
384 act, ok := actions[a]
385 if !ok {
386 act = new(action)
387 var usesFacts bool
388 for _, f := range a.FactTypes {
389 usesFacts = true
390 gob.Register(f)
391 }
392 for _, req := range a.Requires {
393 if registerFacts(req) {
394 usesFacts = true
395 }
396 }
397 act.usesFacts = usesFacts
398 actions[a] = act
399 }
400 return act.usesFacts
401 }
402 var filtered []*analysis.Analyzer
403 for _, a := range analyzers {
404 if registerFacts(a) || !cfg.VetxOnly {
405 filtered = append(filtered, a)
406 }
407 }
408 analyzers = filtered
409
410
411 facts, err := facts.NewDecoder(pkg).Decode(makeFactImporter(cfg))
412 if err != nil {
413 return nil, err
414 }
415
416
417 var exec func(a *analysis.Analyzer) *action
418 var execAll func(analyzers []*analysis.Analyzer)
419 exec = func(a *analysis.Analyzer) *action {
420 act := actions[a]
421 act.once.Do(func() {
422 execAll(a.Requires)
423
424
425
426 inputs := make(map[*analysis.Analyzer]any)
427 var failed []string
428 for _, req := range a.Requires {
429 reqact := exec(req)
430 if reqact.err != nil {
431 failed = append(failed, req.String())
432 continue
433 }
434 inputs[req] = reqact.result
435 }
436
437
438 if failed != nil {
439 sort.Strings(failed)
440 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
441 return
442 }
443
444 factFilter := make(map[reflect.Type]bool)
445 for _, f := range a.FactTypes {
446 factFilter[reflect.TypeOf(f)] = true
447 }
448
449 module := &analysis.Module{
450 Path: cfg.ModulePath,
451 Version: cfg.ModuleVersion,
452 GoVersion: cfg.GoVersion,
453 }
454
455 pass := &analysis.Pass{
456 Analyzer: a,
457 Fset: fset,
458 Files: files,
459 OtherFiles: cfg.NonGoFiles,
460 IgnoredFiles: cfg.IgnoredFiles,
461 Pkg: pkg,
462 TypesInfo: info,
463 TypesSizes: tc.Sizes,
464 TypeErrors: nil,
465 ResultOf: inputs,
466 Report: func(d analysis.Diagnostic) {
467
468 if err := driverutil.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
469
470
471 log.Println(err)
472 d.SuggestedFixes = nil
473 }
474 act.diagnostics = append(act.diagnostics, d)
475 },
476 ImportObjectFact: facts.ImportObjectFact,
477 ExportObjectFact: facts.ExportObjectFact,
478 AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
479 ImportPackageFact: facts.ImportPackageFact,
480 ExportPackageFact: facts.ExportPackageFact,
481 AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
482 Module: module,
483 }
484 pass.ReadFile = driverutil.CheckedReadFile(pass, os.ReadFile)
485
486 t0 := time.Now()
487 act.result, act.err = a.Run(pass)
488
489 if act.err == nil {
490 for i := range act.diagnostics {
491 if url, uerr := driverutil.ResolveURL(a, act.diagnostics[i]); uerr == nil {
492 act.diagnostics[i].URL = url
493 } else {
494 act.err = uerr
495 }
496 }
497 }
498 if false {
499 log.Printf("analysis %s = %s", pass, time.Since(t0))
500 }
501 })
502 return act
503 }
504 execAll = func(analyzers []*analysis.Analyzer) {
505 var wg sync.WaitGroup
506 for _, a := range analyzers {
507 wg.Add(1)
508 go func(a *analysis.Analyzer) {
509 _ = exec(a)
510 wg.Done()
511 }(a)
512 }
513 wg.Wait()
514 }
515
516 execAll(analyzers)
517
518
519 results := make([]result, len(analyzers))
520 for i, a := range analyzers {
521 act := actions[a]
522 results[i] = result{pkg, files, a, act.diagnostics, act.err}
523 }
524
525 data := facts.Encode()
526 if err := exportFacts(cfg, data); err != nil {
527 return nil, fmt.Errorf("failed to export analysis facts: %v", err)
528 }
529 if err := exportTypes(cfg, fset, pkg); err != nil {
530 return nil, fmt.Errorf("failed to export type information: %v", err)
531 }
532
533 return results, nil
534 }
535
536 type result struct {
537 pkg *types.Package
538 files []*ast.File
539 a *analysis.Analyzer
540 diagnostics []analysis.Diagnostic
541 err error
542 }
543
544 type importerFunc func(path string) (*types.Package, error)
545
546 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
547
View as plain text