1/* Copyright 2018 The Bazel Authors. All rights reserved. 2 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9Unless required by applicable law or agreed to in writing, software 10distributed under the License is distributed on an "AS IS" BASIS, 11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12See the License for the specific language governing permissions and 13limitations under the License. 14*/ 15 16// Loads and runs registered analyses on a well-typed Go package. 17// The code in this file is combined with the code generated by 18// generate_nogo_main.go. 19 20package main 21 22import ( 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 46const nogoBaseConfigName = "_base" 47 48func init() { 49 if err := analysis.Validate(analyzers); err != nil { 50 log.Fatal(err) 51 } 52} 53 54var typesSizes = types.SizesFor("gc", os.Getenv("GOARCH")) 55 56func main() { 57 log.SetFlags(0) // no timestamp 58 log.SetPrefix("nogo: ") 59 if err := run(os.Args[1:]); err != nil { 60 log.Fatal(err) 61 } 62} 63 64// run returns an error if there is a problem loading the package or if any 65// analysis fails. 66func 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// Adapted from go/src/cmd/compile/internal/gc/main.go. Keep in sync. 103func 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++ // 1-based 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// checkPackage runs all the given analyzers on the specified package and 146// returns the source code diagnostics that the must be printed in the build log. 147// It returns an empty string if no source code diagnostics need to be printed. 148// 149// This implementation was adapted from that of golang.org/x/tools/go/checker/internal/checker. 150func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFile, importMap map[string]string, factMap map[string]string, filenames []string) (string, []byte, error) { 151 // Register fact types and establish dependencies between analyzers. 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 // Load the package, including AST, types, and facts. 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 // Process nolint directives similar to golangci-lint. 206 for _, f := range pkg.syntax { 207 // CommentMap will correctly associate comments to the largest node group 208 // applicable. This handles inline comments that might trail a large 209 // assignment and will apply the comment to the entire assignment. 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 // Execute the analyzers. 233 execAll(roots) 234 235 // Process diagnostics and encode facts for importers of this package. 236 diagnostics := checkAnalysisResults(roots, pkg) 237 facts := pkg.facts.Encode() 238 return diagnostics, facts, nil 239} 240 241type Range struct { 242 from token.Position 243 to int 244} 245 246// An action represents one unit of analysis work: the application of 247// one analysis to one package. Actions form a DAG within a 248// package (as different analyzers are applied, either in sequence or 249// parallel). 250type 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 264func (act *action) String() string { 265 return fmt.Sprintf("%s@%s", act.a, act.pkg) 266} 267 268func 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 280func (act *action) exec() { act.once.Do(act.execOnce) } 281 282func (act *action) execOnce() { 283 // Analyze dependencies. 284 execAll(act.deps) 285 286 // Report an error if any dependency failed. 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 // Plumb the output values of the dependencies 300 // into the inputs of this action. 301 inputs := make(map[*analysis.Analyzer]interface{}) 302 for _, dep := range act.deps { 303 // Same package, different analysis (horizontal edge): 304 // in-memory outputs of prerequisite analyzers 305 // become inputs to this analysis pass. 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 // The list of nolint ranges is built for the entire package. Make sure we 313 // only apply ranges to the correct file. 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 // Found a nolint range. Ignore the issue. 321 return 322 } 323 act.diagnostics = append(act.diagnostics, d) 324 } 325 326 // Run the analysis. 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// load parses and type checks the source code in each file in filenames. 366// load also deserializes facts stored for imported packages. 367func 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// A goPackage describes a loaded Go package. 408type goPackage struct { 409 // fset provides position information for types, typesInfo, and syntax. 410 // It is set only when types is set. 411 fset *token.FileSet 412 // syntax is the package's syntax trees. 413 syntax []*ast.File 414 // types provides type information for the package. 415 types *types.Package 416 // facts contains information saved by the analysis framework. Passes may 417 // import facts for imported packages and may also export facts for this 418 // package to be consumed by analyses in downstream packages. 419 facts *facts.Set 420 // illTyped indicates whether the package or any dependency contains errors. 421 // It is set only when types is set. 422 illTyped bool 423 // typeCheckError contains any error encountered during type-checking. It is 424 // only set when illTyped is true. 425 typeCheckError error 426 // typesInfo provides type information about the package's syntax trees. 427 // It is set only when syntax is set. 428 typesInfo *types.Info 429} 430 431func (g *goPackage) String() string { 432 return g.types.Path() 433} 434 435// checkAnalysisResults checks the analysis diagnostics in the given actions 436// and returns a string containing all the diagnostics that should be printed 437// to the build log. 438func 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 // Analyzer failed. 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 // Use the base config if it exists. 456 if baseConfig, ok := configs[nogoBaseConfigName]; ok { 457 currentConfig = baseConfig 458 } 459 // Overwrite the config with the desired config. Any unset fields 460 // in the config will default to the base config. 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 // Discard diagnostics based on the analyzer configuration. 480 for _, d := range act.diagnostics { 481 // NOTE(golang.org/issue/31008): nilness does not set positions, 482 // so don't assume the position is valid. 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 // This analyzer emits diagnostics for only a set of files. 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// config determines which source files an analyzer will emit diagnostics for. 535// config values are generated in another file that is compiled with 536// nogo_main.go by the nogo rule. 537type config struct { 538 // onlyFiles is a list of regular expressions that match files an analyzer 539 // will emit diagnostics for. When empty, the analyzer will emit diagnostics 540 // for all files. 541 onlyFiles []*regexp.Regexp 542 543 // excludeFiles is a list of regular expressions that match files that an 544 // analyzer will not emit diagnostics for. 545 excludeFiles []*regexp.Regexp 546 547 // analyzerFlags is a map of flag names to flag values which will be passed 548 // to Analyzer.Flags. Note that no leading '-' should be present in a flag 549 // name 550 analyzerFlags map[string]string 551} 552 553// importer is an implementation of go/types.Importer that imports type 554// information from the export data in compiled .a files. 555type importer struct { 556 fset *token.FileSet 557 importMap map[string]string // map import path in source code to package path 558 packageCache map[string]*types.Package // cache of previously imported packages 559 packageFile map[string]string // map package path to .a file with export data 560 factMap map[string]string // map import path in source code to file containing serialized facts 561} 562 563func 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 573func (i *importer) Import(path string) (*types.Package, error) { 574 if imp, ok := i.importMap[path]; ok { 575 // Translate import path if necessary. 576 path = imp 577 } 578 if path == "unsafe" { 579 // Special case: go/types has pre-defined type information for unsafe. 580 // See https://github.com/golang/go/issues/13882. 581 return types.Unsafe, nil 582 } 583 if pkg, ok := i.packageCache[path]; ok && pkg.Complete() { 584 return pkg, nil // cache hit 585 } 586 587 archive, ok := i.packageFile[path] 588 if !ok { 589 return nil, fmt.Errorf("could not import %q", path) 590 } 591 // open file 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 // add file name to error 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 612func (i *importer) readFacts(pkg *types.Package) ([]byte, error) { 613 archive := i.factMap[pkg.Path()] 614 if archive == "" { 615 // Packages that were not built with the nogo toolchain will not be 616 // analyzed, so there's no opportunity to store facts. This includes 617 // packages in the standard library and packages built with go_tool_library, 618 // such as coverdata. Analyzers are expected to hard code information 619 // about standard library definitions and must gracefully handle packages 620 // that don't have facts. For example, the "printf" analyzer must know 621 // fmt.Printf accepts a format string. 622 return nil, nil 623 } 624 factReader, err := readFileInArchive(nogoFact, archive) 625 if os.IsNotExist(err) { 626 // Packages that were not built with the nogo toolchain will not be 627 // analyzed, so there's no opportunity to store facts. This includes 628 // packages in the standard library and packages built with go_tool_library, 629 // such as coverdata. 630 return nil, nil 631 } else if err != nil { 632 return nil, err 633 } 634 defer factReader.Close() 635 return ioutil.ReadAll(factReader) 636} 637 638type factMultiFlag map[string]string 639 640func (m *factMultiFlag) String() string { 641 if m == nil || len(*m) == 0 { 642 return "" 643 } 644 return fmt.Sprintf("%v", *m) 645} 646 647func (m *factMultiFlag) Set(v string) error { 648 parts := strings.Split(v, "=") 649 if len(parts) != 2 { 650 return fmt.Errorf("badly formatted -fact flag: %s", v) 651 } 652 (*m)[parts[0]] = parts[1] 653 return nil 654} 655