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