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