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