1
15
16
17
18
19
20 package main
21
22 import (
23 "bytes"
24 "encoding/gob"
25 "errors"
26 "flag"
27 "fmt"
28 "go/ast"
29 "go/parser"
30 "go/token"
31 "go/types"
32 "io/ioutil"
33 "log"
34 "os"
35 "reflect"
36 "regexp"
37 "sort"
38 "strings"
39 "sync"
40
41 "golang.org/x/tools/go/analysis"
42 "golang.org/x/tools/go/gcexportdata"
43 "golang.org/x/tools/internal/facts"
44 )
45
46 const nogoBaseConfigName = "_base"
47
48 func init() {
49 if err := analysis.Validate(analyzers); err != nil {
50 log.Fatal(err)
51 }
52 }
53
54 var typesSizes = types.SizesFor("gc", os.Getenv("GOARCH"))
55
56 func main() {
57 log.SetFlags(0)
58 log.SetPrefix("nogo: ")
59 if err := run(os.Args[1:]); err != nil {
60 log.Fatal(err)
61 }
62 }
63
64
65
66 func run(args []string) error {
67 args, _, err := expandParamsFiles(args)
68 if err != nil {
69 return fmt.Errorf("error reading paramfiles: %v", err)
70 }
71
72 factMap := factMultiFlag{}
73 flags := flag.NewFlagSet("nogo", flag.ExitOnError)
74 flags.Var(&factMap, "fact", "Import path and file containing facts for that library, separated by '=' (may be repeated)'")
75 importcfg := flags.String("importcfg", "", "The import configuration file")
76 packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled")
77 xPath := flags.String("x", "", "The archive file where serialized facts should be written")
78 flags.Parse(args)
79 srcs := flags.Args()
80
81 packageFile, importMap, err := readImportCfg(*importcfg)
82 if err != nil {
83 return fmt.Errorf("error parsing importcfg: %v", err)
84 }
85
86 diagnostics, facts, err := checkPackage(analyzers, *packagePath, packageFile, importMap, factMap, srcs)
87 if err != nil {
88 return fmt.Errorf("error running analyzers: %v", err)
89 }
90 if diagnostics != "" {
91 return fmt.Errorf("errors found by nogo during build-time code analysis:\n%s\n", diagnostics)
92 }
93 if *xPath != "" {
94 if err := ioutil.WriteFile(abs(*xPath), facts, 0o666); err != nil {
95 return fmt.Errorf("error writing facts: %v", err)
96 }
97 }
98
99 return nil
100 }
101
102
103 func readImportCfg(file string) (packageFile map[string]string, importMap map[string]string, err error) {
104 packageFile, importMap = make(map[string]string), make(map[string]string)
105 data, err := ioutil.ReadFile(file)
106 if err != nil {
107 return nil, nil, fmt.Errorf("-importcfg: %v", err)
108 }
109
110 for lineNum, line := range strings.Split(string(data), "\n") {
111 lineNum++
112 line = strings.TrimSpace(line)
113 if line == "" || strings.HasPrefix(line, "#") {
114 continue
115 }
116
117 var verb, args string
118 if i := strings.Index(line, " "); i < 0 {
119 verb = line
120 } else {
121 verb, args = line[:i], strings.TrimSpace(line[i+1:])
122 }
123 var before, after string
124 if i := strings.Index(args, "="); i >= 0 {
125 before, after = args[:i], args[i+1:]
126 }
127 switch verb {
128 default:
129 return nil, nil, fmt.Errorf("%s:%d: unknown directive %q", file, lineNum, verb)
130 case "importmap":
131 if before == "" || after == "" {
132 return nil, nil, fmt.Errorf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum)
133 }
134 importMap[before] = after
135 case "packagefile":
136 if before == "" || after == "" {
137 return nil, nil, fmt.Errorf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum)
138 }
139 packageFile[before] = after
140 }
141 }
142 return packageFile, importMap, nil
143 }
144
145
146
147
148
149
150 func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFile, importMap map[string]string, factMap map[string]string, filenames []string) (string, []byte, error) {
151
152 actions := make(map[*analysis.Analyzer]*action)
153 var visit func(a *analysis.Analyzer) *action
154 visit = func(a *analysis.Analyzer) *action {
155 act, ok := actions[a]
156 if !ok {
157 act = &action{a: a}
158 actions[a] = act
159 for _, f := range a.FactTypes {
160 act.usesFacts = true
161 gob.Register(f)
162 }
163 act.deps = make([]*action, len(a.Requires))
164 for i, req := range a.Requires {
165 dep := visit(req)
166 if dep.usesFacts {
167 act.usesFacts = true
168 }
169 act.deps[i] = dep
170 }
171 }
172 return act
173 }
174
175 roots := make([]*action, 0, len(analyzers))
176 for _, a := range analyzers {
177 if cfg, ok := configs[a.Name]; ok {
178 for flagKey, flagVal := range cfg.analyzerFlags {
179 if strings.HasPrefix(flagKey, "-") {
180 return "", nil, fmt.Errorf(
181 "%s: flag should not begin with '-': %s", a.Name, flagKey)
182 }
183 if flag := a.Flags.Lookup(flagKey); flag == nil {
184 return "", nil, fmt.Errorf("%s: unrecognized flag: %s", a.Name, flagKey)
185 }
186 if err := a.Flags.Set(flagKey, flagVal); err != nil {
187 return "", nil, fmt.Errorf(
188 "%s: invalid value for flag: %s=%s: %w", a.Name, flagKey, flagVal, err)
189 }
190 }
191 }
192 roots = append(roots, visit(a))
193 }
194
195
196 imp := newImporter(importMap, packageFile, factMap)
197 pkg, err := load(packagePath, imp, filenames)
198 if err != nil {
199 return "", nil, fmt.Errorf("error loading package: %v", err)
200 }
201 for _, act := range actions {
202 act.pkg = pkg
203 }
204
205
206 for _, f := range pkg.syntax {
207
208
209
210 commentMap := ast.NewCommentMap(pkg.fset, f, f.Comments)
211 for node, groups := range commentMap {
212 rng := &Range{
213 from: pkg.fset.Position(node.Pos()),
214 to: pkg.fset.Position(node.End()).Line,
215 }
216 for _, group := range groups {
217 for _, comm := range group.List {
218 linters, ok := parseNolint(comm.Text)
219 if !ok {
220 continue
221 }
222 for analyzer, act := range actions {
223 if linters == nil || linters[analyzer.Name] {
224 act.nolint = append(act.nolint, rng)
225 }
226 }
227 }
228 }
229 }
230 }
231
232
233 execAll(roots)
234
235
236 diagnostics := checkAnalysisResults(roots, pkg)
237 facts := pkg.facts.Encode()
238 return diagnostics, facts, nil
239 }
240
241 type Range struct {
242 from token.Position
243 to int
244 }
245
246
247
248
249
250 type action struct {
251 once sync.Once
252 a *analysis.Analyzer
253 pass *analysis.Pass
254 pkg *goPackage
255 deps []*action
256 inputs map[*analysis.Analyzer]interface{}
257 result interface{}
258 diagnostics []analysis.Diagnostic
259 usesFacts bool
260 err error
261 nolint []*Range
262 }
263
264 func (act *action) String() string {
265 return fmt.Sprintf("%s@%s", act.a, act.pkg)
266 }
267
268 func execAll(actions []*action) {
269 var wg sync.WaitGroup
270 wg.Add(len(actions))
271 for _, act := range actions {
272 go func(act *action) {
273 defer wg.Done()
274 act.exec()
275 }(act)
276 }
277 wg.Wait()
278 }
279
280 func (act *action) exec() { act.once.Do(act.execOnce) }
281
282 func (act *action) execOnce() {
283
284 execAll(act.deps)
285
286
287 var failed []string
288 for _, dep := range act.deps {
289 if dep.err != nil {
290 failed = append(failed, dep.String())
291 }
292 }
293 if failed != nil {
294 sort.Strings(failed)
295 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
296 return
297 }
298
299
300
301 inputs := make(map[*analysis.Analyzer]interface{})
302 for _, dep := range act.deps {
303
304
305
306 inputs[dep.a] = dep.result
307 }
308
309 ignoreNolintReporter := func(d analysis.Diagnostic) {
310 pos := act.pkg.fset.Position(d.Pos)
311 for _, rng := range act.nolint {
312
313
314 if pos.Filename != rng.from.Filename {
315 continue
316 }
317 if pos.Line < rng.from.Line || pos.Line > rng.to {
318 continue
319 }
320
321 return
322 }
323 act.diagnostics = append(act.diagnostics, d)
324 }
325
326
327 factFilter := make(map[reflect.Type]bool)
328 for _, f := range act.a.FactTypes {
329 factFilter[reflect.TypeOf(f)] = true
330 }
331 pass := &analysis.Pass{
332 Analyzer: act.a,
333 Fset: act.pkg.fset,
334 Files: act.pkg.syntax,
335 Pkg: act.pkg.types,
336 TypesInfo: act.pkg.typesInfo,
337 ResultOf: inputs,
338 Report: ignoreNolintReporter,
339 ImportPackageFact: act.pkg.facts.ImportPackageFact,
340 ExportPackageFact: act.pkg.facts.ExportPackageFact,
341 ImportObjectFact: act.pkg.facts.ImportObjectFact,
342 ExportObjectFact: act.pkg.facts.ExportObjectFact,
343 AllPackageFacts: func() []analysis.PackageFact { return act.pkg.facts.AllPackageFacts(factFilter) },
344 AllObjectFacts: func() []analysis.ObjectFact { return act.pkg.facts.AllObjectFacts(factFilter) },
345 TypesSizes: typesSizes,
346 }
347 act.pass = pass
348
349 var err error
350 if act.pkg.illTyped && !pass.Analyzer.RunDespiteErrors {
351 err = fmt.Errorf("analysis skipped due to type-checking error: %v", act.pkg.typeCheckError)
352 } else {
353 act.result, err = pass.Analyzer.Run(pass)
354 if err == nil {
355 if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want {
356 err = fmt.Errorf(
357 "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
358 pass.Pkg.Path(), pass.Analyzer, got, want)
359 }
360 }
361 }
362 act.err = err
363 }
364
365
366
367 func load(packagePath string, imp *importer, filenames []string) (*goPackage, error) {
368 if len(filenames) == 0 {
369 return nil, errors.New("no filenames")
370 }
371 var syntax []*ast.File
372 for _, file := range filenames {
373 s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments)
374 if err != nil {
375 return nil, err
376 }
377 syntax = append(syntax, s)
378 }
379 pkg := &goPackage{fset: imp.fset, syntax: syntax}
380
381 config := types.Config{Importer: imp}
382 info := &types.Info{
383 Types: make(map[ast.Expr]types.TypeAndValue),
384 Uses: make(map[*ast.Ident]types.Object),
385 Defs: make(map[*ast.Ident]types.Object),
386 Implicits: make(map[ast.Node]types.Object),
387 Scopes: make(map[ast.Node]*types.Scope),
388 Selections: make(map[*ast.SelectorExpr]*types.Selection),
389 }
390
391 initInstanceInfo(info)
392
393 types, err := config.Check(packagePath, pkg.fset, syntax, info)
394 if err != nil {
395 pkg.illTyped, pkg.typeCheckError = true, err
396 }
397 pkg.types, pkg.typesInfo = types, info
398
399 pkg.facts, err = facts.NewDecoder(pkg.types).Decode(imp.readFacts)
400 if err != nil {
401 return nil, fmt.Errorf("internal error decoding facts: %v", err)
402 }
403
404 return pkg, nil
405 }
406
407
408 type goPackage struct {
409
410
411 fset *token.FileSet
412
413 syntax []*ast.File
414
415 types *types.Package
416
417
418
419 facts *facts.Set
420
421
422 illTyped bool
423
424
425 typeCheckError error
426
427
428 typesInfo *types.Info
429 }
430
431 func (g *goPackage) String() string {
432 return g.types.Path()
433 }
434
435
436
437
438 func checkAnalysisResults(actions []*action, pkg *goPackage) string {
439 type entry struct {
440 analysis.Diagnostic
441 *analysis.Analyzer
442 }
443 var diagnostics []entry
444 var errs []error
445 for _, act := range actions {
446 if act.err != nil {
447
448 errs = append(errs, fmt.Errorf("analyzer %q failed: %v", act.a.Name, act.err))
449 continue
450 }
451 if len(act.diagnostics) == 0 {
452 continue
453 }
454 var currentConfig config
455
456 if baseConfig, ok := configs[nogoBaseConfigName]; ok {
457 currentConfig = baseConfig
458 }
459
460
461 if actionConfig, ok := configs[act.a.Name]; ok {
462 if actionConfig.analyzerFlags != nil {
463 currentConfig.analyzerFlags = actionConfig.analyzerFlags
464 }
465 if actionConfig.onlyFiles != nil {
466 currentConfig.onlyFiles = actionConfig.onlyFiles
467 }
468 if actionConfig.excludeFiles != nil {
469 currentConfig.excludeFiles = actionConfig.excludeFiles
470 }
471 }
472
473 if currentConfig.onlyFiles == nil && currentConfig.excludeFiles == nil {
474 for _, diag := range act.diagnostics {
475 diagnostics = append(diagnostics, entry{Diagnostic: diag, Analyzer: act.a})
476 }
477 continue
478 }
479
480 for _, d := range act.diagnostics {
481
482
483 p := pkg.fset.Position(d.Pos)
484 filename := "-"
485 if p.IsValid() {
486 filename = p.Filename
487 }
488 include := true
489 if len(currentConfig.onlyFiles) > 0 {
490
491 include = false
492 for _, pattern := range currentConfig.onlyFiles {
493 if pattern.MatchString(filename) {
494 include = true
495 break
496 }
497 }
498 }
499 if include {
500 for _, pattern := range currentConfig.excludeFiles {
501 if pattern.MatchString(filename) {
502 include = false
503 break
504 }
505 }
506 }
507 if include {
508 diagnostics = append(diagnostics, entry{Diagnostic: d, Analyzer: act.a})
509 }
510 }
511 }
512 if len(diagnostics) == 0 && len(errs) == 0 {
513 return ""
514 }
515
516 sort.Slice(diagnostics, func(i, j int) bool {
517 return diagnostics[i].Pos < diagnostics[j].Pos
518 })
519 errMsg := &bytes.Buffer{}
520 sep := ""
521 for _, err := range errs {
522 errMsg.WriteString(sep)
523 sep = "\n"
524 errMsg.WriteString(err.Error())
525 }
526 for _, d := range diagnostics {
527 errMsg.WriteString(sep)
528 sep = "\n"
529 fmt.Fprintf(errMsg, "%s: %s (%s)", pkg.fset.Position(d.Pos), d.Message, d.Name)
530 }
531 return errMsg.String()
532 }
533
534
535
536
537 type config struct {
538
539
540
541 onlyFiles []*regexp.Regexp
542
543
544
545 excludeFiles []*regexp.Regexp
546
547
548
549
550 analyzerFlags map[string]string
551 }
552
553
554
555 type importer struct {
556 fset *token.FileSet
557 importMap map[string]string
558 packageCache map[string]*types.Package
559 packageFile map[string]string
560 factMap map[string]string
561 }
562
563 func newImporter(importMap, packageFile map[string]string, factMap map[string]string) *importer {
564 return &importer{
565 fset: token.NewFileSet(),
566 importMap: importMap,
567 packageCache: make(map[string]*types.Package),
568 packageFile: packageFile,
569 factMap: factMap,
570 }
571 }
572
573 func (i *importer) Import(path string) (*types.Package, error) {
574 if imp, ok := i.importMap[path]; ok {
575
576 path = imp
577 }
578 if path == "unsafe" {
579
580
581 return types.Unsafe, nil
582 }
583 if pkg, ok := i.packageCache[path]; ok && pkg.Complete() {
584 return pkg, nil
585 }
586
587 archive, ok := i.packageFile[path]
588 if !ok {
589 return nil, fmt.Errorf("could not import %q", path)
590 }
591
592 f, err := os.Open(archive)
593 if err != nil {
594 return nil, err
595 }
596 defer func() {
597 f.Close()
598 if err != nil {
599
600 err = fmt.Errorf("reading export data: %s: %v", archive, err)
601 }
602 }()
603
604 r, err := gcexportdata.NewReader(f)
605 if err != nil {
606 return nil, err
607 }
608
609 return gcexportdata.Read(r, i.fset, i.packageCache, path)
610 }
611
612 func (i *importer) readFacts(pkgPath string) ([]byte, error) {
613 facts := i.factMap[pkgPath]
614 if facts == "" {
615
616
617
618
619
620
621
622 return nil, nil
623 }
624 return os.ReadFile(facts)
625 }
626
627 type factMultiFlag map[string]string
628
629 func (m *factMultiFlag) String() string {
630 if m == nil || len(*m) == 0 {
631 return ""
632 }
633 return fmt.Sprintf("%v", *m)
634 }
635
636 func (m *factMultiFlag) Set(v string) error {
637 parts := strings.Split(v, "=")
638 if len(parts) != 2 {
639 return fmt.Errorf("badly formatted -fact flag: %s", v)
640 }
641 (*m)[parts[0]] = parts[1]
642 return nil
643 }
644
View as plain text