// 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() }