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// 37package facts 38 39import ( 40 "bytes" 41 "encoding/gob" 42 "fmt" 43 "go/types" 44 "io/ioutil" 45 "log" 46 "reflect" 47 "sort" 48 "sync" 49 50 "golang.org/x/tools/go/analysis" 51 "golang.org/x/tools/go/types/objectpath" 52) 53 54const 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. 63type Set struct { 64 pkg *types.Package 65 mu sync.Mutex 66 m map[key]analysis.Fact 67} 68 69type 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. 76func (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. 91func (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 102func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact { 103 var facts []analysis.ObjectFact 104 for k, v := range s.m { 105 if k.obj != nil && filter[k.t] { 106 facts = append(facts, analysis.ObjectFact{k.obj, v}) 107 } 108 } 109 return facts 110} 111 112// ImportPackageFact implements analysis.Pass.ImportPackageFact. 113func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { 114 if pkg == nil { 115 panic("nil package") 116 } 117 key := key{pkg: pkg, t: reflect.TypeOf(ptr)} 118 s.mu.Lock() 119 defer s.mu.Unlock() 120 if v, ok := s.m[key]; ok { 121 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 122 return true 123 } 124 return false 125} 126 127// ExportPackageFact implements analysis.Pass.ExportPackageFact. 128func (s *Set) ExportPackageFact(fact analysis.Fact) { 129 key := key{pkg: s.pkg, t: reflect.TypeOf(fact)} 130 s.mu.Lock() 131 s.m[key] = fact // clobber any existing entry 132 s.mu.Unlock() 133} 134 135func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact { 136 var facts []analysis.PackageFact 137 for k, v := range s.m { 138 if k.obj == nil && filter[k.t] { 139 facts = append(facts, analysis.PackageFact{k.pkg, v}) 140 } 141 } 142 return facts 143} 144 145// gobFact is the Gob declaration of a serialized fact. 146type gobFact struct { 147 PkgPath string // path of package 148 Object objectpath.Path // optional path of object relative to package itself 149 Fact analysis.Fact // type and value of user-defined Fact 150} 151 152// Decode decodes all the facts relevant to the analysis of package pkg. 153// The read function reads serialized fact data from an external source 154// for one of of pkg's direct imports. The empty file is a valid 155// encoding of an empty fact set. 156// 157// It is the caller's responsibility to call gob.Register on all 158// necessary fact types. 159func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) { 160 // Compute the import map for this package. 161 // See the package doc comment. 162 packages := importMap(pkg.Imports()) 163 164 // Read facts from imported packages. 165 // Facts may describe indirectly imported packages, or their objects. 166 m := make(map[key]analysis.Fact) // one big bucket 167 for _, imp := range pkg.Imports() { 168 logf := func(format string, args ...interface{}) { 169 if debug { 170 prefix := fmt.Sprintf("in %s, importing %s: ", 171 pkg.Path(), imp.Path()) 172 log.Print(prefix, fmt.Sprintf(format, args...)) 173 } 174 } 175 176 // Read the gob-encoded facts. 177 data, err := read(imp.Path()) 178 if err != nil { 179 return nil, fmt.Errorf("in %s, can't import facts for package %q: %v", 180 pkg.Path(), imp.Path(), err) 181 } 182 if len(data) == 0 { 183 continue // no facts 184 } 185 var gobFacts []gobFact 186 if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil { 187 return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err) 188 } 189 if debug { 190 logf("decoded %d facts: %v", len(gobFacts), gobFacts) 191 } 192 193 // Parse each one into a key and a Fact. 194 for _, f := range gobFacts { 195 factPkg := packages[f.PkgPath] 196 if factPkg == nil { 197 // Fact relates to a dependency that was 198 // unused in this translation unit. Skip. 199 logf("no package %q; discarding %v", f.PkgPath, f.Fact) 200 continue 201 } 202 key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)} 203 if f.Object != "" { 204 // object fact 205 obj, err := objectpath.Object(factPkg, f.Object) 206 if err != nil { 207 // (most likely due to unexported object) 208 // TODO(adonovan): audit for other possibilities. 209 logf("no object for path: %v; discarding %s", err, f.Fact) 210 continue 211 } 212 key.obj = obj 213 logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj) 214 } else { 215 // package fact 216 logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg) 217 } 218 m[key] = f.Fact 219 } 220 } 221 222 return &Set{pkg: pkg, m: m}, nil 223} 224 225// Encode encodes a set of facts to a memory buffer. 226// 227// It may fail if one of the Facts could not be gob-encoded, but this is 228// a sign of a bug in an Analyzer. 229func (s *Set) Encode() []byte { 230 231 // TODO(adonovan): opt: use a more efficient encoding 232 // that avoids repeating PkgPath for each fact. 233 234 // Gather all facts, including those from imported packages. 235 var gobFacts []gobFact 236 237 s.mu.Lock() 238 for k, fact := range s.m { 239 if debug { 240 log.Printf("%v => %s\n", k, fact) 241 } 242 var object objectpath.Path 243 if k.obj != nil { 244 path, err := objectpath.For(k.obj) 245 if err != nil { 246 if debug { 247 log.Printf("discarding fact %s about %s\n", fact, k.obj) 248 } 249 continue // object not accessible from package API; discard fact 250 } 251 object = path 252 } 253 gobFacts = append(gobFacts, gobFact{ 254 PkgPath: k.pkg.Path(), 255 Object: object, 256 Fact: fact, 257 }) 258 } 259 s.mu.Unlock() 260 261 // Sort facts by (package, object, type) for determinism. 262 sort.Slice(gobFacts, func(i, j int) bool { 263 x, y := gobFacts[i], gobFacts[j] 264 if x.PkgPath != y.PkgPath { 265 return x.PkgPath < y.PkgPath 266 } 267 if x.Object != y.Object { 268 return x.Object < y.Object 269 } 270 tx := reflect.TypeOf(x.Fact) 271 ty := reflect.TypeOf(y.Fact) 272 if tx != ty { 273 return tx.String() < ty.String() 274 } 275 return false // equal 276 }) 277 278 var buf bytes.Buffer 279 if len(gobFacts) > 0 { 280 if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil { 281 // Fact encoding should never fail. Identify the culprit. 282 for _, gf := range gobFacts { 283 if err := gob.NewEncoder(ioutil.Discard).Encode(gf); err != nil { 284 fact := gf.Fact 285 pkgpath := reflect.TypeOf(fact).Elem().PkgPath() 286 log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q", 287 fact, err, fact, pkgpath) 288 } 289 } 290 } 291 } 292 293 if debug { 294 log.Printf("package %q: encode %d facts, %d bytes\n", 295 s.pkg.Path(), len(gobFacts), buf.Len()) 296 } 297 298 return buf.Bytes() 299} 300 301// String is provided only for debugging, and must not be called 302// concurrent with any Import/Export method. 303func (s *Set) String() string { 304 var buf bytes.Buffer 305 buf.WriteString("{") 306 for k, f := range s.m { 307 if buf.Len() > 1 { 308 buf.WriteString(", ") 309 } 310 if k.obj != nil { 311 buf.WriteString(k.obj.String()) 312 } else { 313 buf.WriteString(k.pkg.Path()) 314 } 315 fmt.Fprintf(&buf, ": %v", f) 316 } 317 buf.WriteString("}") 318 return buf.String() 319} 320