refs.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. Copyright 2019 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. "strconv"
  18. "sync"
  19. )
  20. // NB(directxman12): most of this is done by the typechecker,
  21. // but it's a bit slow/heavyweight for what we want -- we want
  22. // to resolve external imports *only* if we actually need them.
  23. // Basically, what we do is:
  24. // 1. Map imports to names
  25. // 2. Find all explicit external references (`name.type`)
  26. // 3. Find all referenced packages by merging explicit references and dot imports
  27. // 4. Only type-check those packages
  28. // 5. Ignore type-checking errors from the missing packages, because we won't ever
  29. // touch unloaded types (they're probably used in ignored fields/types, variables, or functions)
  30. // (done using PrintErrors with an ignore argument from the caller).
  31. // 6. Notice any actual type-checking errors via invalid types
  32. // importsMap saves import aliases, mapping them to underlying packages.
  33. type importsMap struct {
  34. // dotImports maps package IDs to packages for any packages that have/ been imported as `.`
  35. dotImports map[string]*Package
  36. // byName maps package aliases or names to the underlying package.
  37. byName map[string]*Package
  38. }
  39. // mapImports maps imports from the names they use in the given file to the underlying package,
  40. // using a map of package import paths to packages (generally from Package.Imports()).
  41. func mapImports(file *ast.File, importedPkgs map[string]*Package) (*importsMap, error) {
  42. m := &importsMap{
  43. dotImports: make(map[string]*Package),
  44. byName: make(map[string]*Package),
  45. }
  46. for _, importSpec := range file.Imports {
  47. path, err := strconv.Unquote(importSpec.Path.Value)
  48. if err != nil {
  49. return nil, ErrFromNode(err, importSpec.Path)
  50. }
  51. importedPkg := importedPkgs[path]
  52. if importedPkg == nil {
  53. return nil, ErrFromNode(fmt.Errorf("no such package located"), importSpec.Path)
  54. }
  55. if importSpec.Name == nil {
  56. m.byName[importedPkg.Name] = importedPkg
  57. continue
  58. }
  59. if importSpec.Name.Name == "." {
  60. m.dotImports[importedPkg.ID] = importedPkg
  61. continue
  62. }
  63. m.byName[importSpec.Name.Name] = importedPkg
  64. }
  65. return m, nil
  66. }
  67. // referenceSet finds references to external packages' types in the given file,
  68. // without otherwise calling into the type-checker. When checking structs,
  69. // it only checks fields with JSON tags.
  70. type referenceSet struct {
  71. file *ast.File
  72. imports *importsMap
  73. pkg *Package
  74. externalRefs map[*Package]struct{}
  75. }
  76. func (r *referenceSet) init() {
  77. if r.externalRefs == nil {
  78. r.externalRefs = make(map[*Package]struct{})
  79. }
  80. }
  81. // NodeFilter filters nodes, accepting them for reference collection
  82. // when true is returned and rejecting them when false is returned.
  83. type NodeFilter func(ast.Node) bool
  84. // collectReferences saves all references to external types in the given info.
  85. func (r *referenceSet) collectReferences(rawType ast.Expr, filterNode NodeFilter) {
  86. r.init()
  87. col := &referenceCollector{
  88. refs: r,
  89. filterNode: filterNode,
  90. }
  91. ast.Walk(col, rawType)
  92. }
  93. // external saves an external reference to the given named package.
  94. func (r *referenceSet) external(pkgName string) {
  95. pkg := r.imports.byName[pkgName]
  96. if pkg == nil {
  97. r.pkg.AddError(fmt.Errorf("use of unimported package %q", pkgName))
  98. return
  99. }
  100. r.externalRefs[pkg] = struct{}{}
  101. }
  102. // referenceCollector visits nodes in an AST, adding external references to a
  103. // referenceSet.
  104. type referenceCollector struct {
  105. refs *referenceSet
  106. filterNode NodeFilter
  107. }
  108. func (c *referenceCollector) Visit(node ast.Node) ast.Visitor {
  109. if !c.filterNode(node) {
  110. return nil
  111. }
  112. switch typedNode := node.(type) {
  113. case *ast.Ident:
  114. // local reference or dot-import, ignore
  115. return nil
  116. case *ast.SelectorExpr:
  117. switch x := typedNode.X.(type) {
  118. case *ast.Ident:
  119. pkgName := x.Name
  120. c.refs.external(pkgName)
  121. return nil
  122. default:
  123. return c
  124. }
  125. default:
  126. return c
  127. }
  128. }
  129. // allReferencedPackages finds all directly referenced packages in the given package.
  130. func allReferencedPackages(pkg *Package, filterNodes NodeFilter) []*Package {
  131. pkg.NeedSyntax()
  132. refsByFile := make(map[*ast.File]*referenceSet)
  133. for _, file := range pkg.Syntax {
  134. imports, err := mapImports(file, pkg.Imports())
  135. if err != nil {
  136. pkg.AddError(err)
  137. return nil
  138. }
  139. refs := &referenceSet{
  140. file: file,
  141. imports: imports,
  142. pkg: pkg,
  143. }
  144. refsByFile[file] = refs
  145. }
  146. EachType(pkg, func(file *ast.File, decl *ast.GenDecl, spec *ast.TypeSpec) {
  147. refs := refsByFile[file]
  148. refs.collectReferences(spec.Type, filterNodes)
  149. })
  150. allPackages := make(map[*Package]struct{})
  151. for _, refs := range refsByFile {
  152. for _, pkg := range refs.imports.dotImports {
  153. allPackages[pkg] = struct{}{}
  154. }
  155. for ref := range refs.externalRefs {
  156. allPackages[ref] = struct{}{}
  157. }
  158. }
  159. res := make([]*Package, 0, len(allPackages))
  160. for pkg := range allPackages {
  161. res = append(res, pkg)
  162. }
  163. return res
  164. }
  165. // TypeChecker performs type-checking on a limitted subset of packages by
  166. // checking each package's types' externally-referenced types, and only
  167. // type-checking those packages.
  168. type TypeChecker struct {
  169. // NodeFilters are used to filter the set of references that are followed
  170. // when typechecking. If any of the filters returns true for a given node,
  171. // its package will be added to the set of packages to check.
  172. //
  173. // If no filters are specified, all references are followed (this may be slow).
  174. //
  175. // Modifying this after the first call to check may yield strange/invalid
  176. // results.
  177. NodeFilters []NodeFilter
  178. checkedPackages map[*Package]struct{}
  179. sync.Mutex
  180. }
  181. // Check type-checks the given package and all packages referenced by types
  182. // that pass through (have true returned by) any of the NodeFilters.
  183. func (c *TypeChecker) Check(root *Package) {
  184. c.init()
  185. // use a sub-checker with the appropriate settings
  186. (&TypeChecker{
  187. NodeFilters: c.NodeFilters,
  188. checkedPackages: c.checkedPackages,
  189. }).check(root)
  190. }
  191. func (c *TypeChecker) isNodeInteresting(node ast.Node) bool {
  192. // no filters --> everything is important
  193. if len(c.NodeFilters) == 0 {
  194. return true
  195. }
  196. // otherwise, passing through any one filter means this node is important
  197. for _, filter := range c.NodeFilters {
  198. if filter(node) {
  199. return true
  200. }
  201. }
  202. return false
  203. }
  204. func (c *TypeChecker) init() {
  205. if c.checkedPackages == nil {
  206. c.checkedPackages = make(map[*Package]struct{})
  207. }
  208. }
  209. // check recursively type-checks the given package, only loading packages that
  210. // are actually referenced by our types (it's the actual implementation of Check,
  211. // without initialization).
  212. func (c *TypeChecker) check(root *Package) {
  213. root.Lock()
  214. defer root.Unlock()
  215. c.Lock()
  216. _, ok := c.checkedPackages[root]
  217. c.Unlock()
  218. if ok {
  219. return
  220. }
  221. refedPackages := allReferencedPackages(root, c.isNodeInteresting)
  222. // first, resolve imports for all leaf packages...
  223. var wg sync.WaitGroup
  224. for _, pkg := range refedPackages {
  225. wg.Add(1)
  226. go func(pkg *Package) {
  227. defer wg.Done()
  228. c.check(pkg)
  229. }(pkg)
  230. }
  231. wg.Wait()
  232. // ...then, we can safely type-check ourself
  233. root.NeedTypesInfo()
  234. c.Lock()
  235. defer c.Unlock()
  236. c.checkedPackages[root] = struct{}{}
  237. }