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 "io/ioutil"
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/go/analysis/internal/facts"
54 "golang.org/x/tools/internal/typeparams"
55 )
56
57
58
59
60 type Config struct {
61 ID string
62 Compiler string
63 Dir string
64 ImportPath 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
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 := ioutil.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 var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
188
189 return importer.For(compiler, lookup)
190 }
191
192 func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
193
194 var files []*ast.File
195 for _, name := range cfg.GoFiles {
196 f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
197 if err != nil {
198 if cfg.SucceedOnTypecheckFailure {
199
200
201 err = nil
202 }
203 return nil, err
204 }
205 files = append(files, f)
206 }
207 compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
208
209 file, ok := cfg.PackageFile[path]
210 if !ok {
211 if cfg.Compiler == "gccgo" && cfg.Standard[path] {
212 return nil, nil
213 }
214 return nil, fmt.Errorf("no package file for %q", path)
215 }
216 return os.Open(file)
217 })
218 importer := importerFunc(func(importPath string) (*types.Package, error) {
219 path, ok := cfg.ImportMap[importPath]
220 if !ok {
221 return nil, fmt.Errorf("can't resolve import %q", path)
222 }
223 return compilerImporter.Import(path)
224 })
225 tc := &types.Config{
226 Importer: importer,
227 Sizes: types.SizesFor("gc", build.Default.GOARCH),
228 }
229 info := &types.Info{
230 Types: make(map[ast.Expr]types.TypeAndValue),
231 Defs: make(map[*ast.Ident]types.Object),
232 Uses: make(map[*ast.Ident]types.Object),
233 Implicits: make(map[ast.Node]types.Object),
234 Scopes: make(map[ast.Node]*types.Scope),
235 Selections: make(map[*ast.SelectorExpr]*types.Selection),
236 }
237 typeparams.InitInstanceInfo(info)
238
239 pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
240 if err != nil {
241 if cfg.SucceedOnTypecheckFailure {
242
243
244 err = nil
245 }
246 return nil, err
247 }
248
249
250
251
252
253
254 type action struct {
255 once sync.Once
256 result interface{}
257 err error
258 usesFacts bool
259 diagnostics []analysis.Diagnostic
260 }
261 actions := make(map[*analysis.Analyzer]*action)
262 var registerFacts func(a *analysis.Analyzer) bool
263 registerFacts = func(a *analysis.Analyzer) bool {
264 act, ok := actions[a]
265 if !ok {
266 act = new(action)
267 var usesFacts bool
268 for _, f := range a.FactTypes {
269 usesFacts = true
270 gob.Register(f)
271 }
272 for _, req := range a.Requires {
273 if registerFacts(req) {
274 usesFacts = true
275 }
276 }
277 act.usesFacts = usesFacts
278 actions[a] = act
279 }
280 return act.usesFacts
281 }
282 var filtered []*analysis.Analyzer
283 for _, a := range analyzers {
284 if registerFacts(a) || !cfg.VetxOnly {
285 filtered = append(filtered, a)
286 }
287 }
288 analyzers = filtered
289
290
291 read := func(path string) ([]byte, error) {
292 if vetx, ok := cfg.PackageVetx[path]; ok {
293 return ioutil.ReadFile(vetx)
294 }
295 return nil, nil
296 }
297 facts, err := facts.Decode(pkg, read)
298 if err != nil {
299 return nil, err
300 }
301
302
303 var exec func(a *analysis.Analyzer) *action
304 var execAll func(analyzers []*analysis.Analyzer)
305 exec = func(a *analysis.Analyzer) *action {
306 act := actions[a]
307 act.once.Do(func() {
308 execAll(a.Requires)
309
310
311
312 inputs := make(map[*analysis.Analyzer]interface{})
313 var failed []string
314 for _, req := range a.Requires {
315 reqact := exec(req)
316 if reqact.err != nil {
317 failed = append(failed, req.String())
318 continue
319 }
320 inputs[req] = reqact.result
321 }
322
323
324 if failed != nil {
325 sort.Strings(failed)
326 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
327 return
328 }
329
330 factFilter := make(map[reflect.Type]bool)
331 for _, f := range a.FactTypes {
332 factFilter[reflect.TypeOf(f)] = true
333 }
334
335 pass := &analysis.Pass{
336 Analyzer: a,
337 Fset: fset,
338 Files: files,
339 OtherFiles: cfg.NonGoFiles,
340 IgnoredFiles: cfg.IgnoredFiles,
341 Pkg: pkg,
342 TypesInfo: info,
343 TypesSizes: tc.Sizes,
344 ResultOf: inputs,
345 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
346 ImportObjectFact: facts.ImportObjectFact,
347 ExportObjectFact: facts.ExportObjectFact,
348 AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
349 ImportPackageFact: facts.ImportPackageFact,
350 ExportPackageFact: facts.ExportPackageFact,
351 AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
352 }
353
354 t0 := time.Now()
355 act.result, act.err = a.Run(pass)
356 if false {
357 log.Printf("analysis %s = %s", pass, time.Since(t0))
358 }
359 })
360 return act
361 }
362 execAll = func(analyzers []*analysis.Analyzer) {
363 var wg sync.WaitGroup
364 for _, a := range analyzers {
365 wg.Add(1)
366 go func(a *analysis.Analyzer) {
367 _ = exec(a)
368 wg.Done()
369 }(a)
370 }
371 wg.Wait()
372 }
373
374 execAll(analyzers)
375
376
377 results := make([]result, len(analyzers))
378 for i, a := range analyzers {
379 act := actions[a]
380 results[i].a = a
381 results[i].err = act.err
382 results[i].diagnostics = act.diagnostics
383 }
384
385 data := facts.Encode()
386 if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil {
387 return nil, fmt.Errorf("failed to write analysis facts: %v", err)
388 }
389
390 return results, nil
391 }
392
393 type result struct {
394 a *analysis.Analyzer
395 diagnostics []analysis.Diagnostic
396 err error
397 }
398
399 type importerFunc func(path string) (*types.Package, error)
400
401 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
402
View as plain text