| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package facts defines a serializable set of analysis.Fact. |
| // |
| // It provides a partial implementation of the Fact-related parts of the |
| // analysis.Pass interface for use in analysis drivers such as "go vet" |
| // and other build systems. |
| // |
| // The serial format is unspecified and may change, so the same version |
| // of this package must be used for reading and writing serialized facts. |
| // |
| // The handling of facts in the analysis system parallels the handling |
| // of type information in the compiler: during compilation of package P, |
| // the compiler emits an export data file that describes the type of |
| // every object (named thing) defined in package P, plus every object |
| // indirectly reachable from one of those objects. Thus the downstream |
| // compiler of package Q need only load one export data file per direct |
| // import of Q, and it will learn everything about the API of package P |
| // and everything it needs to know about the API of P's dependencies. |
| // |
| // Similarly, analysis of package P emits a fact set containing facts |
| // about all objects exported from P, plus additional facts about only |
| // those objects of P's dependencies that are reachable from the API of |
| // package P; the downstream analysis of Q need only load one fact set |
| // per direct import of Q. |
| // |
| // The notion of "exportedness" that matters here is that of the |
| // compiler. According to the language spec, a method pkg.T.f is |
| // unexported simply because its name starts with lowercase. But the |
| // compiler must nonetheless export f so that downstream compilations can |
| // accurately ascertain whether pkg.T implements an interface pkg.I |
| // defined as interface{f()}. Exported thus means "described in export |
| // data". |
| // |
| package facts |
| |
| import ( |
| "bytes" |
| "encoding/gob" |
| "fmt" |
| "go/types" |
| "io/ioutil" |
| "log" |
| "reflect" |
| "sort" |
| "sync" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/go/types/objectpath" |
| ) |
| |
| const debug = false |
| |
| // A Set is a set of analysis.Facts. |
| // |
| // Decode creates a Set of facts by reading from the imports of a given |
| // package, and Encode writes out the set. Between these operation, |
| // the Import and Export methods will query and update the set. |
| // |
| // All of Set's methods except String are safe to call concurrently. |
| type Set struct { |
| pkg *types.Package |
| mu sync.Mutex |
| m map[key]analysis.Fact |
| } |
| |
| type key struct { |
| pkg *types.Package |
| obj types.Object // (object facts only) |
| t reflect.Type |
| } |
| |
| // ImportObjectFact implements analysis.Pass.ImportObjectFact. |
| func (s *Set) ImportObjectFact(obj types.Object, ptr analysis.Fact) bool { |
| if obj == nil { |
| panic("nil object") |
| } |
| key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(ptr)} |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| if v, ok := s.m[key]; ok { |
| reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) |
| return true |
| } |
| return false |
| } |
| |
| // ExportObjectFact implements analysis.Pass.ExportObjectFact. |
| func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) { |
| if obj.Pkg() != s.pkg { |
| log.Panicf("in package %s: ExportObjectFact(%s, %T): can't set fact on object belonging another package", |
| s.pkg, obj, fact) |
| } |
| key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(fact)} |
| s.mu.Lock() |
| s.m[key] = fact // clobber any existing entry |
| s.mu.Unlock() |
| } |
| |
| func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact { |
| var facts []analysis.ObjectFact |
| s.mu.Lock() |
| for k, v := range s.m { |
| if k.obj != nil && filter[k.t] { |
| facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: v}) |
| } |
| } |
| s.mu.Unlock() |
| return facts |
| } |
| |
| // ImportPackageFact implements analysis.Pass.ImportPackageFact. |
| func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { |
| if pkg == nil { |
| panic("nil package") |
| } |
| key := key{pkg: pkg, t: reflect.TypeOf(ptr)} |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| if v, ok := s.m[key]; ok { |
| reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) |
| return true |
| } |
| return false |
| } |
| |
| // ExportPackageFact implements analysis.Pass.ExportPackageFact. |
| func (s *Set) ExportPackageFact(fact analysis.Fact) { |
| key := key{pkg: s.pkg, t: reflect.TypeOf(fact)} |
| s.mu.Lock() |
| s.m[key] = fact // clobber any existing entry |
| s.mu.Unlock() |
| } |
| |
| func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact { |
| var facts []analysis.PackageFact |
| s.mu.Lock() |
| for k, v := range s.m { |
| if k.obj == nil && filter[k.t] { |
| facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: v}) |
| } |
| } |
| s.mu.Unlock() |
| return facts |
| } |
| |
| // gobFact is the Gob declaration of a serialized fact. |
| type gobFact struct { |
| PkgPath string // path of package |
| Object objectpath.Path // optional path of object relative to package itself |
| Fact analysis.Fact // type and value of user-defined Fact |
| } |
| |
| // Decode decodes all the facts relevant to the analysis of package pkg. |
| // The read function reads serialized fact data from an external source |
| // for one of of pkg's direct imports. The empty file is a valid |
| // encoding of an empty fact set. |
| // |
| // It is the caller's responsibility to call gob.Register on all |
| // necessary fact types. |
| func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) { |
| // Compute the import map for this package. |
| // See the package doc comment. |
| packages := importMap(pkg.Imports()) |
| |
| // Read facts from imported packages. |
| // Facts may describe indirectly imported packages, or their objects. |
| m := make(map[key]analysis.Fact) // one big bucket |
| for _, imp := range pkg.Imports() { |
| logf := func(format string, args ...interface{}) { |
| if debug { |
| prefix := fmt.Sprintf("in %s, importing %s: ", |
| pkg.Path(), imp.Path()) |
| log.Print(prefix, fmt.Sprintf(format, args...)) |
| } |
| } |
| |
| // Read the gob-encoded facts. |
| data, err := read(imp.Path()) |
| if err != nil { |
| return nil, fmt.Errorf("in %s, can't import facts for package %q: %v", |
| pkg.Path(), imp.Path(), err) |
| } |
| if len(data) == 0 { |
| continue // no facts |
| } |
| var gobFacts []gobFact |
| if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil { |
| return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err) |
| } |
| if debug { |
| logf("decoded %d facts: %v", len(gobFacts), gobFacts) |
| } |
| |
| // Parse each one into a key and a Fact. |
| for _, f := range gobFacts { |
| factPkg := packages[f.PkgPath] |
| if factPkg == nil { |
| // Fact relates to a dependency that was |
| // unused in this translation unit. Skip. |
| logf("no package %q; discarding %v", f.PkgPath, f.Fact) |
| continue |
| } |
| key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)} |
| if f.Object != "" { |
| // object fact |
| obj, err := objectpath.Object(factPkg, f.Object) |
| if err != nil { |
| // (most likely due to unexported object) |
| // TODO(adonovan): audit for other possibilities. |
| logf("no object for path: %v; discarding %s", err, f.Fact) |
| continue |
| } |
| key.obj = obj |
| logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj) |
| } else { |
| // package fact |
| logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg) |
| } |
| m[key] = f.Fact |
| } |
| } |
| |
| return &Set{pkg: pkg, m: m}, nil |
| } |
| |
| // Encode encodes a set of facts to a memory buffer. |
| // |
| // It may fail if one of the Facts could not be gob-encoded, but this is |
| // a sign of a bug in an Analyzer. |
| func (s *Set) Encode() []byte { |
| |
| // TODO(adonovan): opt: use a more efficient encoding |
| // that avoids repeating PkgPath for each fact. |
| |
| // Gather all facts, including those from imported packages. |
| var gobFacts []gobFact |
| |
| s.mu.Lock() |
| for k, fact := range s.m { |
| if debug { |
| log.Printf("%v => %s\n", k, fact) |
| } |
| var object objectpath.Path |
| if k.obj != nil { |
| path, err := objectpath.For(k.obj) |
| if err != nil { |
| if debug { |
| log.Printf("discarding fact %s about %s\n", fact, k.obj) |
| } |
| continue // object not accessible from package API; discard fact |
| } |
| object = path |
| } |
| gobFacts = append(gobFacts, gobFact{ |
| PkgPath: k.pkg.Path(), |
| Object: object, |
| Fact: fact, |
| }) |
| } |
| s.mu.Unlock() |
| |
| // Sort facts by (package, object, type) for determinism. |
| sort.Slice(gobFacts, func(i, j int) bool { |
| x, y := gobFacts[i], gobFacts[j] |
| if x.PkgPath != y.PkgPath { |
| return x.PkgPath < y.PkgPath |
| } |
| if x.Object != y.Object { |
| return x.Object < y.Object |
| } |
| tx := reflect.TypeOf(x.Fact) |
| ty := reflect.TypeOf(y.Fact) |
| if tx != ty { |
| return tx.String() < ty.String() |
| } |
| return false // equal |
| }) |
| |
| var buf bytes.Buffer |
| if len(gobFacts) > 0 { |
| if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil { |
| // Fact encoding should never fail. Identify the culprit. |
| for _, gf := range gobFacts { |
| if err := gob.NewEncoder(ioutil.Discard).Encode(gf); err != nil { |
| fact := gf.Fact |
| pkgpath := reflect.TypeOf(fact).Elem().PkgPath() |
| log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q", |
| fact, err, fact, pkgpath) |
| } |
| } |
| } |
| } |
| |
| if debug { |
| log.Printf("package %q: encode %d facts, %d bytes\n", |
| s.pkg.Path(), len(gobFacts), buf.Len()) |
| } |
| |
| return buf.Bytes() |
| } |
| |
| // String is provided only for debugging, and must not be called |
| // concurrent with any Import/Export method. |
| func (s *Set) String() string { |
| var buf bytes.Buffer |
| buf.WriteString("{") |
| for k, f := range s.m { |
| if buf.Len() > 1 { |
| buf.WriteString(", ") |
| } |
| if k.obj != nil { |
| buf.WriteString(k.obj.String()) |
| } else { |
| buf.WriteString(k.pkg.Path()) |
| } |
| fmt.Fprintf(&buf, ": %v", f) |
| } |
| buf.WriteString("}") |
| return buf.String() |
| } |