
Some checks failed
Extra / Vulnerability scanner (push) Has been cancelled
CI / go-mod (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Release a tag / release (push) Has been cancelled
CI / tests-on-windows (push) Has been cancelled
CI / tests-on-macos (push) Has been cancelled
CI / tests-on-unix (1.14) (push) Has been cancelled
CI / tests-on-unix (1.15) (push) Has been cancelled
CI / tests-on-unix (1.16) (push) Has been cancelled
CI / check_generated (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile.alpine]) (push) Has been cancelled
Release a tag / docker-release (map[Dockerfile:build/Dockerfile]) (push) Has been cancelled
126 lines
3.5 KiB
Go
126 lines
3.5 KiB
Go
package goanalysis
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"go/types"
|
|
"reflect"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
)
|
|
|
|
type objectFactKey struct {
|
|
obj types.Object
|
|
typ reflect.Type
|
|
}
|
|
|
|
type packageFactKey struct {
|
|
pkg *types.Package
|
|
typ reflect.Type
|
|
}
|
|
|
|
type Fact struct {
|
|
Path string // non-empty only for object facts
|
|
Fact analysis.Fact
|
|
}
|
|
|
|
// inheritFacts populates act.facts with
|
|
// those it obtains from its dependency, dep.
|
|
func inheritFacts(act, dep *action) {
|
|
serialize := false
|
|
|
|
for key, fact := range dep.objectFacts {
|
|
// Filter out facts related to objects
|
|
// that are irrelevant downstream
|
|
// (equivalently: not in the compiler export data).
|
|
if !exportedFrom(key.obj, dep.pkg.Types) {
|
|
factsInheritDebugf("%v: discarding %T fact from %s for %s: %s", act, fact, dep, key.obj, fact)
|
|
continue
|
|
}
|
|
|
|
// Optionally serialize/deserialize fact
|
|
// to verify that it works across address spaces.
|
|
if serialize {
|
|
var err error
|
|
fact, err = codeFact(fact)
|
|
if err != nil {
|
|
act.r.log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
|
}
|
|
}
|
|
|
|
factsInheritDebugf("%v: inherited %T fact for %s: %s", act, fact, key.obj, fact)
|
|
act.objectFacts[key] = fact
|
|
}
|
|
|
|
for key, fact := range dep.packageFacts {
|
|
// TODO: filter out facts that belong to
|
|
// packages not mentioned in the export data
|
|
// to prevent side channels.
|
|
|
|
// Optionally serialize/deserialize fact
|
|
// to verify that it works across address spaces
|
|
// and is deterministic.
|
|
if serialize {
|
|
var err error
|
|
fact, err = codeFact(fact)
|
|
if err != nil {
|
|
act.r.log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
|
}
|
|
}
|
|
|
|
factsInheritDebugf("%v: inherited %T fact for %s: %s", act, fact, key.pkg.Path(), fact)
|
|
act.packageFacts[key] = fact
|
|
}
|
|
}
|
|
|
|
// codeFact encodes then decodes a fact,
|
|
// just to exercise that logic.
|
|
func codeFact(fact analysis.Fact) (analysis.Fact, error) {
|
|
// We encode facts one at a time.
|
|
// A real modular driver would emit all facts
|
|
// into one encoder to improve gob efficiency.
|
|
var buf bytes.Buffer
|
|
if err := gob.NewEncoder(&buf).Encode(fact); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Encode it twice and assert that we get the same bits.
|
|
// This helps detect nondeterministic Gob encoding (e.g. of maps).
|
|
var buf2 bytes.Buffer
|
|
if err := gob.NewEncoder(&buf2).Encode(fact); err != nil {
|
|
return nil, err
|
|
}
|
|
if !bytes.Equal(buf.Bytes(), buf2.Bytes()) {
|
|
return nil, fmt.Errorf("encoding of %T fact is nondeterministic", fact)
|
|
}
|
|
|
|
newFact := reflect.New(reflect.TypeOf(fact).Elem()).Interface().(analysis.Fact)
|
|
if err := gob.NewDecoder(&buf).Decode(newFact); err != nil {
|
|
return nil, err
|
|
}
|
|
return newFact, nil
|
|
}
|
|
|
|
// exportedFrom reports whether obj may be visible to a package that imports pkg.
|
|
// This includes not just the exported members of pkg, but also unexported
|
|
// constants, types, fields, and methods, perhaps belonging to other packages,
|
|
// that find there way into the API.
|
|
// This is an over-approximation of the more accurate approach used by
|
|
// gc export data, which walks the type graph, but it's much simpler.
|
|
//
|
|
// TODO(adonovan): do more accurate filtering by walking the type graph.
|
|
func exportedFrom(obj types.Object, pkg *types.Package) bool {
|
|
switch obj := obj.(type) {
|
|
case *types.Func:
|
|
return obj.Exported() && obj.Pkg() == pkg ||
|
|
obj.Type().(*types.Signature).Recv() != nil
|
|
case *types.Var:
|
|
return obj.Exported() && obj.Pkg() == pkg ||
|
|
obj.IsField()
|
|
case *types.TypeName, *types.Const:
|
|
return true
|
|
}
|
|
return false // Nil, Builtin, Label, or PkgName
|
|
}
|