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