github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/internal/facts/facts.go (about)

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