]>
Commit | Line | Data |
---|---|---|
4f4a855d ILT |
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 | |
5a8ea165 | 32 | // compiler must nonetheless export f so that downstream compilations can |
4f4a855d ILT |
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 | "golang.org/x/tools/go/analysis" | |
51 | "golang.org/x/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 | ||
5a8ea165 ILT |
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 | ||
4f4a855d ILT |
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 | ||
5a8ea165 ILT |
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 | ||
4f4a855d ILT |
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 | } |