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