github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/nogo_main.go (about) 1 /* Copyright 2018 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations 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 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/analysis/internal/facts" 43 "golang.org/x/tools/go/gcexportdata" 44 ) 45 46 func init() { 47 if err := analysis.Validate(analyzers); err != nil { 48 log.Fatal(err) 49 } 50 } 51 52 var typesSizes = types.SizesFor("gc", os.Getenv("GOARCH")) 53 54 func main() { 55 log.SetFlags(0) // no timestamp 56 log.SetPrefix("nogo: ") 57 if err := run(os.Args[1:]); err != nil { 58 log.Fatal(err) 59 } 60 } 61 62 // run returns an error if there is a problem loading the package or if any 63 // analysis fails. 64 func run(args []string) error { 65 args, err := expandParamsFiles(args) 66 if err != nil { 67 return fmt.Errorf("error reading paramfiles: %v", err) 68 } 69 70 factMap := factMultiFlag{} 71 flags := flag.NewFlagSet("nogo", flag.ExitOnError) 72 flags.Var(&factMap, "fact", "Import path and file containing facts for that library, separated by '=' (may be repeated)'") 73 importcfg := flags.String("importcfg", "", "The import configuration file") 74 packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled") 75 xPath := flags.String("x", "", "The archive file where serialized facts should be written") 76 flags.Parse(args) 77 srcs := flags.Args() 78 79 packageFile, importMap, err := readImportCfg(*importcfg) 80 if err != nil { 81 return fmt.Errorf("error parsing importcfg: %v", err) 82 } 83 84 diagnostics, facts, err := checkPackage(analyzers, *packagePath, packageFile, importMap, factMap, srcs) 85 if err != nil { 86 return fmt.Errorf("error running analyzers: %v", err) 87 } 88 if diagnostics != "" { 89 return fmt.Errorf("errors found by nogo during build-time code analysis:\n%s\n", diagnostics) 90 } 91 if *xPath != "" { 92 if err := ioutil.WriteFile(abs(*xPath), facts, 0666); err != nil { 93 return fmt.Errorf("error writing facts: %v", err) 94 } 95 } 96 97 return nil 98 } 99 100 // Adapted from go/src/cmd/compile/internal/gc/main.go. Keep in sync. 101 func readImportCfg(file string) (packageFile map[string]string, importMap map[string]string, err error) { 102 packageFile, importMap = make(map[string]string), make(map[string]string) 103 data, err := ioutil.ReadFile(file) 104 if err != nil { 105 return nil, nil, fmt.Errorf("-importcfg: %v", err) 106 } 107 108 for lineNum, line := range strings.Split(string(data), "\n") { 109 lineNum++ // 1-based 110 line = strings.TrimSpace(line) 111 if line == "" || strings.HasPrefix(line, "#") { 112 continue 113 } 114 115 var verb, args string 116 if i := strings.Index(line, " "); i < 0 { 117 verb = line 118 } else { 119 verb, args = line[:i], strings.TrimSpace(line[i+1:]) 120 } 121 var before, after string 122 if i := strings.Index(args, "="); i >= 0 { 123 before, after = args[:i], args[i+1:] 124 } 125 switch verb { 126 default: 127 return nil, nil, fmt.Errorf("%s:%d: unknown directive %q", file, lineNum, verb) 128 case "importmap": 129 if before == "" || after == "" { 130 return nil, nil, fmt.Errorf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum) 131 } 132 importMap[before] = after 133 case "packagefile": 134 if before == "" || after == "" { 135 return nil, nil, fmt.Errorf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum) 136 } 137 packageFile[before] = after 138 } 139 } 140 return packageFile, importMap, nil 141 } 142 143 // checkPackage runs all the given analyzers on the specified package and 144 // returns the source code diagnostics that the must be printed in the build log. 145 // It returns an empty string if no source code diagnostics need to be printed. 146 // 147 // This implementation was adapted from that of golang.org/x/tools/go/checker/internal/checker. 148 func checkPackage(analyzers []*analysis.Analyzer, packagePath string, packageFile, importMap map[string]string, factMap map[string]string, filenames []string) (string, []byte, error) { 149 // Register fact types and establish dependencies between analyzers. 150 actions := make(map[*analysis.Analyzer]*action) 151 var visit func(a *analysis.Analyzer) *action 152 visit = func(a *analysis.Analyzer) *action { 153 act, ok := actions[a] 154 if !ok { 155 act = &action{a: a} 156 actions[a] = act 157 for _, f := range a.FactTypes { 158 act.usesFacts = true 159 gob.Register(f) 160 } 161 act.deps = make([]*action, len(a.Requires)) 162 for i, req := range a.Requires { 163 dep := visit(req) 164 if dep.usesFacts { 165 act.usesFacts = true 166 } 167 act.deps[i] = dep 168 } 169 } 170 return act 171 } 172 173 roots := make([]*action, 0, len(analyzers)) 174 for _, a := range analyzers { 175 roots = append(roots, visit(a)) 176 } 177 178 // Load the package, including AST, types, and facts. 179 imp := newImporter(importMap, packageFile, factMap) 180 pkg, err := load(packagePath, imp, filenames) 181 if err != nil { 182 return "", nil, fmt.Errorf("error loading package: %v", err) 183 } 184 for _, act := range actions { 185 act.pkg = pkg 186 } 187 188 // Execute the analyzers. 189 execAll(roots) 190 191 // Process diagnostics and encode facts for importers of this package. 192 diagnostics := checkAnalysisResults(roots, pkg) 193 facts := pkg.facts.Encode() 194 return diagnostics, facts, nil 195 } 196 197 // An action represents one unit of analysis work: the application of 198 // one analysis to one package. Actions form a DAG within a 199 // package (as different analyzers are applied, either in sequence or 200 // parallel). 201 type action struct { 202 once sync.Once 203 a *analysis.Analyzer 204 pass *analysis.Pass 205 pkg *goPackage 206 deps []*action 207 inputs map[*analysis.Analyzer]interface{} 208 result interface{} 209 diagnostics []analysis.Diagnostic 210 usesFacts bool 211 err error 212 } 213 214 func (act *action) String() string { 215 return fmt.Sprintf("%s@%s", act.a, act.pkg) 216 } 217 218 func execAll(actions []*action) { 219 var wg sync.WaitGroup 220 wg.Add(len(actions)) 221 for _, act := range actions { 222 go func(act *action) { 223 defer wg.Done() 224 act.exec() 225 }(act) 226 } 227 wg.Wait() 228 } 229 230 func (act *action) exec() { act.once.Do(act.execOnce) } 231 232 func (act *action) execOnce() { 233 // Analyze dependencies. 234 execAll(act.deps) 235 236 // Report an error if any dependency failed. 237 var failed []string 238 for _, dep := range act.deps { 239 if dep.err != nil { 240 failed = append(failed, dep.String()) 241 } 242 } 243 if failed != nil { 244 sort.Strings(failed) 245 act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", ")) 246 return 247 } 248 249 // Plumb the output values of the dependencies 250 // into the inputs of this action. 251 inputs := make(map[*analysis.Analyzer]interface{}) 252 for _, dep := range act.deps { 253 // Same package, different analysis (horizontal edge): 254 // in-memory outputs of prerequisite analyzers 255 // become inputs to this analysis pass. 256 inputs[dep.a] = dep.result 257 } 258 259 // Run the analysis. 260 factFilter := make(map[reflect.Type]bool) 261 for _, f := range act.a.FactTypes { 262 factFilter[reflect.TypeOf(f)] = true 263 } 264 pass := &analysis.Pass{ 265 Analyzer: act.a, 266 Fset: act.pkg.fset, 267 Files: act.pkg.syntax, 268 Pkg: act.pkg.types, 269 TypesInfo: act.pkg.typesInfo, 270 ResultOf: inputs, 271 Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) }, 272 ImportPackageFact: act.pkg.facts.ImportPackageFact, 273 ExportPackageFact: act.pkg.facts.ExportPackageFact, 274 ImportObjectFact: act.pkg.facts.ImportObjectFact, 275 ExportObjectFact: act.pkg.facts.ExportObjectFact, 276 AllPackageFacts: func() []analysis.PackageFact { return act.pkg.facts.AllPackageFacts(factFilter) }, 277 AllObjectFacts: func() []analysis.ObjectFact { return act.pkg.facts.AllObjectFacts(factFilter) }, 278 TypesSizes: typesSizes, 279 } 280 act.pass = pass 281 282 var err error 283 if act.pkg.illTyped && !pass.Analyzer.RunDespiteErrors { 284 err = fmt.Errorf("analysis skipped due to type-checking error: %v", act.pkg.typeCheckError) 285 } else { 286 act.result, err = pass.Analyzer.Run(pass) 287 if err == nil { 288 if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want { 289 err = fmt.Errorf( 290 "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v", 291 pass.Pkg.Path(), pass.Analyzer, got, want) 292 } 293 } 294 } 295 act.err = err 296 } 297 298 // load parses and type checks the source code in each file in filenames. 299 // load also deserializes facts stored for imported packages. 300 func load(packagePath string, imp *importer, filenames []string) (*goPackage, error) { 301 if len(filenames) == 0 { 302 return nil, errors.New("no filenames") 303 } 304 var syntax []*ast.File 305 for _, file := range filenames { 306 s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments) 307 if err != nil { 308 return nil, err 309 } 310 syntax = append(syntax, s) 311 } 312 pkg := &goPackage{fset: imp.fset, syntax: syntax} 313 314 config := types.Config{Importer: imp} 315 info := &types.Info{ 316 Types: make(map[ast.Expr]types.TypeAndValue), 317 Uses: make(map[*ast.Ident]types.Object), 318 Defs: make(map[*ast.Ident]types.Object), 319 Implicits: make(map[ast.Node]types.Object), 320 Scopes: make(map[ast.Node]*types.Scope), 321 Selections: make(map[*ast.SelectorExpr]*types.Selection), 322 } 323 types, err := config.Check(packagePath, pkg.fset, syntax, info) 324 if err != nil { 325 pkg.illTyped, pkg.typeCheckError = true, err 326 } 327 pkg.types, pkg.typesInfo = types, info 328 329 pkg.facts, err = facts.Decode(pkg.types, imp.readFacts) 330 if err != nil { 331 return nil, fmt.Errorf("internal error decoding facts: %v", err) 332 } 333 334 return pkg, nil 335 } 336 337 // A goPackage describes a loaded Go package. 338 type goPackage struct { 339 // fset provides position information for types, typesInfo, and syntax. 340 // It is set only when types is set. 341 fset *token.FileSet 342 // syntax is the package's syntax trees. 343 syntax []*ast.File 344 // types provides type information for the package. 345 types *types.Package 346 // facts contains information saved by the analysis framework. Passes may 347 // import facts for imported packages and may also export facts for this 348 // package to be consumed by analyses in downstream packages. 349 facts *facts.Set 350 // illTyped indicates whether the package or any dependency contains errors. 351 // It is set only when types is set. 352 illTyped bool 353 // typeCheckError contains any error encountered during type-checking. It is 354 // only set when illTyped is true. 355 typeCheckError error 356 // typesInfo provides type information about the package's syntax trees. 357 // It is set only when syntax is set. 358 typesInfo *types.Info 359 } 360 361 func (g *goPackage) String() string { 362 return g.types.Path() 363 } 364 365 // checkAnalysisResults checks the analysis diagnostics in the given actions 366 // and returns a string containing all the diagnostics that should be printed 367 // to the build log. 368 func checkAnalysisResults(actions []*action, pkg *goPackage) string { 369 var diagnostics []analysis.Diagnostic 370 var errs []error 371 for _, act := range actions { 372 if act.err != nil { 373 // Analyzer failed. 374 errs = append(errs, fmt.Errorf("analyzer %q failed: %v", act.a.Name, act.err)) 375 continue 376 } 377 if len(act.diagnostics) == 0 { 378 continue 379 } 380 config, ok := configs[act.a.Name] 381 if !ok { 382 // If the analyzer is not explicitly configured, it emits diagnostics for 383 // all files. 384 diagnostics = append(diagnostics, act.diagnostics...) 385 continue 386 } 387 // Discard diagnostics based on the analyzer configuration. 388 for _, d := range act.diagnostics { 389 // NOTE(golang.org/issue/31008): nilness does not set positions, 390 // so don't assume the position is valid. 391 f := pkg.fset.File(d.Pos) 392 filename := "-" 393 if f != nil { 394 filename = f.Name() 395 } 396 include := true 397 if len(config.onlyFiles) > 0 { 398 // This analyzer emits diagnostics for only a set of files. 399 include = false 400 for _, pattern := range config.onlyFiles { 401 if pattern.MatchString(filename) { 402 include = true 403 break 404 } 405 } 406 } 407 if include { 408 for _, pattern := range config.excludeFiles { 409 if pattern.MatchString(filename) { 410 include = false 411 break 412 } 413 } 414 } 415 if include { 416 diagnostics = append(diagnostics, d) 417 } 418 } 419 } 420 if len(diagnostics) == 0 && len(errs) == 0 { 421 return "" 422 } 423 424 sort.Slice(diagnostics, func(i, j int) bool { 425 return diagnostics[i].Pos < diagnostics[j].Pos 426 }) 427 errMsg := &bytes.Buffer{} 428 sep := "" 429 for _, err := range errs { 430 errMsg.WriteString(sep) 431 sep = "\n" 432 errMsg.WriteString(err.Error()) 433 } 434 for _, d := range diagnostics { 435 errMsg.WriteString(sep) 436 sep = "\n" 437 fmt.Fprintf(errMsg, "%s: %s", pkg.fset.Position(d.Pos), d.Message) 438 } 439 return errMsg.String() 440 } 441 442 // config determines which source files an analyzer will emit diagnostics for. 443 // config values are generated in another file that is compiled with 444 // nogo_main.go by the nogo rule. 445 type config struct { 446 // onlyFiles is a list of regular expressions that match files an analyzer 447 // will emit diagnostics for. When empty, the analyzer will emit diagnostics 448 // for all files. 449 onlyFiles []*regexp.Regexp 450 451 // excludeFiles is a list of regular expressions that match files that an 452 // analyzer will not emit diagnostics for. 453 excludeFiles []*regexp.Regexp 454 } 455 456 // importer is an implementation of go/types.Importer that imports type 457 // information from the export data in compiled .a files. 458 type importer struct { 459 fset *token.FileSet 460 importMap map[string]string // map import path in source code to package path 461 packageCache map[string]*types.Package // cache of previously imported packages 462 packageFile map[string]string // map package path to .a file with export data 463 factMap map[string]string // map import path in source code to file containing serialized facts 464 } 465 466 func newImporter(importMap, packageFile map[string]string, factMap map[string]string) *importer { 467 return &importer{ 468 fset: token.NewFileSet(), 469 importMap: importMap, 470 packageCache: make(map[string]*types.Package), 471 packageFile: packageFile, 472 factMap: factMap, 473 } 474 } 475 476 func (i *importer) Import(path string) (*types.Package, error) { 477 if imp, ok := i.importMap[path]; ok { 478 // Translate import path if necessary. 479 path = imp 480 } 481 if path == "unsafe" { 482 // Special case: go/types has pre-defined type information for unsafe. 483 // See https://github.com/golang/go/issues/13882. 484 return types.Unsafe, nil 485 } 486 if pkg, ok := i.packageCache[path]; ok && pkg.Complete() { 487 return pkg, nil // cache hit 488 } 489 490 archive, ok := i.packageFile[path] 491 if !ok { 492 return nil, fmt.Errorf("could not import %q", path) 493 } 494 // open file 495 f, err := os.Open(archive) 496 if err != nil { 497 return nil, err 498 } 499 defer func() { 500 f.Close() 501 if err != nil { 502 // add file name to error 503 err = fmt.Errorf("reading export data: %s: %v", archive, err) 504 } 505 }() 506 507 r, err := gcexportdata.NewReader(f) 508 if err != nil { 509 return nil, err 510 } 511 512 return gcexportdata.Read(r, i.fset, i.packageCache, path) 513 } 514 515 func (i *importer) readFacts(path string) ([]byte, error) { 516 archive := i.factMap[path] 517 if archive == "" { 518 // Packages that were not built with the nogo toolchain will not be 519 // analyzed, so there's no opportunity to store facts. This includes 520 // packages in the standard library and packages built with go_tool_library, 521 // such as coverdata. Analyzers are expected to hard code information 522 // about standard library definitions and must gracefully handle packages 523 // that don't have facts. For example, the "printf" analyzer must know 524 // fmt.Printf accepts a format string. 525 return nil, nil 526 } 527 factReader, err := readFileInArchive(nogoFact, archive) 528 if os.IsNotExist(err) { 529 // Packages that were not built with the nogo toolchain will not be 530 // analyzed, so there's no opportunity to store facts. This includes 531 // packages in the standard library and packages built with go_tool_library, 532 // such as coverdata. 533 return nil, nil 534 } else if err != nil { 535 return nil, err 536 } 537 defer factReader.Close() 538 return ioutil.ReadAll(factReader) 539 } 540 541 type factMultiFlag map[string]string 542 543 func (m *factMultiFlag) String() string { 544 if m == nil || len(*m) == 0 { 545 return "" 546 } 547 return fmt.Sprintf("%v", *m) 548 } 549 550 func (m *factMultiFlag) Set(v string) error { 551 parts := strings.Split(v, "=") 552 if len(parts) != 2 { 553 return fmt.Errorf("badly formatted -fact flag: %s", v) 554 } 555 (*m)[parts[0]] = parts[1] 556 return nil 557 }