github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modindex/read.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modindex 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "go/build" 13 "go/build/constraint" 14 "go/token" 15 "path" 16 "path/filepath" 17 "runtime" 18 "runtime/debug" 19 "sort" 20 "strings" 21 "sync" 22 "time" 23 "unsafe" 24 25 "github.com/go-asm/go/godebug" 26 "github.com/go-asm/go/goroot" 27 28 "github.com/go-asm/go/cmd/go/base" 29 "github.com/go-asm/go/cmd/go/cache" 30 "github.com/go-asm/go/cmd/go/cfg" 31 "github.com/go-asm/go/cmd/go/fsys" 32 "github.com/go-asm/go/cmd/go/imports" 33 "github.com/go-asm/go/cmd/go/par" 34 "github.com/go-asm/go/cmd/go/str" 35 ) 36 37 // enabled is used to flag off the behavior of the module index on tip. 38 // It will be removed before the release. 39 // TODO(matloob): Remove enabled once we have more confidence on the 40 // module index. 41 var enabled = godebug.New("#goindex").Value() != "0" 42 43 // Module represents and encoded module index file. It is used to 44 // do the equivalent of build.Import of packages in the module and answer other 45 // questions based on the index file's data. 46 type Module struct { 47 modroot string 48 d *decoder 49 n int // number of packages 50 } 51 52 // moduleHash returns an ActionID corresponding to the state of the module 53 // located at filesystem path modroot. 54 func moduleHash(modroot string, ismodcache bool) (cache.ActionID, error) { 55 // We expect modules stored within the module cache to be checksummed and 56 // immutable, and we expect released modules within GOROOT to change only 57 // infrequently (when the Go version changes). 58 if !ismodcache { 59 // The contents of this module may change over time. We don't want to pay 60 // the cost to detect changes and re-index whenever they occur, so just 61 // don't index it at all. 62 // 63 // Note that this is true even for modules in GOROOT/src: non-release builds 64 // of the Go toolchain may have arbitrary development changes on top of the 65 // commit reported by runtime.Version, or could be completely artificial due 66 // to lacking a `git` binary (like "devel gomote.XXXXX", as synthesized by 67 // "gomote push" as of 2022-06-15). (Release builds shouldn't have 68 // modifications, but we don't want to use a behavior for releases that we 69 // haven't tested during development.) 70 return cache.ActionID{}, ErrNotIndexed 71 } 72 73 h := cache.NewHash("moduleIndex") 74 // TODO(bcmills): Since modules in the index are checksummed, we could 75 // probably improve the cache hit rate by keying off of the module 76 // path@version (perhaps including the checksum?) instead of the module root 77 // directory. 78 fmt.Fprintf(h, "module index %s %s %v\n", runtime.Version(), indexVersion, modroot) 79 return h.Sum(), nil 80 } 81 82 const modTimeCutoff = 2 * time.Second 83 84 // dirHash returns an ActionID corresponding to the state of the package 85 // located at filesystem path pkgdir. 86 func dirHash(modroot, pkgdir string) (cache.ActionID, error) { 87 h := cache.NewHash("moduleIndex") 88 fmt.Fprintf(h, "modroot %s\n", modroot) 89 fmt.Fprintf(h, "package %s %s %v\n", runtime.Version(), indexVersion, pkgdir) 90 entries, err := fsys.ReadDir(pkgdir) 91 if err != nil { 92 // pkgdir might not be a directory. give up on hashing. 93 return cache.ActionID{}, ErrNotIndexed 94 } 95 cutoff := time.Now().Add(-modTimeCutoff) 96 for _, info := range entries { 97 if info.IsDir() { 98 continue 99 } 100 101 if !info.Mode().IsRegular() { 102 return cache.ActionID{}, ErrNotIndexed 103 } 104 // To avoid problems for very recent files where a new 105 // write might not change the mtime due to file system 106 // mtime precision, reject caching if a file was read that 107 // is less than modTimeCutoff old. 108 // 109 // This is the same strategy used for hashing test inputs. 110 // See hashOpen in github.com/go-asm/go/cmd/go/test/test.go for the 111 // corresponding code. 112 if info.ModTime().After(cutoff) { 113 return cache.ActionID{}, ErrNotIndexed 114 } 115 116 fmt.Fprintf(h, "file %v %v %v\n", info.Name(), info.ModTime(), info.Size()) 117 } 118 return h.Sum(), nil 119 } 120 121 var ErrNotIndexed = errors.New("not in module index") 122 123 var ( 124 errDisabled = fmt.Errorf("%w: module indexing disabled", ErrNotIndexed) 125 errNotFromModuleCache = fmt.Errorf("%w: not from module cache", ErrNotIndexed) 126 ) 127 128 // GetPackage returns the IndexPackage for the package at the given path. 129 // It will return ErrNotIndexed if the directory should be read without 130 // using the index, for instance because the index is disabled, or the package 131 // is not in a module. 132 func GetPackage(modroot, pkgdir string) (*IndexPackage, error) { 133 mi, err := GetModule(modroot) 134 if err == nil { 135 return mi.Package(relPath(pkgdir, modroot)), nil 136 } 137 if !errors.Is(err, errNotFromModuleCache) { 138 return nil, err 139 } 140 if cfg.BuildContext.Compiler == "gccgo" && str.HasPathPrefix(modroot, cfg.GOROOTsrc) { 141 return nil, err // gccgo has no sources for GOROOT packages. 142 } 143 return openIndexPackage(modroot, pkgdir) 144 } 145 146 // GetModule returns the Module for the given modroot. 147 // It will return ErrNotIndexed if the directory should be read without 148 // using the index, for instance because the index is disabled, or the package 149 // is not in a module. 150 func GetModule(modroot string) (*Module, error) { 151 if !enabled || cache.DefaultDir() == "off" { 152 return nil, errDisabled 153 } 154 if modroot == "" { 155 panic("modindex.GetPackage called with empty modroot") 156 } 157 if cfg.BuildMod == "vendor" { 158 // Even if the main module is in the module cache, 159 // its vendored dependencies are not loaded from their 160 // usual cached locations. 161 return nil, errNotFromModuleCache 162 } 163 modroot = filepath.Clean(modroot) 164 if str.HasFilePathPrefix(modroot, cfg.GOROOTsrc) || !str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) { 165 return nil, errNotFromModuleCache 166 } 167 return openIndexModule(modroot, true) 168 } 169 170 var mcache par.ErrCache[string, *Module] 171 172 // openIndexModule returns the module index for modPath. 173 // It will return ErrNotIndexed if the module can not be read 174 // using the index because it contains symlinks. 175 func openIndexModule(modroot string, ismodcache bool) (*Module, error) { 176 return mcache.Do(modroot, func() (*Module, error) { 177 fsys.Trace("openIndexModule", modroot) 178 id, err := moduleHash(modroot, ismodcache) 179 if err != nil { 180 return nil, err 181 } 182 data, _, err := cache.GetMmap(cache.Default(), id) 183 if err != nil { 184 // Couldn't read from modindex. Assume we couldn't read from 185 // the index because the module hasn't been indexed yet. 186 data, err = indexModule(modroot) 187 if err != nil { 188 return nil, err 189 } 190 if err = cache.PutBytes(cache.Default(), id, data); err != nil { 191 return nil, err 192 } 193 } 194 mi, err := fromBytes(modroot, data) 195 if err != nil { 196 return nil, err 197 } 198 return mi, nil 199 }) 200 } 201 202 var pcache par.ErrCache[[2]string, *IndexPackage] 203 204 func openIndexPackage(modroot, pkgdir string) (*IndexPackage, error) { 205 return pcache.Do([2]string{modroot, pkgdir}, func() (*IndexPackage, error) { 206 fsys.Trace("openIndexPackage", pkgdir) 207 id, err := dirHash(modroot, pkgdir) 208 if err != nil { 209 return nil, err 210 } 211 data, _, err := cache.GetMmap(cache.Default(), id) 212 if err != nil { 213 // Couldn't read from index. Assume we couldn't read from 214 // the index because the package hasn't been indexed yet. 215 data = indexPackage(modroot, pkgdir) 216 if err = cache.PutBytes(cache.Default(), id, data); err != nil { 217 return nil, err 218 } 219 } 220 pkg, err := packageFromBytes(modroot, data) 221 if err != nil { 222 return nil, err 223 } 224 return pkg, nil 225 }) 226 } 227 228 var errCorrupt = errors.New("corrupt index") 229 230 // protect marks the start of a large section of code that accesses the index. 231 // It should be used as: 232 // 233 // defer unprotect(protect, &err) 234 // 235 // It should not be used for trivial accesses which would be 236 // dwarfed by the overhead of the defer. 237 func protect() bool { 238 return debug.SetPanicOnFault(true) 239 } 240 241 var isTest = false 242 243 // unprotect marks the end of a large section of code that accesses the index. 244 // It should be used as: 245 // 246 // defer unprotect(protect, &err) 247 // 248 // end looks for panics due to errCorrupt or bad mmap accesses. 249 // When it finds them, it adds explanatory text, consumes the panic, and sets *errp instead. 250 // If errp is nil, end adds the explanatory text but then calls base.Fatalf. 251 func unprotect(old bool, errp *error) { 252 // SetPanicOnFault's errors _may_ satisfy this interface. Even though it's not guaranteed 253 // that all its errors satisfy this interface, we'll only check for these errors so that 254 // we don't suppress panics that could have been produced from other sources. 255 type addrer interface { 256 Addr() uintptr 257 } 258 259 debug.SetPanicOnFault(old) 260 261 if e := recover(); e != nil { 262 if _, ok := e.(addrer); ok || e == errCorrupt { 263 // This panic was almost certainly caused by SetPanicOnFault or our panic(errCorrupt). 264 err := fmt.Errorf("error reading module index: %v", e) 265 if errp != nil { 266 *errp = err 267 return 268 } 269 if isTest { 270 panic(err) 271 } 272 base.Fatalf("%v", err) 273 } 274 // The panic was likely not caused by SetPanicOnFault. 275 panic(e) 276 } 277 } 278 279 // fromBytes returns a *Module given the encoded representation. 280 func fromBytes(moddir string, data []byte) (m *Module, err error) { 281 if !enabled { 282 panic("use of index") 283 } 284 285 defer unprotect(protect(), &err) 286 287 if !bytes.HasPrefix(data, []byte(indexVersion+"\n")) { 288 return nil, errCorrupt 289 } 290 291 const hdr = len(indexVersion + "\n") 292 d := &decoder{data: data} 293 str := d.intAt(hdr) 294 if str < hdr+8 || len(d.data) < str { 295 return nil, errCorrupt 296 } 297 d.data, d.str = data[:str], d.data[str:] 298 // Check that string table looks valid. 299 // First string is empty string (length 0), 300 // and we leave a marker byte 0xFF at the end 301 // just to make sure that the file is not truncated. 302 if len(d.str) == 0 || d.str[0] != 0 || d.str[len(d.str)-1] != 0xFF { 303 return nil, errCorrupt 304 } 305 306 n := d.intAt(hdr + 4) 307 if n < 0 || n > (len(d.data)-8)/8 { 308 return nil, errCorrupt 309 } 310 311 m = &Module{ 312 moddir, 313 d, 314 n, 315 } 316 return m, nil 317 } 318 319 // packageFromBytes returns a *IndexPackage given the encoded representation. 320 func packageFromBytes(modroot string, data []byte) (p *IndexPackage, err error) { 321 m, err := fromBytes(modroot, data) 322 if err != nil { 323 return nil, err 324 } 325 if m.n != 1 { 326 return nil, fmt.Errorf("corrupt single-package index") 327 } 328 return m.pkg(0), nil 329 } 330 331 // pkgDir returns the dir string of the i'th package in the index. 332 func (m *Module) pkgDir(i int) string { 333 if i < 0 || i >= m.n { 334 panic(errCorrupt) 335 } 336 return m.d.stringAt(12 + 8 + 8*i) 337 } 338 339 // pkgOff returns the offset of the data for the i'th package in the index. 340 func (m *Module) pkgOff(i int) int { 341 if i < 0 || i >= m.n { 342 panic(errCorrupt) 343 } 344 return m.d.intAt(12 + 8 + 8*i + 4) 345 } 346 347 // Walk calls f for each package in the index, passing the path to that package relative to the module root. 348 func (m *Module) Walk(f func(path string)) { 349 defer unprotect(protect(), nil) 350 for i := 0; i < m.n; i++ { 351 f(m.pkgDir(i)) 352 } 353 } 354 355 // relPath returns the path relative to the module's root. 356 func relPath(path, modroot string) string { 357 return str.TrimFilePathPrefix(filepath.Clean(path), filepath.Clean(modroot)) 358 } 359 360 var installgorootAll = godebug.New("installgoroot").Value() == "all" 361 362 // Import is the equivalent of build.Import given the information in Module. 363 func (rp *IndexPackage) Import(bctxt build.Context, mode build.ImportMode) (p *build.Package, err error) { 364 defer unprotect(protect(), &err) 365 366 ctxt := (*Context)(&bctxt) 367 368 p = &build.Package{} 369 370 p.ImportPath = "." 371 p.Dir = filepath.Join(rp.modroot, rp.dir) 372 373 var pkgerr error 374 switch ctxt.Compiler { 375 case "gccgo", "gc": 376 default: 377 // Save error for end of function. 378 pkgerr = fmt.Errorf("import %q: unknown compiler %q", p.Dir, ctxt.Compiler) 379 } 380 381 if p.Dir == "" { 382 return p, fmt.Errorf("import %q: import of unknown directory", p.Dir) 383 } 384 385 // goroot and gopath 386 inTestdata := func(sub string) bool { 387 return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || str.HasPathPrefix(sub, "testdata") 388 } 389 var pkga string 390 if !inTestdata(rp.dir) { 391 // In build.go, p.Root should only be set in the non-local-import case, or in 392 // GOROOT or GOPATH. Since module mode only calls Import with path set to "." 393 // and the module index doesn't apply outside modules, the GOROOT case is 394 // the only case where p.Root needs to be set. 395 if ctxt.GOROOT != "" && str.HasFilePathPrefix(p.Dir, cfg.GOROOTsrc) && p.Dir != cfg.GOROOTsrc { 396 p.Root = ctxt.GOROOT 397 p.Goroot = true 398 modprefix := str.TrimFilePathPrefix(rp.modroot, cfg.GOROOTsrc) 399 p.ImportPath = rp.dir 400 if modprefix != "" { 401 p.ImportPath = filepath.Join(modprefix, p.ImportPath) 402 } 403 404 // Set GOROOT-specific fields (sometimes for modules in a GOPATH directory). 405 // The fields set below (SrcRoot, PkgRoot, BinDir, PkgTargetRoot, and PkgObj) 406 // are only set in build.Import if p.Root != "". 407 var pkgtargetroot string 408 suffix := "" 409 if ctxt.InstallSuffix != "" { 410 suffix = "_" + ctxt.InstallSuffix 411 } 412 switch ctxt.Compiler { 413 case "gccgo": 414 pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix 415 dir, elem := path.Split(p.ImportPath) 416 pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" 417 case "gc": 418 pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix 419 pkga = pkgtargetroot + "/" + p.ImportPath + ".a" 420 } 421 p.SrcRoot = ctxt.joinPath(p.Root, "src") 422 p.PkgRoot = ctxt.joinPath(p.Root, "pkg") 423 p.BinDir = ctxt.joinPath(p.Root, "bin") 424 if pkga != "" { 425 // Always set PkgTargetRoot. It might be used when building in shared 426 // mode. 427 p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) 428 429 // Set the install target if applicable. 430 if !p.Goroot || (installgorootAll && p.ImportPath != "unsafe" && p.ImportPath != "builtin") { 431 p.PkgObj = ctxt.joinPath(p.Root, pkga) 432 } 433 } 434 } 435 } 436 437 if rp.error != nil { 438 if errors.Is(rp.error, errCannotFindPackage) && ctxt.Compiler == "gccgo" && p.Goroot { 439 return p, nil 440 } 441 return p, rp.error 442 } 443 444 if mode&build.FindOnly != 0 { 445 return p, pkgerr 446 } 447 448 // We need to do a second round of bad file processing. 449 var badGoError error 450 badGoFiles := make(map[string]bool) 451 badGoFile := func(name string, err error) { 452 if badGoError == nil { 453 badGoError = err 454 } 455 if !badGoFiles[name] { 456 p.InvalidGoFiles = append(p.InvalidGoFiles, name) 457 badGoFiles[name] = true 458 } 459 } 460 461 var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems) 462 var firstFile string 463 embedPos := make(map[string][]token.Position) 464 testEmbedPos := make(map[string][]token.Position) 465 xTestEmbedPos := make(map[string][]token.Position) 466 importPos := make(map[string][]token.Position) 467 testImportPos := make(map[string][]token.Position) 468 xTestImportPos := make(map[string][]token.Position) 469 allTags := make(map[string]bool) 470 for _, tf := range rp.sourceFiles { 471 name := tf.name() 472 // Check errors for go files and call badGoFiles to put them in 473 // InvalidGoFiles if they do have an error. 474 if strings.HasSuffix(name, ".go") { 475 if error := tf.error(); error != "" { 476 badGoFile(name, errors.New(tf.error())) 477 continue 478 } else if parseError := tf.parseError(); parseError != "" { 479 badGoFile(name, parseErrorFromString(tf.parseError())) 480 // Fall through: we still want to list files with parse errors. 481 } 482 } 483 484 var shouldBuild = true 485 if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { 486 shouldBuild = false 487 } else if goBuildConstraint := tf.goBuildConstraint(); goBuildConstraint != "" { 488 x, err := constraint.Parse(goBuildConstraint) 489 if err != nil { 490 return p, fmt.Errorf("%s: parsing //go:build line: %v", name, err) 491 } 492 shouldBuild = ctxt.eval(x, allTags) 493 } else if plusBuildConstraints := tf.plusBuildConstraints(); len(plusBuildConstraints) > 0 { 494 for _, text := range plusBuildConstraints { 495 if x, err := constraint.Parse(text); err == nil { 496 if !ctxt.eval(x, allTags) { 497 shouldBuild = false 498 } 499 } 500 } 501 } 502 503 ext := nameExt(name) 504 if !shouldBuild || tf.ignoreFile() { 505 if ext == ".go" { 506 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) 507 } else if fileListForExt(p, ext) != nil { 508 p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, name) 509 } 510 continue 511 } 512 513 // Going to save the file. For non-Go files, can stop here. 514 switch ext { 515 case ".go": 516 // keep going 517 case ".S", ".sx": 518 // special case for cgo, handled at end 519 Sfiles = append(Sfiles, name) 520 continue 521 default: 522 if list := fileListForExt(p, ext); list != nil { 523 *list = append(*list, name) 524 } 525 continue 526 } 527 528 pkg := tf.pkgName() 529 if pkg == "documentation" { 530 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) 531 continue 532 } 533 isTest := strings.HasSuffix(name, "_test.go") 534 isXTest := false 535 if isTest && strings.HasSuffix(tf.pkgName(), "_test") && p.Name != tf.pkgName() { 536 isXTest = true 537 pkg = pkg[:len(pkg)-len("_test")] 538 } 539 540 if !isTest && tf.binaryOnly() { 541 p.BinaryOnly = true 542 } 543 544 if p.Name == "" { 545 p.Name = pkg 546 firstFile = name 547 } else if pkg != p.Name { 548 // TODO(#45999): The choice of p.Name is arbitrary based on file iteration 549 // order. Instead of resolving p.Name arbitrarily, we should clear out the 550 // existing Name and mark the existing files as also invalid. 551 badGoFile(name, &MultiplePackageError{ 552 Dir: p.Dir, 553 Packages: []string{p.Name, pkg}, 554 Files: []string{firstFile, name}, 555 }) 556 } 557 // Grab the first package comment as docs, provided it is not from a test file. 558 if p.Doc == "" && !isTest && !isXTest { 559 if synopsis := tf.synopsis(); synopsis != "" { 560 p.Doc = synopsis 561 } 562 } 563 564 // Record Imports and information about cgo. 565 isCgo := false 566 imports := tf.imports() 567 for _, imp := range imports { 568 if imp.path == "C" { 569 if isTest { 570 badGoFile(name, fmt.Errorf("use of cgo in test %s not supported", name)) 571 continue 572 } 573 isCgo = true 574 } 575 } 576 if directives := tf.cgoDirectives(); directives != "" { 577 if err := ctxt.saveCgo(name, p, directives); err != nil { 578 badGoFile(name, err) 579 } 580 } 581 582 var fileList *[]string 583 var importMap, embedMap map[string][]token.Position 584 var directives *[]build.Directive 585 switch { 586 case isCgo: 587 allTags["cgo"] = true 588 if ctxt.CgoEnabled { 589 fileList = &p.CgoFiles 590 importMap = importPos 591 embedMap = embedPos 592 directives = &p.Directives 593 } else { 594 // Ignore Imports and Embeds from cgo files if cgo is disabled. 595 fileList = &p.IgnoredGoFiles 596 } 597 case isXTest: 598 fileList = &p.XTestGoFiles 599 importMap = xTestImportPos 600 embedMap = xTestEmbedPos 601 directives = &p.XTestDirectives 602 case isTest: 603 fileList = &p.TestGoFiles 604 importMap = testImportPos 605 embedMap = testEmbedPos 606 directives = &p.TestDirectives 607 default: 608 fileList = &p.GoFiles 609 importMap = importPos 610 embedMap = embedPos 611 directives = &p.Directives 612 } 613 *fileList = append(*fileList, name) 614 if importMap != nil { 615 for _, imp := range imports { 616 importMap[imp.path] = append(importMap[imp.path], imp.position) 617 } 618 } 619 if embedMap != nil { 620 for _, e := range tf.embeds() { 621 embedMap[e.pattern] = append(embedMap[e.pattern], e.position) 622 } 623 } 624 if directives != nil { 625 *directives = append(*directives, tf.directives()...) 626 } 627 } 628 629 p.EmbedPatterns, p.EmbedPatternPos = cleanDecls(embedPos) 630 p.TestEmbedPatterns, p.TestEmbedPatternPos = cleanDecls(testEmbedPos) 631 p.XTestEmbedPatterns, p.XTestEmbedPatternPos = cleanDecls(xTestEmbedPos) 632 633 p.Imports, p.ImportPos = cleanDecls(importPos) 634 p.TestImports, p.TestImportPos = cleanDecls(testImportPos) 635 p.XTestImports, p.XTestImportPos = cleanDecls(xTestImportPos) 636 637 for tag := range allTags { 638 p.AllTags = append(p.AllTags, tag) 639 } 640 sort.Strings(p.AllTags) 641 642 if len(p.CgoFiles) > 0 { 643 p.SFiles = append(p.SFiles, Sfiles...) 644 sort.Strings(p.SFiles) 645 } else { 646 p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, Sfiles...) 647 sort.Strings(p.IgnoredOtherFiles) 648 } 649 650 if badGoError != nil { 651 return p, badGoError 652 } 653 if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { 654 return p, &build.NoGoError{Dir: p.Dir} 655 } 656 return p, pkgerr 657 } 658 659 // IsStandardPackage reports whether path is a standard package 660 // for the goroot and compiler using the module index if possible, 661 // and otherwise falling back to github.com/go-asm/go/goroot.IsStandardPackage 662 func IsStandardPackage(goroot_, compiler, path string) bool { 663 if !enabled || compiler != "gc" { 664 return goroot.IsStandardPackage(goroot_, compiler, path) 665 } 666 667 reldir := filepath.FromSlash(path) // relative dir path in module index for package 668 modroot := filepath.Join(goroot_, "src") 669 if str.HasFilePathPrefix(reldir, "cmd") { 670 reldir = str.TrimFilePathPrefix(reldir, "cmd") 671 modroot = filepath.Join(modroot, "cmd") 672 } 673 if _, err := GetPackage(modroot, filepath.Join(modroot, reldir)); err == nil { 674 // Note that goroot.IsStandardPackage doesn't check that the directory 675 // actually contains any go files-- merely that it exists. GetPackage 676 // returning a nil error is enough for us to know the directory exists. 677 return true 678 } else if errors.Is(err, ErrNotIndexed) { 679 // Fall back because package isn't indexable. (Probably because 680 // a file was modified recently) 681 return goroot.IsStandardPackage(goroot_, compiler, path) 682 } 683 return false 684 } 685 686 // IsDirWithGoFiles is the equivalent of fsys.IsDirWithGoFiles using the information in the index. 687 func (rp *IndexPackage) IsDirWithGoFiles() (_ bool, err error) { 688 defer func() { 689 if e := recover(); e != nil { 690 err = fmt.Errorf("error reading module index: %v", e) 691 } 692 }() 693 for _, sf := range rp.sourceFiles { 694 if strings.HasSuffix(sf.name(), ".go") { 695 return true, nil 696 } 697 } 698 return false, nil 699 } 700 701 // ScanDir implements imports.ScanDir using the information in the index. 702 func (rp *IndexPackage) ScanDir(tags map[string]bool) (sortedImports []string, sortedTestImports []string, err error) { 703 // TODO(matloob) dir should eventually be relative to indexed directory 704 // TODO(matloob): skip reading raw package and jump straight to data we need? 705 706 defer func() { 707 if e := recover(); e != nil { 708 err = fmt.Errorf("error reading module index: %v", e) 709 } 710 }() 711 712 imports_ := make(map[string]bool) 713 testImports := make(map[string]bool) 714 numFiles := 0 715 716 Files: 717 for _, sf := range rp.sourceFiles { 718 name := sf.name() 719 if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") || !strings.HasSuffix(name, ".go") || !imports.MatchFile(name, tags) { 720 continue 721 } 722 723 // The following section exists for backwards compatibility reasons: 724 // scanDir ignores files with import "C" when collecting the list 725 // of imports unless the "cgo" tag is provided. The following comment 726 // is copied from the original. 727 // 728 // import "C" is implicit requirement of cgo tag. 729 // When listing files on the command line (explicitFiles=true) 730 // we do not apply build tag filtering but we still do apply 731 // cgo filtering, so no explicitFiles check here. 732 // Why? Because we always have, and it's not worth breaking 733 // that behavior now. 734 imps := sf.imports() // TODO(matloob): directly read import paths to avoid the extra strings? 735 for _, imp := range imps { 736 if imp.path == "C" && !tags["cgo"] && !tags["*"] { 737 continue Files 738 } 739 } 740 741 if !shouldBuild(sf, tags) { 742 continue 743 } 744 numFiles++ 745 m := imports_ 746 if strings.HasSuffix(name, "_test.go") { 747 m = testImports 748 } 749 for _, p := range imps { 750 m[p.path] = true 751 } 752 } 753 if numFiles == 0 { 754 return nil, nil, imports.ErrNoGo 755 } 756 return keys(imports_), keys(testImports), nil 757 } 758 759 func keys(m map[string]bool) []string { 760 list := make([]string, 0, len(m)) 761 for k := range m { 762 list = append(list, k) 763 } 764 sort.Strings(list) 765 return list 766 } 767 768 // implements imports.ShouldBuild in terms of an index sourcefile. 769 func shouldBuild(sf *sourceFile, tags map[string]bool) bool { 770 if goBuildConstraint := sf.goBuildConstraint(); goBuildConstraint != "" { 771 x, err := constraint.Parse(goBuildConstraint) 772 if err != nil { 773 return false 774 } 775 return imports.Eval(x, tags, true) 776 } 777 778 plusBuildConstraints := sf.plusBuildConstraints() 779 for _, text := range plusBuildConstraints { 780 if x, err := constraint.Parse(text); err == nil { 781 if !imports.Eval(x, tags, true) { 782 return false 783 } 784 } 785 } 786 787 return true 788 } 789 790 // IndexPackage holds the information needed to access information in the 791 // index needed to load a package in a specific directory. 792 type IndexPackage struct { 793 error error 794 dir string // directory of the package relative to the modroot 795 796 modroot string 797 798 // Source files 799 sourceFiles []*sourceFile 800 } 801 802 var errCannotFindPackage = errors.New("cannot find package") 803 804 // Package and returns finds the package with the given path (relative to the module root). 805 // If the package does not exist, Package returns an IndexPackage that will return an 806 // appropriate error from its methods. 807 func (m *Module) Package(path string) *IndexPackage { 808 defer unprotect(protect(), nil) 809 810 i, ok := sort.Find(m.n, func(i int) int { 811 return strings.Compare(path, m.pkgDir(i)) 812 }) 813 if !ok { 814 return &IndexPackage{error: fmt.Errorf("%w %q in:\n\t%s", errCannotFindPackage, path, filepath.Join(m.modroot, path))} 815 } 816 return m.pkg(i) 817 } 818 819 // pkg returns the i'th IndexPackage in m. 820 func (m *Module) pkg(i int) *IndexPackage { 821 r := m.d.readAt(m.pkgOff(i)) 822 p := new(IndexPackage) 823 if errstr := r.string(); errstr != "" { 824 p.error = errors.New(errstr) 825 } 826 p.dir = r.string() 827 p.sourceFiles = make([]*sourceFile, r.int()) 828 for i := range p.sourceFiles { 829 p.sourceFiles[i] = &sourceFile{ 830 d: m.d, 831 pos: r.int(), 832 } 833 } 834 p.modroot = m.modroot 835 return p 836 } 837 838 // sourceFile represents the information of a given source file in the module index. 839 type sourceFile struct { 840 d *decoder // encoding of this source file 841 pos int // start of sourceFile encoding in d 842 onceReadImports sync.Once 843 savedImports []rawImport // saved imports so that they're only read once 844 } 845 846 // Offsets for fields in the sourceFile. 847 const ( 848 sourceFileError = 4 * iota 849 sourceFileParseError 850 sourceFileSynopsis 851 sourceFileName 852 sourceFilePkgName 853 sourceFileIgnoreFile 854 sourceFileBinaryOnly 855 sourceFileCgoDirectives 856 sourceFileGoBuildConstraint 857 sourceFileNumPlusBuildConstraints 858 ) 859 860 func (sf *sourceFile) error() string { 861 return sf.d.stringAt(sf.pos + sourceFileError) 862 } 863 func (sf *sourceFile) parseError() string { 864 return sf.d.stringAt(sf.pos + sourceFileParseError) 865 } 866 func (sf *sourceFile) synopsis() string { 867 return sf.d.stringAt(sf.pos + sourceFileSynopsis) 868 } 869 func (sf *sourceFile) name() string { 870 return sf.d.stringAt(sf.pos + sourceFileName) 871 } 872 func (sf *sourceFile) pkgName() string { 873 return sf.d.stringAt(sf.pos + sourceFilePkgName) 874 } 875 func (sf *sourceFile) ignoreFile() bool { 876 return sf.d.boolAt(sf.pos + sourceFileIgnoreFile) 877 } 878 func (sf *sourceFile) binaryOnly() bool { 879 return sf.d.boolAt(sf.pos + sourceFileBinaryOnly) 880 } 881 func (sf *sourceFile) cgoDirectives() string { 882 return sf.d.stringAt(sf.pos + sourceFileCgoDirectives) 883 } 884 func (sf *sourceFile) goBuildConstraint() string { 885 return sf.d.stringAt(sf.pos + sourceFileGoBuildConstraint) 886 } 887 888 func (sf *sourceFile) plusBuildConstraints() []string { 889 pos := sf.pos + sourceFileNumPlusBuildConstraints 890 n := sf.d.intAt(pos) 891 pos += 4 892 ret := make([]string, n) 893 for i := 0; i < n; i++ { 894 ret[i] = sf.d.stringAt(pos) 895 pos += 4 896 } 897 return ret 898 } 899 900 func (sf *sourceFile) importsOffset() int { 901 pos := sf.pos + sourceFileNumPlusBuildConstraints 902 n := sf.d.intAt(pos) 903 // each build constraint is 1 uint32 904 return pos + 4 + n*4 905 } 906 907 func (sf *sourceFile) embedsOffset() int { 908 pos := sf.importsOffset() 909 n := sf.d.intAt(pos) 910 // each import is 5 uint32s (string + tokpos) 911 return pos + 4 + n*(4*5) 912 } 913 914 func (sf *sourceFile) directivesOffset() int { 915 pos := sf.embedsOffset() 916 n := sf.d.intAt(pos) 917 // each embed is 5 uint32s (string + tokpos) 918 return pos + 4 + n*(4*5) 919 } 920 921 func (sf *sourceFile) imports() []rawImport { 922 sf.onceReadImports.Do(func() { 923 importsOffset := sf.importsOffset() 924 r := sf.d.readAt(importsOffset) 925 numImports := r.int() 926 ret := make([]rawImport, numImports) 927 for i := 0; i < numImports; i++ { 928 ret[i] = rawImport{r.string(), r.tokpos()} 929 } 930 sf.savedImports = ret 931 }) 932 return sf.savedImports 933 } 934 935 func (sf *sourceFile) embeds() []embed { 936 embedsOffset := sf.embedsOffset() 937 r := sf.d.readAt(embedsOffset) 938 numEmbeds := r.int() 939 ret := make([]embed, numEmbeds) 940 for i := range ret { 941 ret[i] = embed{r.string(), r.tokpos()} 942 } 943 return ret 944 } 945 946 func (sf *sourceFile) directives() []build.Directive { 947 directivesOffset := sf.directivesOffset() 948 r := sf.d.readAt(directivesOffset) 949 numDirectives := r.int() 950 ret := make([]build.Directive, numDirectives) 951 for i := range ret { 952 ret[i] = build.Directive{Text: r.string(), Pos: r.tokpos()} 953 } 954 return ret 955 } 956 957 func asString(b []byte) string { 958 return unsafe.String(unsafe.SliceData(b), len(b)) 959 } 960 961 // A decoder helps decode the index format. 962 type decoder struct { 963 data []byte // data after header 964 str []byte // string table 965 } 966 967 // intAt returns the int at the given offset in d.data. 968 func (d *decoder) intAt(off int) int { 969 if off < 0 || len(d.data)-off < 4 { 970 panic(errCorrupt) 971 } 972 i := binary.LittleEndian.Uint32(d.data[off : off+4]) 973 if int32(i)>>31 != 0 { 974 panic(errCorrupt) 975 } 976 return int(i) 977 } 978 979 // boolAt returns the bool at the given offset in d.data. 980 func (d *decoder) boolAt(off int) bool { 981 return d.intAt(off) != 0 982 } 983 984 // stringAt returns the string pointed at by the int at the given offset in d.data. 985 func (d *decoder) stringAt(off int) string { 986 return d.stringTableAt(d.intAt(off)) 987 } 988 989 // stringTableAt returns the string at the given offset in the string table d.str. 990 func (d *decoder) stringTableAt(off int) string { 991 if off < 0 || off >= len(d.str) { 992 panic(errCorrupt) 993 } 994 s := d.str[off:] 995 v, n := binary.Uvarint(s) 996 if n <= 0 || v > uint64(len(s[n:])) { 997 panic(errCorrupt) 998 } 999 return asString(s[n : n+int(v)]) 1000 } 1001 1002 // A reader reads sequential fields from a section of the index format. 1003 type reader struct { 1004 d *decoder 1005 pos int 1006 } 1007 1008 // readAt returns a reader starting at the given position in d. 1009 func (d *decoder) readAt(pos int) *reader { 1010 return &reader{d, pos} 1011 } 1012 1013 // int reads the next int. 1014 func (r *reader) int() int { 1015 i := r.d.intAt(r.pos) 1016 r.pos += 4 1017 return i 1018 } 1019 1020 // string reads the next string. 1021 func (r *reader) string() string { 1022 return r.d.stringTableAt(r.int()) 1023 } 1024 1025 // bool reads the next bool. 1026 func (r *reader) bool() bool { 1027 return r.int() != 0 1028 } 1029 1030 // tokpos reads the next token.Position. 1031 func (r *reader) tokpos() token.Position { 1032 return token.Position{ 1033 Filename: r.string(), 1034 Offset: r.int(), 1035 Line: r.int(), 1036 Column: r.int(), 1037 } 1038 }