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