github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/tools/nogo/nogo.go (about) 1 // Copyright 2019 The gVisor Authors. 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 // Package nogo implements binary analysis similar to bazel's nogo, 16 // or the unitchecker package. It exists in order to provide additional 17 // facilities for analysis, namely plumbing through the output from 18 // dumping the generated binary (to analyze actual produced code). 19 package nogo 20 21 import ( 22 "bytes" 23 "encoding/gob" 24 "errors" 25 "fmt" 26 "go/ast" 27 "go/build" 28 "go/parser" 29 "go/token" 30 "go/types" 31 "io" 32 "io/ioutil" 33 "log" 34 "os" 35 "path" 36 "path/filepath" 37 "reflect" 38 "sort" 39 "strings" 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 "golang.org/x/tools/go/types/objectpath" 45 46 // Special case: flags live here and change overall behavior. 47 "github.com/SagerNet/gvisor/tools/nogo/objdump" 48 "github.com/SagerNet/gvisor/tools/worker" 49 ) 50 51 // StdlibConfig is serialized as the configuration. 52 // 53 // This contains everything required for stdlib analysis. 54 type StdlibConfig struct { 55 Srcs []string 56 GOOS string 57 GOARCH string 58 Tags []string 59 } 60 61 // PackageConfig is serialized as the configuration. 62 // 63 // This contains everything required for single package analysis. 64 type PackageConfig struct { 65 ImportPath string 66 GoFiles []string 67 NonGoFiles []string 68 Tags []string 69 GOOS string 70 GOARCH string 71 ImportMap map[string]string 72 FactMap map[string]string 73 StdlibFacts string 74 } 75 76 // loader is a fact-loader function. 77 type loader func(string) ([]byte, error) 78 79 // saver is a fact-saver function. 80 type saver func([]byte) error 81 82 // stdlibFact is used for serialiation. 83 type stdlibFact struct { 84 Package string 85 Facts []byte 86 } 87 88 // stdlibFacts is a set of standard library facts. 89 type stdlibFacts map[string][]byte 90 91 // Size implements worker.Sizer.Size. 92 func (sf stdlibFacts) Size() int64 { 93 size := int64(0) 94 for filename, data := range sf { 95 size += int64(len(filename)) 96 size += int64(len(data)) 97 } 98 return size 99 } 100 101 // EncodeTo serializes stdlibFacts. 102 func (sf stdlibFacts) EncodeTo(w io.Writer) error { 103 stdlibFactsSorted := make([]stdlibFact, 0, len(sf)) 104 for pkg, facts := range sf { 105 stdlibFactsSorted = append(stdlibFactsSorted, stdlibFact{ 106 Package: pkg, 107 Facts: facts, 108 }) 109 } 110 sort.Slice(stdlibFactsSorted, func(i, j int) bool { 111 return stdlibFactsSorted[i].Package < stdlibFactsSorted[j].Package 112 }) 113 enc := gob.NewEncoder(w) 114 if err := enc.Encode(stdlibFactsSorted); err != nil { 115 return err 116 } 117 return nil 118 } 119 120 // DecodeFrom deserializes stdlibFacts. 121 func (sf stdlibFacts) DecodeFrom(r io.Reader) error { 122 var stdlibFactsSorted []stdlibFact 123 dec := gob.NewDecoder(r) 124 if err := dec.Decode(&stdlibFactsSorted); err != nil { 125 return err 126 } 127 for _, stdlibFact := range stdlibFactsSorted { 128 sf[stdlibFact.Package] = stdlibFact.Facts 129 } 130 return nil 131 } 132 133 var ( 134 // cachedFacts caches by file (just byte data). 135 cachedFacts = worker.NewCache("facts") 136 137 // stdlibCachedFacts caches the standard library (stdlibFacts). 138 stdlibCachedFacts = worker.NewCache("stdlib") 139 ) 140 141 // factLoader loads facts. 142 func (c *PackageConfig) factLoader(path string) (data []byte, err error) { 143 filename, ok := c.FactMap[path] 144 if ok { 145 cb := cachedFacts.Lookup([]string{filename}, func() worker.Sizer { 146 data, readErr := ioutil.ReadFile(filename) 147 if readErr != nil { 148 err = fmt.Errorf("error loading %q: %w", filename, readErr) 149 return nil 150 } 151 return worker.CacheBytes(data) 152 }) 153 if cb != nil { 154 return []byte(cb.(worker.CacheBytes)), err 155 } 156 return nil, err 157 } 158 cb := stdlibCachedFacts.Lookup([]string{c.StdlibFacts}, func() worker.Sizer { 159 r, openErr := os.Open(c.StdlibFacts) 160 if openErr != nil { 161 err = fmt.Errorf("error loading stdlib facts from %q: %w", c.StdlibFacts, openErr) 162 return nil 163 } 164 defer r.Close() 165 sf := make(stdlibFacts) 166 if readErr := sf.DecodeFrom(r); readErr != nil { 167 err = fmt.Errorf("error loading stdlib facts: %w", readErr) 168 return nil 169 } 170 return sf 171 }) 172 if cb != nil { 173 return (cb.(stdlibFacts))[path], err 174 } 175 return nil, err 176 } 177 178 // shouldInclude indicates whether the file should be included. 179 // 180 // NOTE: This does only basic parsing of tags. 181 func (c *PackageConfig) shouldInclude(path string) (bool, error) { 182 ctx := build.Default 183 ctx.GOOS = c.GOOS 184 ctx.GOARCH = c.GOARCH 185 ctx.BuildTags = c.Tags 186 return ctx.MatchFile(filepath.Dir(path), filepath.Base(path)) 187 } 188 189 // importer is an implementation of go/types.Importer. 190 // 191 // This wraps a configuration, which provides the map of package names to 192 // files, and the facts. Note that this importer implementation will always 193 // pass when a given package is not available. 194 type importer struct { 195 *PackageConfig 196 fset *token.FileSet 197 cache map[string]*types.Package 198 lastErr error 199 callback func(string) error 200 } 201 202 // Import implements types.Importer.Import. 203 func (i *importer) Import(path string) (*types.Package, error) { 204 if path == "unsafe" { 205 // Special case: go/types has pre-defined type information for 206 // unsafe. We ensure that this package is correct, in case any 207 // analyzers are specifically looking for this. 208 return types.Unsafe, nil 209 } 210 211 // Call the internal callback. This is used to resolve loading order 212 // for the standard library. See checkStdlib. 213 if i.callback != nil { 214 if err := i.callback(path); err != nil { 215 i.lastErr = err 216 return nil, err 217 } 218 } 219 220 // Check the cache. 221 if pkg, ok := i.cache[path]; ok && pkg.Complete() { 222 return pkg, nil 223 } 224 225 // Actually load the data. 226 realPath, ok := i.ImportMap[path] 227 var ( 228 rc io.ReadCloser 229 err error 230 ) 231 if !ok { 232 // Not found in the import path. Attempt to find the package 233 // via the standard library. 234 rc, err = findStdPkg(i.GOOS, i.GOARCH, path) 235 } else { 236 // Open the file. 237 rc, err = os.Open(realPath) 238 } 239 if err != nil { 240 i.lastErr = err 241 return nil, err 242 } 243 defer rc.Close() 244 245 // Load all exported data. 246 r, err := gcexportdata.NewReader(rc) 247 if err != nil { 248 return nil, err 249 } 250 251 return gcexportdata.Read(r, i.fset, i.cache, path) 252 } 253 254 // ErrSkip indicates the package should be skipped. 255 var ErrSkip = errors.New("skipped") 256 257 // CheckStdlib checks the standard library. 258 // 259 // This constructs a synthetic package configuration for each library in the 260 // standard library sources, and call CheckPackage repeatedly. 261 // 262 // Note that not all parts of the source are expected to build. We skip obvious 263 // test files, and cmd files, which should not be dependencies. 264 func CheckStdlib(config *StdlibConfig, analyzers []*analysis.Analyzer) (allFindings FindingSet, facts []byte, err error) { 265 if len(config.Srcs) == 0 { 266 return nil, nil, nil 267 } 268 269 // Ensure all paths are normalized. 270 for i := 0; i < len(config.Srcs); i++ { 271 config.Srcs[i] = path.Clean(config.Srcs[i]) 272 } 273 274 // Calculate the root source directory. This is always a directory 275 // named 'src', of which we simply take the first we find. This is a 276 // bit fragile, but works for all currently known Go source 277 // configurations. 278 // 279 // Note that there may be extra files outside of the root source 280 // directory; we simply ignore those. 281 rootSrcPrefix := "" 282 for _, file := range config.Srcs { 283 const src = "/src/" 284 i := strings.Index(file, src) 285 if i == -1 { 286 // Superfluous file. 287 continue 288 } 289 290 // Index of first character after /src/. 291 i += len(src) 292 rootSrcPrefix = file[:i] 293 break 294 } 295 296 // Aggregate all files by directory. 297 packages := make(map[string]*PackageConfig) 298 for _, file := range config.Srcs { 299 if !strings.HasPrefix(file, rootSrcPrefix) { 300 // Superflouous file. 301 continue 302 } 303 304 d := path.Dir(file) 305 if len(rootSrcPrefix) >= len(d) { 306 continue // Not a file. 307 } 308 pkg := d[len(rootSrcPrefix):] 309 // Skip cmd packages and obvious test files: see above. 310 if strings.HasPrefix(pkg, "cmd/") || strings.HasSuffix(file, "_test.go") { 311 continue 312 } 313 c, ok := packages[pkg] 314 if !ok { 315 c = &PackageConfig{ 316 ImportPath: pkg, 317 GOOS: config.GOOS, 318 GOARCH: config.GOARCH, 319 Tags: config.Tags, 320 } 321 packages[pkg] = c 322 } 323 // Add the files appropriately. Note that they will be further 324 // filtered by architecture and build tags below, so this need 325 // not be done immediately. 326 if strings.HasSuffix(file, ".go") { 327 c.GoFiles = append(c.GoFiles, file) 328 } else { 329 c.NonGoFiles = append(c.NonGoFiles, file) 330 } 331 } 332 333 // Closure to check a single package. 334 localStdlibFacts := make(stdlibFacts) 335 localStdlibErrs := make(map[string]error) 336 stdlibCachedFacts.Lookup([]string{""}, func() worker.Sizer { 337 return localStdlibFacts 338 }) 339 var checkOne func(pkg string) error // Recursive. 340 checkOne = func(pkg string) error { 341 // Is this already done? 342 if _, ok := localStdlibFacts[pkg]; ok { 343 return nil 344 } 345 // Did this fail previously? 346 if _, ok := localStdlibErrs[pkg]; ok { 347 return nil 348 } 349 350 // Lookup the configuration. 351 config, ok := packages[pkg] 352 if !ok { 353 return nil // Not known. 354 } 355 356 // Find the binary package, and provide to objdump. 357 rc, err := findStdPkg(config.GOOS, config.GOARCH, pkg) 358 if err != nil { 359 // If there's no binary for this package, it is likely 360 // not built with the distribution. That's fine, we can 361 // just skip analysis. 362 localStdlibErrs[pkg] = err 363 return nil 364 } 365 366 // Provide the input. 367 oldReader := objdump.Reader 368 objdump.Reader = rc // For analysis. 369 defer func() { 370 rc.Close() 371 objdump.Reader = oldReader // Restore. 372 }() 373 374 // Run the analysis. 375 findings, factData, err := CheckPackage(config, analyzers, checkOne) 376 if err != nil { 377 // If we can't analyze a package from the standard library, 378 // then we skip it. It will simply not have any findings. 379 localStdlibErrs[pkg] = err 380 return nil 381 } 382 localStdlibFacts[pkg] = factData 383 allFindings = append(allFindings, findings...) 384 return nil 385 } 386 387 // Check all packages. 388 // 389 // Note that this may call checkOne recursively, so it's not guaranteed 390 // to evaluate in the order provided here. We do ensure however, that 391 // all packages are evaluated. 392 for pkg := range packages { 393 if err := checkOne(pkg); err != nil { 394 return nil, nil, err 395 } 396 } 397 398 // Sanity check. 399 if len(localStdlibFacts) == 0 { 400 return nil, nil, fmt.Errorf("no stdlib facts found: misconfiguration?") 401 } 402 403 // Write out all findings. 404 buf := bytes.NewBuffer(nil) 405 if err := localStdlibFacts.EncodeTo(buf); err != nil { 406 return nil, nil, fmt.Errorf("error serialized stdlib facts: %v", err) 407 } 408 409 // Write out all errors. 410 for pkg, err := range localStdlibErrs { 411 log.Printf("WARNING: error while processing %v: %v", pkg, err) 412 } 413 414 // Return all findings. 415 return allFindings, buf.Bytes(), nil 416 } 417 418 // sanityCheckScope checks that all object in astTypes map to the correct 419 // objects in binaryTypes. Note that we don't check whether the sets are the 420 // same, we only care about the fidelity of objects in astTypes. 421 // 422 // When an inconsistency is identified, we record it in the astToBinaryMap. 423 // This allows us to dynamically replace facts and correct for the issue. The 424 // total number of mismatches is returned. 425 func sanityCheckScope(astScope *types.Scope, binaryTypes *types.Package, binaryScope *types.Scope, astToBinary map[types.Object]types.Object) error { 426 for _, x := range astScope.Names() { 427 fe := astScope.Lookup(x) 428 path, err := objectpath.For(fe) 429 if err != nil { 430 continue // Not an encoded object. 431 } 432 se, err := objectpath.Object(binaryTypes, path) 433 if err != nil { 434 continue // May be unused, see below. 435 } 436 if fe.Id() != se.Id() { 437 // These types are incompatible. This means that when 438 // this objectpath is loading from the binaryTypes (for 439 // dependencies) it will resolve to a fact for that 440 // type. We don't actually care about this error since 441 // we do the rewritten, but may as well alert. 442 log.Printf("WARNING: Object %s is a victim of go/issues/44195.", fe.Id()) 443 } 444 se = binaryScope.Lookup(x) 445 if se == nil { 446 // The fact may not be exported in the objectdata, if 447 // it is package internal. This is fine, as nothing out 448 // of this package can use these symbols. 449 continue 450 } 451 // Save the translation. 452 astToBinary[fe] = se 453 } 454 for i := 0; i < astScope.NumChildren(); i++ { 455 if err := sanityCheckScope(astScope.Child(i), binaryTypes, binaryScope, astToBinary); err != nil { 456 return err 457 } 458 } 459 return nil 460 } 461 462 // sanityCheckTypes checks that two types are sane. The total number of 463 // mismatches is returned. 464 func sanityCheckTypes(astTypes, binaryTypes *types.Package, astToBinary map[types.Object]types.Object) error { 465 return sanityCheckScope(astTypes.Scope(), binaryTypes, binaryTypes.Scope(), astToBinary) 466 } 467 468 // CheckPackage runs all given analyzers. 469 // 470 // The implementation was adapted from [1], which was in turn adpated from [2]. 471 // This returns a list of matching analysis issues, or an error if the analysis 472 // could not be completed. 473 // 474 // [1] bazelbuid/rules_go/tools/builders/nogo_main.go 475 // [2] golang.org/x/tools/go/checker/internal/checker 476 func CheckPackage(config *PackageConfig, analyzers []*analysis.Analyzer, importCallback func(string) error) (findings []Finding, factData []byte, err error) { 477 imp := &importer{ 478 PackageConfig: config, 479 fset: token.NewFileSet(), 480 cache: make(map[string]*types.Package), 481 callback: importCallback, 482 } 483 484 // Load all source files. 485 var syntax []*ast.File 486 for _, file := range config.GoFiles { 487 include, err := config.shouldInclude(file) 488 if err != nil { 489 return nil, nil, fmt.Errorf("error evaluating file %q: %v", file, err) 490 } 491 if !include { 492 continue 493 } 494 s, err := parser.ParseFile(imp.fset, file, nil, parser.ParseComments) 495 if err != nil { 496 return nil, nil, fmt.Errorf("error parsing file %q: %v", file, err) 497 } 498 syntax = append(syntax, s) 499 } 500 501 // Check type information. 502 typesSizes := types.SizesFor("gc", config.GOARCH) 503 typeConfig := types.Config{Importer: imp} 504 typesInfo := &types.Info{ 505 Types: make(map[ast.Expr]types.TypeAndValue), 506 Uses: make(map[*ast.Ident]types.Object), 507 Defs: make(map[*ast.Ident]types.Object), 508 Implicits: make(map[ast.Node]types.Object), 509 Scopes: make(map[ast.Node]*types.Scope), 510 Selections: make(map[*ast.SelectorExpr]*types.Selection), 511 } 512 astTypes, err := typeConfig.Check(config.ImportPath, imp.fset, syntax, typesInfo) 513 if err != nil && imp.lastErr != ErrSkip { 514 return nil, nil, fmt.Errorf("error checking types: %w", err) 515 } 516 517 // Load all facts using the astTypes, although it may need reconciling 518 // later on. See the fact functions below. 519 astFacts, err := facts.Decode(astTypes, config.factLoader) 520 if err != nil { 521 return nil, nil, fmt.Errorf("error decoding facts: %w", err) 522 } 523 524 // Sanity check all types and record metadata to prevent 525 // https://github.com/golang/go/issues/44195. 526 // 527 // This block loads the binary types, whose encoding will be well 528 // defined and aligned with any downstream consumers. Below in the fact 529 // functions for the analysis, we serialize types to both the astFacts 530 // and the binaryFacts if available. The binaryFacts are the final 531 // encoded facts in order to ensure compatibility. We keep the 532 // intermediate astTypes in order to allow exporting and importing 533 // within the local package under analysis. 534 var ( 535 astToBinary = make(map[types.Object]types.Object) 536 binaryFacts *facts.Set 537 ) 538 if _, ok := config.ImportMap[config.ImportPath]; ok { 539 binaryTypes, err := imp.Import(config.ImportPath) 540 if err != nil { 541 return nil, nil, fmt.Errorf("error loading self: %w", err) 542 } 543 if err := sanityCheckTypes(astTypes, binaryTypes, astToBinary); err != nil { 544 return nil, nil, fmt.Errorf("error sanity checking types: %w", err) 545 } 546 binaryFacts, err = facts.Decode(binaryTypes, config.factLoader) 547 if err != nil { 548 return nil, nil, fmt.Errorf("error decoding facts: %w", err) 549 } 550 } 551 552 // Register fact types and establish dependencies between analyzers. 553 // The visit closure will execute recursively, and populate results 554 // will all required analysis results. 555 results := make(map[*analysis.Analyzer]interface{}) 556 var visit func(*analysis.Analyzer) error // For recursion. 557 visit = func(a *analysis.Analyzer) error { 558 if _, ok := results[a]; ok { 559 return nil 560 } 561 562 // Run recursively for all dependencies. 563 for _, req := range a.Requires { 564 if err := visit(req); err != nil { 565 return err 566 } 567 } 568 569 // Run the analysis. 570 localFactsFilter := make(map[reflect.Type]bool) 571 for _, f := range a.FactTypes { 572 localFactsFilter[reflect.TypeOf(f)] = true 573 } 574 p := &analysis.Pass{ 575 Analyzer: a, 576 Fset: imp.fset, 577 Files: syntax, 578 Pkg: astTypes, 579 TypesInfo: typesInfo, 580 ResultOf: results, // All results. 581 Report: func(d analysis.Diagnostic) { 582 findings = append(findings, Finding{ 583 Category: AnalyzerName(a.Name), 584 Position: imp.fset.Position(d.Pos), 585 Message: d.Message, 586 }) 587 }, 588 ImportPackageFact: astFacts.ImportPackageFact, 589 ExportPackageFact: func(fact analysis.Fact) { 590 astFacts.ExportPackageFact(fact) 591 if binaryFacts != nil { 592 binaryFacts.ExportPackageFact(fact) 593 } 594 }, 595 ImportObjectFact: astFacts.ImportObjectFact, 596 ExportObjectFact: func(obj types.Object, fact analysis.Fact) { 597 astFacts.ExportObjectFact(obj, fact) 598 // Note that if no object is recorded in 599 // astToBinary and binaryFacts != nil, then the 600 // object doesn't appear in the exported data. 601 // It was likely an internal object to the 602 // package, and there is no meaningful 603 // downstream consumer of the fact. 604 if binaryObj, ok := astToBinary[obj]; ok && binaryFacts != nil { 605 binaryFacts.ExportObjectFact(binaryObj, fact) 606 } 607 }, 608 AllPackageFacts: func() []analysis.PackageFact { return astFacts.AllPackageFacts(localFactsFilter) }, 609 AllObjectFacts: func() []analysis.ObjectFact { return astFacts.AllObjectFacts(localFactsFilter) }, 610 TypesSizes: typesSizes, 611 } 612 result, err := a.Run(p) 613 if err != nil { 614 return fmt.Errorf("error running analysis %s: %v", a, err) 615 } 616 617 // Sanity check & save the result. 618 if got, want := reflect.TypeOf(result), a.ResultType; got != want { 619 return fmt.Errorf("error: analyzer %s returned a result of type %v, but declared ResultType %v", a, got, want) 620 } 621 results[a] = result 622 return nil // Success. 623 } 624 625 // Visit all analyzers recursively. 626 for _, a := range analyzers { 627 if imp.lastErr == ErrSkip { 628 continue // No local analysis. 629 } 630 if err := visit(a); err != nil { 631 return nil, nil, err // Already has context. 632 } 633 } 634 635 // Return all findings. Note that we have a preference to returning the 636 // binary facts if available, so that downstream consumers of these 637 // facts will find the export aligns with the internal type details. 638 // See the block above with the call to sanityCheckTypes. 639 if binaryFacts != nil { 640 return findings, binaryFacts.Encode(), nil 641 } 642 return findings, astFacts.Encode(), nil 643 } 644 645 func init() { 646 gob.Register((*stdlibFact)(nil)) 647 }