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//
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
102func (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.
115func (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.
130func (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
137func (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.
150type 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.
163func 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.
233func (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.
307func (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}
324