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