390 lines
12 KiB
Go
390 lines
12 KiB
Go
// 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"
|
|
"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
|
|
}
|
|
|
|
// A Decoder decodes the facts from the direct imports of the package
|
|
// provided to NewEncoder. A single decoder may be used to decode
|
|
// multiple fact sets (e.g. each for a different set of fact types)
|
|
// for the same package. Each call to Decode returns an independent
|
|
// fact set.
|
|
type Decoder struct {
|
|
pkg *types.Package
|
|
getPackage GetPackageFunc
|
|
}
|
|
|
|
// NewDecoder returns a fact decoder for the specified package.
|
|
//
|
|
// It uses a brute-force recursive approach to enumerate all objects
|
|
// defined by dependencies of pkg, so that it can learn the set of
|
|
// package paths that may be mentioned in the fact encoding. This does
|
|
// not scale well; use [NewDecoderFunc] where possible.
|
|
func NewDecoder(pkg *types.Package) *Decoder {
|
|
// Compute the import map for this package.
|
|
// See the package doc comment.
|
|
m := importMap(pkg.Imports())
|
|
getPackageFunc := func(path string) *types.Package { return m[path] }
|
|
return NewDecoderFunc(pkg, getPackageFunc)
|
|
}
|
|
|
|
// NewDecoderFunc returns a fact decoder for the specified package.
|
|
//
|
|
// It calls the getPackage function for the package path string of
|
|
// each dependency (perhaps indirect) that it encounters in the
|
|
// encoding. If the function returns nil, the fact is discarded.
|
|
//
|
|
// This function is preferred over [NewDecoder] when the client is
|
|
// capable of efficient look-up of packages by package path.
|
|
func NewDecoderFunc(pkg *types.Package, getPackage GetPackageFunc) *Decoder {
|
|
return &Decoder{
|
|
pkg: pkg,
|
|
getPackage: getPackage,
|
|
}
|
|
}
|
|
|
|
// A GetPackageFunc function returns the package denoted by a package path.
|
|
type GetPackageFunc = func(pkgPath string) *types.Package
|
|
|
|
// Decode decodes all the facts relevant to the analysis of package
|
|
// pkgPath. The read function reads serialized fact data from an external
|
|
// source for one of pkg's direct imports, identified by package path.
|
|
// 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.
|
|
//
|
|
// Concurrent calls to Decode are safe, so long as the
|
|
// [GetPackageFunc] (if any) is also concurrency-safe.
|
|
func (d *Decoder) Decode(read func(pkgPath string) ([]byte, error)) (*Set, error) {
|
|
// 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 d.pkg.Imports() {
|
|
logf := func(format string, args ...interface{}) {
|
|
if debug {
|
|
prefix := fmt.Sprintf("in %s, importing %s: ",
|
|
d.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",
|
|
d.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)
|
|
}
|
|
logf("decoded %d facts: %v", len(gobFacts), gobFacts)
|
|
|
|
// Parse each one into a key and a Fact.
|
|
for _, f := range gobFacts {
|
|
factPkg := d.getPackage(f.PkgPath) // possibly an indirect dependency
|
|
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: d.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 {
|
|
encoder := new(objectpath.Encoder)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Don't export facts that we imported from another
|
|
// package, unless they represent fields or methods,
|
|
// or package-level types.
|
|
// (Facts about packages, and other package-level
|
|
// objects, are only obtained from direct imports so
|
|
// they needn't be reexported.)
|
|
//
|
|
// This is analogous to the pruning done by "deep"
|
|
// export data for types, but not as precise because
|
|
// we aren't careful about which structs or methods
|
|
// we rexport: it should be only those referenced
|
|
// from the API of s.pkg.
|
|
// TODO(adonovan): opt: be more precise. e.g.
|
|
// intersect with the set of objects computed by
|
|
// importMap(s.pkg.Imports()).
|
|
// TODO(adonovan): opt: implement "shallow" facts.
|
|
if k.pkg != s.pkg {
|
|
if k.obj == nil {
|
|
continue // imported package fact
|
|
}
|
|
if _, isType := k.obj.(*types.TypeName); !isType &&
|
|
k.obj.Parent() == k.obj.Pkg().Scope() {
|
|
continue // imported fact about package-level non-type object
|
|
}
|
|
}
|
|
|
|
var object objectpath.Path
|
|
if k.obj != nil {
|
|
path, err := encoder.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(io.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()
|
|
}
|