loader.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. Copyright 2019-2022 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package loader
  14. import (
  15. "fmt"
  16. "go/ast"
  17. "go/parser"
  18. "go/scanner"
  19. "go/token"
  20. "go/types"
  21. "os"
  22. "path/filepath"
  23. "regexp"
  24. "sync"
  25. "golang.org/x/tools/go/packages"
  26. "k8s.io/apimachinery/pkg/util/sets"
  27. )
  28. // Much of this is strongly inspired by the contents of go/packages,
  29. // except that it allows for lazy loading of syntax and type-checking
  30. // information to speed up cases where full traversal isn't needed.
  31. // PrintErrors print errors associated with all packages
  32. // in the given package graph, starting at the given root
  33. // packages and traversing through all imports. It will skip
  34. // any errors of the kinds specified in filterKinds. It will
  35. // return true if any errors were printed.
  36. func PrintErrors(pkgs []*Package, filterKinds ...packages.ErrorKind) bool {
  37. pkgsRaw := make([]*packages.Package, len(pkgs))
  38. for i, pkg := range pkgs {
  39. pkgsRaw[i] = pkg.Package
  40. }
  41. toSkip := make(map[packages.ErrorKind]struct{})
  42. for _, errKind := range filterKinds {
  43. toSkip[errKind] = struct{}{}
  44. }
  45. hadErrors := false
  46. packages.Visit(pkgsRaw, nil, func(pkgRaw *packages.Package) {
  47. for _, err := range pkgRaw.Errors {
  48. if _, skip := toSkip[err.Kind]; skip {
  49. continue
  50. }
  51. hadErrors = true
  52. fmt.Fprintln(os.Stderr, err)
  53. }
  54. })
  55. return hadErrors
  56. }
  57. // Package is a single, unique Go package that can be
  58. // lazily parsed and type-checked. Packages should not
  59. // be constructed directly -- instead, use LoadRoots.
  60. // For a given call to LoadRoots, only a single instance
  61. // of each package exists, and thus they may be used as keys
  62. // and for comparison.
  63. type Package struct {
  64. *packages.Package
  65. imports map[string]*Package
  66. loader *loader
  67. sync.Mutex
  68. }
  69. // Imports returns the imports for the given package, indexed by
  70. // package path (*not* name in any particular file).
  71. func (p *Package) Imports() map[string]*Package {
  72. if p.imports == nil {
  73. p.imports = p.loader.packagesFor(p.Package.Imports)
  74. }
  75. return p.imports
  76. }
  77. // NeedTypesInfo indicates that type-checking information is needed for this package.
  78. // Actual type-checking information can be accessed via the Types and TypesInfo fields.
  79. func (p *Package) NeedTypesInfo() {
  80. if p.TypesInfo != nil {
  81. return
  82. }
  83. p.NeedSyntax()
  84. p.loader.typeCheck(p)
  85. }
  86. // NeedSyntax indicates that a parsed AST is needed for this package.
  87. // Actual ASTs can be accessed via the Syntax field.
  88. func (p *Package) NeedSyntax() {
  89. if p.Syntax != nil {
  90. return
  91. }
  92. out := make([]*ast.File, len(p.CompiledGoFiles))
  93. var wg sync.WaitGroup
  94. wg.Add(len(p.CompiledGoFiles))
  95. for i, filename := range p.CompiledGoFiles {
  96. go func(i int, filename string) {
  97. defer wg.Done()
  98. src, err := os.ReadFile(filename)
  99. if err != nil {
  100. p.AddError(err)
  101. return
  102. }
  103. out[i], err = p.loader.parseFile(filename, src)
  104. if err != nil {
  105. p.AddError(err)
  106. return
  107. }
  108. }(i, filename)
  109. }
  110. wg.Wait()
  111. for _, file := range out {
  112. if file == nil {
  113. return
  114. }
  115. }
  116. p.Syntax = out
  117. }
  118. // AddError adds an error to the errors associated with the given package.
  119. func (p *Package) AddError(err error) {
  120. switch typedErr := err.(type) {
  121. case *os.PathError:
  122. // file-reading errors
  123. p.Errors = append(p.Errors, packages.Error{
  124. Pos: typedErr.Path + ":1",
  125. Msg: typedErr.Err.Error(),
  126. Kind: packages.ParseError,
  127. })
  128. case scanner.ErrorList:
  129. // parsing/scanning errors
  130. for _, subErr := range typedErr {
  131. p.Errors = append(p.Errors, packages.Error{
  132. Pos: subErr.Pos.String(),
  133. Msg: subErr.Msg,
  134. Kind: packages.ParseError,
  135. })
  136. }
  137. case types.Error:
  138. // type-checking errors
  139. p.Errors = append(p.Errors, packages.Error{
  140. Pos: typedErr.Fset.Position(typedErr.Pos).String(),
  141. Msg: typedErr.Msg,
  142. Kind: packages.TypeError,
  143. })
  144. case ErrList:
  145. for _, subErr := range typedErr {
  146. p.AddError(subErr)
  147. }
  148. case PositionedError:
  149. p.Errors = append(p.Errors, packages.Error{
  150. Pos: p.loader.cfg.Fset.Position(typedErr.Pos).String(),
  151. Msg: typedErr.Error(),
  152. Kind: packages.UnknownError,
  153. })
  154. default:
  155. // should only happen for external errors, like ref checking
  156. p.Errors = append(p.Errors, packages.Error{
  157. Pos: p.ID + ":-",
  158. Msg: err.Error(),
  159. Kind: packages.UnknownError,
  160. })
  161. }
  162. }
  163. // loader loads packages and their imports. Loaded packages will have
  164. // type size, imports, and exports file information populated. Additional
  165. // information, like ASTs and type-checking information, can be accessed
  166. // via methods on individual packages.
  167. type loader struct {
  168. // Roots are the loaded "root" packages in the package graph loaded via
  169. // LoadRoots.
  170. Roots []*Package
  171. // cfg contains the package loading config (initialized on demand)
  172. cfg *packages.Config
  173. // packages contains the cache of Packages indexed by the underlying
  174. // package.Package, so that we don't ever produce two Packages with
  175. // the same underlying packages.Package.
  176. packages map[*packages.Package]*Package
  177. packagesMu sync.Mutex
  178. }
  179. // packageFor returns a wrapped Package for the given packages.Package,
  180. // ensuring that there's a one-to-one mapping between the two.
  181. // It's *not* threadsafe -- use packagesFor for that.
  182. func (l *loader) packageFor(pkgRaw *packages.Package) *Package {
  183. if l.packages[pkgRaw] == nil {
  184. l.packages[pkgRaw] = &Package{
  185. Package: pkgRaw,
  186. loader: l,
  187. }
  188. }
  189. return l.packages[pkgRaw]
  190. }
  191. // packagesFor returns a map of Package objects for each packages.Package in the input
  192. // map, ensuring that there's a one-to-one mapping between package.Package and Package
  193. // (as per packageFor).
  194. func (l *loader) packagesFor(pkgsRaw map[string]*packages.Package) map[string]*Package {
  195. l.packagesMu.Lock()
  196. defer l.packagesMu.Unlock()
  197. out := make(map[string]*Package, len(pkgsRaw))
  198. for name, rawPkg := range pkgsRaw {
  199. out[name] = l.packageFor(rawPkg)
  200. }
  201. return out
  202. }
  203. // typeCheck type-checks the given package.
  204. func (l *loader) typeCheck(pkg *Package) {
  205. // don't conflict with typeCheckFromExportData
  206. pkg.TypesInfo = &types.Info{
  207. Types: make(map[ast.Expr]types.TypeAndValue),
  208. Defs: make(map[*ast.Ident]types.Object),
  209. Uses: make(map[*ast.Ident]types.Object),
  210. Implicits: make(map[ast.Node]types.Object),
  211. Scopes: make(map[ast.Node]*types.Scope),
  212. Selections: make(map[*ast.SelectorExpr]*types.Selection),
  213. }
  214. pkg.Fset = l.cfg.Fset
  215. pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
  216. importer := importerFunc(func(path string) (*types.Package, error) {
  217. if path == "unsafe" {
  218. return types.Unsafe, nil
  219. }
  220. // The imports map is keyed by import path.
  221. importedPkg := pkg.Imports()[path]
  222. if importedPkg == nil {
  223. return nil, fmt.Errorf("package %q possibly creates an import loop", path)
  224. }
  225. // it's possible to have a call to check in parallel to a call to this
  226. // if one package in the package graph gets its dependency filtered out,
  227. // but another doesn't (so one wants a "placeholder" package here, and another
  228. // wants the full check).
  229. //
  230. // Thus, we need to lock here (at least for the time being) to avoid
  231. // races between the above write to `pkg.Types` and this checking of
  232. // importedPkg.Types.
  233. importedPkg.Lock()
  234. defer importedPkg.Unlock()
  235. if importedPkg.Types != nil && importedPkg.Types.Complete() {
  236. return importedPkg.Types, nil
  237. }
  238. // if we haven't already loaded typecheck data, we don't care about this package's types
  239. return types.NewPackage(importedPkg.PkgPath, importedPkg.Name), nil
  240. })
  241. var errs []error
  242. // type-check
  243. checkConfig := &types.Config{
  244. Importer: importer,
  245. IgnoreFuncBodies: true, // we only need decl-level info
  246. Error: func(err error) {
  247. errs = append(errs, err)
  248. },
  249. Sizes: pkg.TypesSizes,
  250. }
  251. if err := types.NewChecker(checkConfig, l.cfg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax); err != nil {
  252. errs = append(errs, err)
  253. }
  254. // make sure that if a given sub-import is ill-typed, we mark this package as ill-typed as well.
  255. illTyped := len(errs) > 0
  256. if !illTyped {
  257. for _, importedPkg := range pkg.Imports() {
  258. if importedPkg.IllTyped {
  259. illTyped = true
  260. break
  261. }
  262. }
  263. }
  264. pkg.IllTyped = illTyped
  265. // publish errors to the package error list.
  266. for _, err := range errs {
  267. pkg.AddError(err)
  268. }
  269. }
  270. // parseFile parses the given file, including comments.
  271. func (l *loader) parseFile(filename string, src []byte) (*ast.File, error) {
  272. // skip function bodies
  273. file, err := parser.ParseFile(l.cfg.Fset, filename, src, parser.AllErrors|parser.ParseComments)
  274. if err != nil {
  275. return nil, err
  276. }
  277. return file, nil
  278. }
  279. // LoadRoots loads the given "root" packages by path, transitively loading
  280. // and all imports as well.
  281. //
  282. // Loaded packages will have type size, imports, and exports file information
  283. // populated. Additional information, like ASTs and type-checking information,
  284. // can be accessed via methods on individual packages.
  285. func LoadRoots(roots ...string) ([]*Package, error) {
  286. return LoadRootsWithConfig(&packages.Config{}, roots...)
  287. }
  288. // LoadRootsWithConfig functions like LoadRoots, except that it allows passing
  289. // a custom loading config. The config will be modified to suit the needs of
  290. // the loader.
  291. //
  292. // This is generally only useful for use in testing when you need to modify
  293. // loading settings to load from a fake location.
  294. //
  295. // This function will traverse Go module boundaries for roots that are file-
  296. // system paths and end with "...". Please note this feature currently only
  297. // supports roots that are filesystem paths. For more information, please
  298. // refer to the high-level outline of this function's logic:
  299. //
  300. // 1. If no roots are provided then load the working directory and return
  301. // early.
  302. //
  303. // 2. Otherwise sort the provided roots into two, distinct buckets:
  304. //
  305. // a. package/module names
  306. // b. filesystem paths
  307. //
  308. // A filesystem path is distinguished from a Go package/module name by
  309. // the same rules as followed by the "go" command. At a high level, a
  310. // root is a filesystem path IFF it meets ANY of the following criteria:
  311. //
  312. // * is absolute
  313. // * begins with .
  314. // * begins with ..
  315. //
  316. // For more information please refer to the output of the command
  317. // "go help packages".
  318. //
  319. // 3. Load the package/module roots as a single call to packages.Load. If
  320. // there are no filesystem path roots then return early.
  321. //
  322. // 4. For filesystem path roots ending with "...", check to see if its
  323. // descendants include any nested, Go modules. If so, add the directory
  324. // that contains the nested Go module to the filesystem path roots.
  325. //
  326. // 5. Load the filesystem path roots and return the load packages for the
  327. // package/module roots AND the filesystem path roots.
  328. func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, error) {
  329. l := &loader{
  330. cfg: cfg,
  331. packages: make(map[*packages.Package]*Package),
  332. }
  333. l.cfg.Mode |= packages.LoadImports | packages.NeedTypesSizes
  334. if l.cfg.Fset == nil {
  335. l.cfg.Fset = token.NewFileSet()
  336. }
  337. // put our build flags first so that callers can override them
  338. l.cfg.BuildFlags = append([]string{"-tags", "ignore_autogenerated"}, l.cfg.BuildFlags...)
  339. // Visit the import graphs of the loaded, root packages. If an imported
  340. // package refers to another loaded, root package, then replace the
  341. // instance of the imported package with a reference to the loaded, root
  342. // package. This is required to make kubebuilder markers work correctly
  343. // when multiple root paths are loaded and types from one path reference
  344. // types from another root path.
  345. defer func() {
  346. for i := range l.Roots {
  347. visitImports(l.Roots, l.Roots[i], nil)
  348. }
  349. }()
  350. // uniquePkgIDs is used to keep track of the discovered packages to be nice
  351. // and try and prevent packages from showing up twice when nested module
  352. // support is enabled. there is not harm that comes from this per se, but
  353. // it makes testing easier when a known number of modules can be asserted
  354. uniquePkgIDs := sets.String{}
  355. // loadPackages returns the Go packages for the provided roots
  356. //
  357. // if validatePkgFn is nil, a package will be returned in the slice,
  358. // otherwise the package is only returned if the result of
  359. // validatePkgFn(pkg.ID) is truthy
  360. loadPackages := func(roots ...string) ([]*Package, error) {
  361. rawPkgs, err := packages.Load(l.cfg, roots...)
  362. if err != nil {
  363. loadRoot := l.cfg.Dir
  364. if l.cfg.Dir == "" {
  365. loadRoot, _ = os.Getwd()
  366. }
  367. return nil, fmt.Errorf("load packages in root %q: %w", loadRoot, err)
  368. }
  369. var pkgs []*Package
  370. for _, rp := range rawPkgs {
  371. p := l.packageFor(rp)
  372. if !uniquePkgIDs.Has(p.ID) {
  373. pkgs = append(pkgs, p)
  374. uniquePkgIDs.Insert(p.ID)
  375. }
  376. }
  377. return pkgs, nil
  378. }
  379. // if no roots were provided then load the current package and return early
  380. if len(roots) == 0 {
  381. pkgs, err := loadPackages()
  382. if err != nil {
  383. return nil, err
  384. }
  385. l.Roots = append(l.Roots, pkgs...)
  386. return l.Roots, nil
  387. }
  388. // pkgRoots is a slice of roots that are package/modules and fspRoots
  389. // is a slice of roots that are local filesystem paths.
  390. //
  391. // please refer to this function's godoc comments for more information on
  392. // how these two types of roots are distinguished from one another
  393. var (
  394. pkgRoots []string
  395. fspRoots []string
  396. fspRootRx = regexp.MustCompile(`^\.{1,2}`)
  397. )
  398. for _, r := range roots {
  399. if filepath.IsAbs(r) || fspRootRx.MatchString(r) {
  400. fspRoots = append(fspRoots, r)
  401. } else {
  402. pkgRoots = append(pkgRoots, r)
  403. }
  404. }
  405. // handle the package roots by sending them into the packages.Load function
  406. // all at once. this is more efficient, but cannot be used for the file-
  407. // system path roots due to them needing a custom, calculated value for the
  408. // cfg.Dir field
  409. if len(pkgRoots) > 0 {
  410. pkgs, err := loadPackages(pkgRoots...)
  411. if err != nil {
  412. return nil, err
  413. }
  414. l.Roots = append(l.Roots, pkgs...)
  415. }
  416. // if there are no filesystem path roots then go ahead and return early
  417. if len(fspRoots) == 0 {
  418. return l.Roots, nil
  419. }
  420. //
  421. // at this point we are handling filesystem path roots
  422. //
  423. // ensure the cfg.Dir field is reset to its original value upon
  424. // returning from this function. it should honestly be fine if it is
  425. // not given most callers will not send in the cfg parameter directly,
  426. // as it's largely for testing, but still, let's be good stewards.
  427. defer func(d string) {
  428. cfg.Dir = d
  429. }(cfg.Dir)
  430. // store the value of cfg.Dir so we can use it later if it is non-empty.
  431. // we need to store it now as the value of cfg.Dir will be updated by
  432. // a loop below
  433. cfgDir := cfg.Dir
  434. // addNestedGoModulesToRoots is given to filepath.WalkDir and adds the
  435. // directory part of p to the list of filesystem path roots IFF p is the
  436. // path to a file named "go.mod"
  437. addNestedGoModulesToRoots := func(
  438. p string,
  439. d os.DirEntry,
  440. e error) error {
  441. if e != nil {
  442. return e
  443. }
  444. if !d.IsDir() && filepath.Base(p) == "go.mod" {
  445. fspRoots = append(fspRoots, filepath.Join(filepath.Dir(p), "..."))
  446. }
  447. return nil
  448. }
  449. // in the first pass over the filesystem path roots we:
  450. //
  451. // 1. make the root into an absolute path
  452. //
  453. // 2. check to see if a root uses the nested path syntax, ex. ...
  454. //
  455. // 3. if so, walk the root's descendants, searching for any nested Go
  456. // modules
  457. //
  458. // 4. if found then the directory containing the Go module is added to
  459. // the list of the filesystem path roots
  460. for i := range fspRoots {
  461. r := fspRoots[i]
  462. // clean up the root
  463. r = filepath.Clean(r)
  464. // get the absolute path of the root
  465. if !filepath.IsAbs(r) {
  466. // if the initial value of cfg.Dir was non-empty then use it when
  467. // building the absolute path to this root. otherwise use the
  468. // filepath.Abs function to get the absolute path of the root based
  469. // on the working directory
  470. if cfgDir != "" {
  471. r = filepath.Join(cfgDir, r)
  472. } else {
  473. ar, err := filepath.Abs(r)
  474. if err != nil {
  475. return nil, err
  476. }
  477. r = ar
  478. }
  479. }
  480. // update the root to be an absolute path
  481. fspRoots[i] = r
  482. b, d := filepath.Base(r), filepath.Dir(r)
  483. // if the base element is "..." then it means nested traversal is
  484. // activated. this can be passed directly to the loader. however, if
  485. // specified we also want to traverse the path manually to determine if
  486. // there are any nested Go modules we want to add to the list of file-
  487. // system path roots to process
  488. if b == "..." {
  489. if err := filepath.WalkDir(
  490. d,
  491. addNestedGoModulesToRoots); err != nil {
  492. return nil, err
  493. }
  494. }
  495. }
  496. // in the second pass over the filesystem path roots we:
  497. //
  498. // 1. determine the directory from which to execute the loader
  499. //
  500. // 2. update the loader config's Dir property to be the directory from
  501. // step one
  502. //
  503. // 3. determine whether the root passed to the loader should be "./."
  504. // or "./..."
  505. //
  506. // 4. execute the loader with the value from step three
  507. for _, r := range fspRoots {
  508. b, d := filepath.Base(r), filepath.Dir(r)
  509. // we want the base part of the path to be either "..." or ".", except
  510. // Go's filepath utilities clean paths during manipulation, removing the
  511. // ".". thus, if not "...", let's update the path components so that:
  512. //
  513. // d = r
  514. // b = "."
  515. if b != "..." {
  516. d = r
  517. b = "."
  518. }
  519. // update the loader configuration's Dir field to the directory part of
  520. // the root
  521. l.cfg.Dir = d
  522. // update the root to be "./..." or "./."
  523. // (with OS-specific filepath separator). please note filepath.Join
  524. // would clean up the trailing "." character that we want preserved,
  525. // hence the more manual path concatenation logic
  526. r = fmt.Sprintf(".%s%s", string(filepath.Separator), b)
  527. // load the packages from the roots
  528. pkgs, err := loadPackages(r)
  529. if err != nil {
  530. return nil, err
  531. }
  532. l.Roots = append(l.Roots, pkgs...)
  533. }
  534. return l.Roots, nil
  535. }
  536. // visitImports walks a dependency graph, replacing imported package
  537. // references with those from the rootPkgs list. This ensures the
  538. // kubebuilder marker generation is handled correctly. For more info,
  539. // please see issue 680.
  540. func visitImports(rootPkgs []*Package, pkg *Package, seen sets.String) {
  541. if seen == nil {
  542. seen = sets.String{}
  543. }
  544. for importedPkgID, importedPkg := range pkg.Imports() {
  545. for i := range rootPkgs {
  546. if importedPkgID == rootPkgs[i].ID {
  547. pkg.imports[importedPkgID] = rootPkgs[i]
  548. }
  549. }
  550. if !seen.Has(importedPkgID) {
  551. seen.Insert(importedPkgID)
  552. visitImports(rootPkgs, importedPkg, seen)
  553. }
  554. }
  555. }
  556. // importFunc is an implementation of the single-method
  557. // types.Importer interface based on a function value.
  558. type importerFunc func(path string) (*types.Package, error)
  559. func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }