| 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