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