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