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