github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/internal/facts/facts.go (about) 1 // Copyright 2018 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 facts defines a serializable set of analysis.Fact. 6 // 7 // It provides a partial implementation of the Fact-related parts of the 8 // analysis.Pass interface for use in analysis drivers such as "go vet" 9 // and other build systems. 10 // 11 // The serial format is unspecified and may change, so the same version 12 // of this package must be used for reading and writing serialized facts. 13 // 14 // The handling of facts in the analysis system parallels the handling 15 // of type information in the compiler: during compilation of package P, 16 // the compiler emits an export data file that describes the type of 17 // every object (named thing) defined in package P, plus every object 18 // indirectly reachable from one of those objects. Thus the downstream 19 // compiler of package Q need only load one export data file per direct 20 // import of Q, and it will learn everything about the API of package P 21 // and everything it needs to know about the API of P's dependencies. 22 // 23 // Similarly, analysis of package P emits a fact set containing facts 24 // about all objects exported from P, plus additional facts about only 25 // those objects of P's dependencies that are reachable from the API of 26 // package P; the downstream analysis of Q need only load one fact set 27 // per direct import of Q. 28 // 29 // The notion of "exportedness" that matters here is that of the 30 // compiler. According to the language spec, a method pkg.T.f is 31 // unexported simply because its name starts with lowercase. But the 32 // compiler must nonetheless export f so that downstream compilations can 33 // accurately ascertain whether pkg.T implements an interface pkg.I 34 // defined as interface{f()}. Exported thus means "described in export 35 // data". 36 // 37 package facts 38 39 import ( 40 "bytes" 41 "encoding/gob" 42 "fmt" 43 "go/types" 44 "io/ioutil" 45 "log" 46 "reflect" 47 "sort" 48 "sync" 49 50 "github.com/powerman/golang-tools/go/analysis" 51 "github.com/powerman/golang-tools/go/types/objectpath" 52 ) 53 54 const debug = false 55 56 // A Set is a set of analysis.Facts. 57 // 58 // Decode creates a Set of facts by reading from the imports of a given 59 // package, and Encode writes out the set. Between these operation, 60 // the Import and Export methods will query and update the set. 61 // 62 // All of Set's methods except String are safe to call concurrently. 63 type Set struct { 64 pkg *types.Package 65 mu sync.Mutex 66 m map[key]analysis.Fact 67 } 68 69 type key struct { 70 pkg *types.Package 71 obj types.Object // (object facts only) 72 t reflect.Type 73 } 74 75 // ImportObjectFact implements analysis.Pass.ImportObjectFact. 76 func (s *Set) ImportObjectFact(obj types.Object, ptr analysis.Fact) bool { 77 if obj == nil { 78 panic("nil object") 79 } 80 key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(ptr)} 81 s.mu.Lock() 82 defer s.mu.Unlock() 83 if v, ok := s.m[key]; ok { 84 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 85 return true 86 } 87 return false 88 } 89 90 // ExportObjectFact implements analysis.Pass.ExportObjectFact. 91 func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) { 92 if obj.Pkg() != s.pkg { 93 log.Panicf("in package %s: ExportObjectFact(%s, %T): can't set fact on object belonging another package", 94 s.pkg, obj, fact) 95 } 96 key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(fact)} 97 s.mu.Lock() 98 s.m[key] = fact // clobber any existing entry 99 s.mu.Unlock() 100 } 101 102 func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact { 103 var facts []analysis.ObjectFact 104 s.mu.Lock() 105 for k, v := range s.m { 106 if k.obj != nil && filter[k.t] { 107 facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: v}) 108 } 109 } 110 s.mu.Unlock() 111 return facts 112 } 113 114 // ImportPackageFact implements analysis.Pass.ImportPackageFact. 115 func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { 116 if pkg == nil { 117 panic("nil package") 118 } 119 key := key{pkg: pkg, t: reflect.TypeOf(ptr)} 120 s.mu.Lock() 121 defer s.mu.Unlock() 122 if v, ok := s.m[key]; ok { 123 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 124 return true 125 } 126 return false 127 } 128 129 // ExportPackageFact implements analysis.Pass.ExportPackageFact. 130 func (s *Set) ExportPackageFact(fact analysis.Fact) { 131 key := key{pkg: s.pkg, t: reflect.TypeOf(fact)} 132 s.mu.Lock() 133 s.m[key] = fact // clobber any existing entry 134 s.mu.Unlock() 135 } 136 137 func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact { 138 var facts []analysis.PackageFact 139 s.mu.Lock() 140 for k, v := range s.m { 141 if k.obj == nil && filter[k.t] { 142 facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: v}) 143 } 144 } 145 s.mu.Unlock() 146 return facts 147 } 148 149 // gobFact is the Gob declaration of a serialized fact. 150 type gobFact struct { 151 PkgPath string // path of package 152 Object objectpath.Path // optional path of object relative to package itself 153 Fact analysis.Fact // type and value of user-defined Fact 154 } 155 156 // Decode decodes all the facts relevant to the analysis of package pkg. 157 // The read function reads serialized fact data from an external source 158 // for one of of pkg's direct imports. The empty file is a valid 159 // encoding of an empty fact set. 160 // 161 // It is the caller's responsibility to call gob.Register on all 162 // necessary fact types. 163 func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) { 164 // Compute the import map for this package. 165 // See the package doc comment. 166 packages := importMap(pkg.Imports()) 167 168 // Read facts from imported packages. 169 // Facts may describe indirectly imported packages, or their objects. 170 m := make(map[key]analysis.Fact) // one big bucket 171 for _, imp := range pkg.Imports() { 172 logf := func(format string, args ...interface{}) { 173 if debug { 174 prefix := fmt.Sprintf("in %s, importing %s: ", 175 pkg.Path(), imp.Path()) 176 log.Print(prefix, fmt.Sprintf(format, args...)) 177 } 178 } 179 180 // Read the gob-encoded facts. 181 data, err := read(imp.Path()) 182 if err != nil { 183 return nil, fmt.Errorf("in %s, can't import facts for package %q: %v", 184 pkg.Path(), imp.Path(), err) 185 } 186 if len(data) == 0 { 187 continue // no facts 188 } 189 var gobFacts []gobFact 190 if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil { 191 return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err) 192 } 193 if debug { 194 logf("decoded %d facts: %v", len(gobFacts), gobFacts) 195 } 196 197 // Parse each one into a key and a Fact. 198 for _, f := range gobFacts { 199 factPkg := packages[f.PkgPath] 200 if factPkg == nil { 201 // Fact relates to a dependency that was 202 // unused in this translation unit. Skip. 203 logf("no package %q; discarding %v", f.PkgPath, f.Fact) 204 continue 205 } 206 key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)} 207 if f.Object != "" { 208 // object fact 209 obj, err := objectpath.Object(factPkg, f.Object) 210 if err != nil { 211 // (most likely due to unexported object) 212 // TODO(adonovan): audit for other possibilities. 213 logf("no object for path: %v; discarding %s", err, f.Fact) 214 continue 215 } 216 key.obj = obj 217 logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj) 218 } else { 219 // package fact 220 logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg) 221 } 222 m[key] = f.Fact 223 } 224 } 225 226 return &Set{pkg: pkg, m: m}, nil 227 } 228 229 // Encode encodes a set of facts to a memory buffer. 230 // 231 // It may fail if one of the Facts could not be gob-encoded, but this is 232 // a sign of a bug in an Analyzer. 233 func (s *Set) Encode() []byte { 234 235 // TODO(adonovan): opt: use a more efficient encoding 236 // that avoids repeating PkgPath for each fact. 237 238 // Gather all facts, including those from imported packages. 239 var gobFacts []gobFact 240 241 s.mu.Lock() 242 for k, fact := range s.m { 243 if debug { 244 log.Printf("%v => %s\n", k, fact) 245 } 246 var object objectpath.Path 247 if k.obj != nil { 248 path, err := objectpath.For(k.obj) 249 if err != nil { 250 if debug { 251 log.Printf("discarding fact %s about %s\n", fact, k.obj) 252 } 253 continue // object not accessible from package API; discard fact 254 } 255 object = path 256 } 257 gobFacts = append(gobFacts, gobFact{ 258 PkgPath: k.pkg.Path(), 259 Object: object, 260 Fact: fact, 261 }) 262 } 263 s.mu.Unlock() 264 265 // Sort facts by (package, object, type) for determinism. 266 sort.Slice(gobFacts, func(i, j int) bool { 267 x, y := gobFacts[i], gobFacts[j] 268 if x.PkgPath != y.PkgPath { 269 return x.PkgPath < y.PkgPath 270 } 271 if x.Object != y.Object { 272 return x.Object < y.Object 273 } 274 tx := reflect.TypeOf(x.Fact) 275 ty := reflect.TypeOf(y.Fact) 276 if tx != ty { 277 return tx.String() < ty.String() 278 } 279 return false // equal 280 }) 281 282 var buf bytes.Buffer 283 if len(gobFacts) > 0 { 284 if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil { 285 // Fact encoding should never fail. Identify the culprit. 286 for _, gf := range gobFacts { 287 if err := gob.NewEncoder(ioutil.Discard).Encode(gf); err != nil { 288 fact := gf.Fact 289 pkgpath := reflect.TypeOf(fact).Elem().PkgPath() 290 log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q", 291 fact, err, fact, pkgpath) 292 } 293 } 294 } 295 } 296 297 if debug { 298 log.Printf("package %q: encode %d facts, %d bytes\n", 299 s.pkg.Path(), len(gobFacts), buf.Len()) 300 } 301 302 return buf.Bytes() 303 } 304 305 // String is provided only for debugging, and must not be called 306 // concurrent with any Import/Export method. 307 func (s *Set) String() string { 308 var buf bytes.Buffer 309 buf.WriteString("{") 310 for k, f := range s.m { 311 if buf.Len() > 1 { 312 buf.WriteString(", ") 313 } 314 if k.obj != nil { 315 buf.WriteString(k.obj.String()) 316 } else { 317 buf.WriteString(k.pkg.Path()) 318 } 319 fmt.Fprintf(&buf, ": %v", f) 320 } 321 buf.WriteString("}") 322 return buf.String() 323 }