go.tools/pointer: opt: type-based label tracking reduces solver time by up to 75%.
Observation: not all alias facts are interesting.
- A channel-peers query also cares about pointers of kind chan.
- An oracle "points-to" query on an expression of kind map
only cares about maps.
- We always care about func, interface and reflect.Value,
since they're needed for sound analysis of dynamic dispatch.
We needn't bother collecting alias information for
uninteresting pointers, and this massively reduces the number
of labels flowing in to the constraint system.
The only constraints that create new labels are addressOf
and offsetAddr; both are now selectively emitted by type.
We compute the set of type kinds to track, based on the
{Indirect,}Query types. (We could enable tracking at an
even finer grain if we want.)
This requires that we can see all the {Indirect,}Query
value types a priori, which is not the case for the PrintCalls
mechanism used in the tests, so I have rewritten the latter
to use {Indirect,}Query instead.
This reduces the solver-phase time for the entire standard
library and tests from >8m to <2m. Similar speedups are
obtained on small and medium-sized programs.
Details:
- shouldTrack inspects the flattened form of a type to see if
it contains fields we must track. It memoizes the result.
- added precondition checks to (*Config).Add{,Indirect}Query.
- added (*ssa.Program).LookupMethod convenience method.
- added Example of how to use the Query mechanism.
- removed code made dead by a recent invariant:
the only pointerlike Const value is nil.
- don't generate constraints for any functions in "reflect".
(we had forgotten to skip synthetic wrappers too).
- write PTA warnings to the log.
- add annotations for more intrinsics.
LGTM=gri, crawshaw
R=crawshaw, gri
CC=golang-codereviews
https://golang.org/cl/62540043
This commit is contained in:
parent
675033b4f3
commit
5f96644dbf
|
|
@ -193,6 +193,7 @@ type analysis struct {
|
||||||
panicNode nodeid // sink for panic, source for recover
|
panicNode nodeid // sink for panic, source for recover
|
||||||
nodes []*node // indexed by nodeid
|
nodes []*node // indexed by nodeid
|
||||||
flattenMemo map[types.Type][]*fieldInfo // memoization of flatten()
|
flattenMemo map[types.Type][]*fieldInfo // memoization of flatten()
|
||||||
|
trackTypes map[types.Type]bool // memoization of shouldTrack()
|
||||||
constraints []constraint // set of constraints
|
constraints []constraint // set of constraints
|
||||||
cgnodes []*cgnode // all cgnodes
|
cgnodes []*cgnode // all cgnodes
|
||||||
genq []*cgnode // queue of functions to generate constraints for
|
genq []*cgnode // queue of functions to generate constraints for
|
||||||
|
|
@ -204,6 +205,7 @@ type analysis struct {
|
||||||
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
||||||
work worklist // solver's worklist
|
work worklist // solver's worklist
|
||||||
result *Result // results of the analysis
|
result *Result // results of the analysis
|
||||||
|
track track // pointerlike types whose aliasing we track
|
||||||
|
|
||||||
// Reflection & intrinsics:
|
// Reflection & intrinsics:
|
||||||
hasher typemap.Hasher // cache of type hashes
|
hasher typemap.Hasher // cache of type hashes
|
||||||
|
|
@ -243,7 +245,41 @@ func (a *analysis) labelFor(id nodeid) *Label {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
|
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
|
||||||
a.result.Warnings = append(a.result.Warnings, Warning{pos, fmt.Sprintf(format, args...)})
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
if a.log != nil {
|
||||||
|
fmt.Fprintf(a.log, "%s: warning: %s\n", a.prog.Fset.Position(pos), msg)
|
||||||
|
}
|
||||||
|
a.result.Warnings = append(a.result.Warnings, Warning{pos, msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeTrackBits sets a.track to the necessary 'track' bits for the pointer queries.
|
||||||
|
func (a *analysis) computeTrackBits() {
|
||||||
|
var queryTypes []types.Type
|
||||||
|
for v := range a.config.Queries {
|
||||||
|
queryTypes = append(queryTypes, v.Type())
|
||||||
|
}
|
||||||
|
for v := range a.config.IndirectQueries {
|
||||||
|
queryTypes = append(queryTypes, mustDeref(v.Type()))
|
||||||
|
}
|
||||||
|
for _, t := range queryTypes {
|
||||||
|
switch t.Underlying().(type) {
|
||||||
|
case *types.Chan:
|
||||||
|
a.track |= trackChan
|
||||||
|
case *types.Map:
|
||||||
|
a.track |= trackMap
|
||||||
|
case *types.Pointer:
|
||||||
|
a.track |= trackPtr
|
||||||
|
case *types.Slice:
|
||||||
|
a.track |= trackSlice
|
||||||
|
case *types.Interface:
|
||||||
|
a.track = trackAll
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rVObj := a.reflectValueObj; rVObj != nil && types.Identical(t, rVObj.Type()) {
|
||||||
|
a.track = trackAll
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze runs the pointer analysis with the scope and options
|
// Analyze runs the pointer analysis with the scope and options
|
||||||
|
|
@ -257,13 +293,13 @@ func Analyze(config *Config) *Result {
|
||||||
globalval: make(map[ssa.Value]nodeid),
|
globalval: make(map[ssa.Value]nodeid),
|
||||||
globalobj: make(map[ssa.Value]nodeid),
|
globalobj: make(map[ssa.Value]nodeid),
|
||||||
flattenMemo: make(map[types.Type][]*fieldInfo),
|
flattenMemo: make(map[types.Type][]*fieldInfo),
|
||||||
|
trackTypes: make(map[types.Type]bool),
|
||||||
hasher: typemap.MakeHasher(),
|
hasher: typemap.MakeHasher(),
|
||||||
intrinsics: make(map[*ssa.Function]intrinsic),
|
intrinsics: make(map[*ssa.Function]intrinsic),
|
||||||
work: makeMapWorklist(),
|
work: makeMapWorklist(),
|
||||||
result: &Result{
|
result: &Result{
|
||||||
Queries: make(map[ssa.Value][]Pointer),
|
Queries: make(map[ssa.Value][]Pointer),
|
||||||
IndirectQueries: make(map[ssa.Value][]Pointer),
|
IndirectQueries: make(map[ssa.Value][]Pointer),
|
||||||
PrintCalls: make(map[*ssa.CallCommon]Pointer),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,7 +314,7 @@ func Analyze(config *Config) *Result {
|
||||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||||
rV := reflect.Object.Scope().Lookup("Value")
|
rV := reflect.Object.Scope().Lookup("Value")
|
||||||
a.reflectValueObj = rV
|
a.reflectValueObj = rV
|
||||||
a.reflectValueCall = a.prog.Method(a.prog.MethodSets.MethodSet(rV.Type()).Lookup(nil, "Call"))
|
a.reflectValueCall = a.prog.LookupMethod(rV.Type(), nil, "Call")
|
||||||
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
|
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
|
||||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||||
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
||||||
|
|
@ -287,12 +323,18 @@ func Analyze(config *Config) *Result {
|
||||||
tReflectValue := a.reflectValueObj.Type()
|
tReflectValue := a.reflectValueObj.Type()
|
||||||
a.flattenMemo[tReflectValue] = []*fieldInfo{{typ: tReflectValue}}
|
a.flattenMemo[tReflectValue] = []*fieldInfo{{typ: tReflectValue}}
|
||||||
|
|
||||||
|
// Override shouldTrack of reflect.Value and *reflect.rtype.
|
||||||
|
// Always track pointers of these types.
|
||||||
|
a.trackTypes[tReflectValue] = true
|
||||||
|
a.trackTypes[a.reflectRtypePtr] = true
|
||||||
|
|
||||||
a.rtypes.SetHasher(a.hasher)
|
a.rtypes.SetHasher(a.hasher)
|
||||||
a.reflectZeros.SetHasher(a.hasher)
|
a.reflectZeros.SetHasher(a.hasher)
|
||||||
}
|
}
|
||||||
if runtime := a.prog.ImportedPackage("runtime"); runtime != nil {
|
if runtime := a.prog.ImportedPackage("runtime"); runtime != nil {
|
||||||
a.runtimeSetFinalizer = runtime.Func("SetFinalizer")
|
a.runtimeSetFinalizer = runtime.Func("SetFinalizer")
|
||||||
}
|
}
|
||||||
|
a.computeTrackBits()
|
||||||
|
|
||||||
root := a.generate()
|
root := a.generate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,6 @@ type Config struct {
|
||||||
// If enabled, the graph will be available in Result.CallGraph.
|
// If enabled, the graph will be available in Result.CallGraph.
|
||||||
BuildCallGraph bool
|
BuildCallGraph bool
|
||||||
|
|
||||||
// QueryPrintCalls causes the analysis to record (in
|
|
||||||
// Result.PrintCalls) the points-to set of the first operand
|
|
||||||
// of each discovered call to the built-in print(x), providing
|
|
||||||
// a convenient way to identify arbitrary expressions of
|
|
||||||
// interest in the tests.
|
|
||||||
//
|
|
||||||
QueryPrintCalls bool
|
|
||||||
|
|
||||||
// The client populates Queries[v] or IndirectQueries[v]
|
// The client populates Queries[v] or IndirectQueries[v]
|
||||||
// for each ssa.Value v of interest, to request that the
|
// for each ssa.Value v of interest, to request that the
|
||||||
// points-to sets pts(v) or pts(*v) be computed. If the
|
// points-to sets pts(v) or pts(*v) be computed. If the
|
||||||
|
|
@ -59,13 +51,14 @@ type Config struct {
|
||||||
// context-sensitively, the corresponding Result.{Indirect,}Queries
|
// context-sensitively, the corresponding Result.{Indirect,}Queries
|
||||||
// slice may have multiple Pointers, one per distinct context.
|
// slice may have multiple Pointers, one per distinct context.
|
||||||
// Use PointsToCombined to merge them.
|
// Use PointsToCombined to merge them.
|
||||||
//
|
|
||||||
// TODO(adonovan): this API doesn't scale well for batch tools
|
|
||||||
// that want to dump the entire solution.
|
|
||||||
//
|
|
||||||
// TODO(adonovan): need we distinguish contexts? Current
|
// TODO(adonovan): need we distinguish contexts? Current
|
||||||
// clients always combine them.
|
// clients always combine them.
|
||||||
//
|
//
|
||||||
|
// TODO(adonovan): this API doesn't scale well for batch tools
|
||||||
|
// that want to dump the entire solution. Perhaps optionally
|
||||||
|
// populate a map[*ssa.DebugRef]Pointer in the Result, one
|
||||||
|
// entry per source expression.
|
||||||
|
//
|
||||||
Queries map[ssa.Value]struct{}
|
Queries map[ssa.Value]struct{}
|
||||||
IndirectQueries map[ssa.Value]struct{}
|
IndirectQueries map[ssa.Value]struct{}
|
||||||
|
|
||||||
|
|
@ -74,8 +67,26 @@ type Config struct {
|
||||||
Log io.Writer
|
Log io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type track uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
trackChan track = 1 << iota // track 'chan' references
|
||||||
|
trackMap // track 'map' references
|
||||||
|
trackPtr // track regular pointers
|
||||||
|
trackSlice // track slice references
|
||||||
|
|
||||||
|
trackAll = ^track(0)
|
||||||
|
)
|
||||||
|
|
||||||
// AddQuery adds v to Config.Queries.
|
// AddQuery adds v to Config.Queries.
|
||||||
|
// Precondition: CanPoint(v.Type()).
|
||||||
|
// TODO(adonovan): consider returning a new Pointer for this query,
|
||||||
|
// which will be initialized during analysis. That avoids the needs
|
||||||
|
// for the corresponding ssa.Value-keyed maps in Config and Result.
|
||||||
func (c *Config) AddQuery(v ssa.Value) {
|
func (c *Config) AddQuery(v ssa.Value) {
|
||||||
|
if !CanPoint(v.Type()) {
|
||||||
|
panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type()))
|
||||||
|
}
|
||||||
if c.Queries == nil {
|
if c.Queries == nil {
|
||||||
c.Queries = make(map[ssa.Value]struct{})
|
c.Queries = make(map[ssa.Value]struct{})
|
||||||
}
|
}
|
||||||
|
|
@ -83,10 +94,14 @@ func (c *Config) AddQuery(v ssa.Value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddQuery adds v to Config.IndirectQueries.
|
// AddQuery adds v to Config.IndirectQueries.
|
||||||
|
// Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()).
|
||||||
func (c *Config) AddIndirectQuery(v ssa.Value) {
|
func (c *Config) AddIndirectQuery(v ssa.Value) {
|
||||||
if c.IndirectQueries == nil {
|
if c.IndirectQueries == nil {
|
||||||
c.IndirectQueries = make(map[ssa.Value]struct{})
|
c.IndirectQueries = make(map[ssa.Value]struct{})
|
||||||
}
|
}
|
||||||
|
if !CanPoint(mustDeref(v.Type())) {
|
||||||
|
panic(fmt.Sprintf("%s is not the address of a pointer-like value: %s", v, v.Type()))
|
||||||
|
}
|
||||||
c.IndirectQueries[v] = struct{}{}
|
c.IndirectQueries[v] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,10 +126,9 @@ type Result struct {
|
||||||
Queries map[ssa.Value][]Pointer // pts(v) for each v in Config.Queries.
|
Queries map[ssa.Value][]Pointer // pts(v) for each v in Config.Queries.
|
||||||
IndirectQueries map[ssa.Value][]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
IndirectQueries map[ssa.Value][]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||||
Warnings []Warning // warnings of unsoundness
|
Warnings []Warning // warnings of unsoundness
|
||||||
PrintCalls map[*ssa.CallCommon]Pointer // pts(x) for each call to print(x)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Pointer is an equivalence class of pointerlike values.
|
// A Pointer is an equivalence class of pointer-like values.
|
||||||
//
|
//
|
||||||
// A pointer doesn't have a unique type because pointers of distinct
|
// A pointer doesn't have a unique type because pointers of distinct
|
||||||
// types may alias the same object.
|
// types may alias the same object.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import (
|
||||||
|
|
||||||
// This program demonstrates how to use the pointer analysis to
|
// This program demonstrates how to use the pointer analysis to
|
||||||
// obtain a conservative call-graph of a Go program.
|
// obtain a conservative call-graph of a Go program.
|
||||||
|
// It also shows how to compute the points-to set of a variable,
|
||||||
|
// in this case, (C).f's ch parameter.
|
||||||
//
|
//
|
||||||
func Example() {
|
func Example() {
|
||||||
const myprog = `
|
const myprog = `
|
||||||
|
|
@ -24,18 +26,19 @@ package main
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type I interface {
|
type I interface {
|
||||||
f()
|
f(map[string]int)
|
||||||
}
|
}
|
||||||
|
|
||||||
type C struct{}
|
type C struct{}
|
||||||
|
|
||||||
func (C) f() {
|
func (C) f(m map[string]int) {
|
||||||
fmt.Println("C.f()")
|
fmt.Println("C.f()")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var i I = C{}
|
var i I = C{}
|
||||||
i.f() // dynamic method call
|
x := map[string]int{"one":1}
|
||||||
|
i.f(x) // dynamic method call
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
// Construct a loader.
|
// Construct a loader.
|
||||||
|
|
@ -64,11 +67,18 @@ func main() {
|
||||||
// Build SSA code for bodies of all functions in the whole program.
|
// Build SSA code for bodies of all functions in the whole program.
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
// Run the pointer analysis and build the complete callgraph.
|
// Configure the pointer analysis to build a call-graph.
|
||||||
config := &pointer.Config{
|
config := &pointer.Config{
|
||||||
Mains: []*ssa.Package{mainPkg},
|
Mains: []*ssa.Package{mainPkg},
|
||||||
BuildCallGraph: true,
|
BuildCallGraph: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query points-to set of (C).f's parameter m, a map.
|
||||||
|
C := mainPkg.Type("C").Type()
|
||||||
|
Cfm := prog.LookupMethod(C, mainPkg.Object, "f").Params[1]
|
||||||
|
config.AddQuery(Cfm)
|
||||||
|
|
||||||
|
// Run the pointer analysis.
|
||||||
result := pointer.Analyze(config)
|
result := pointer.Analyze(config)
|
||||||
|
|
||||||
// Find edges originating from the main package.
|
// Find edges originating from the main package.
|
||||||
|
|
@ -88,9 +98,26 @@ func main() {
|
||||||
for _, edge := range edges {
|
for _, edge := range edges {
|
||||||
fmt.Println(edge)
|
fmt.Println(edge)
|
||||||
}
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Print the labels of (C).f(m)'s points-to set.
|
||||||
|
fmt.Println("m may point to:")
|
||||||
|
ptset := pointer.PointsToCombined(result.Queries[Cfm])
|
||||||
|
var labels []string
|
||||||
|
for _, l := range ptset.Labels() {
|
||||||
|
label := fmt.Sprintf(" %s: %s", prog.Fset.Position(l.Pos()), l)
|
||||||
|
labels = append(labels, label)
|
||||||
|
}
|
||||||
|
sort.Strings(labels)
|
||||||
|
for _, label := range labels {
|
||||||
|
fmt.Println(label)
|
||||||
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// (main.C).f --> fmt.Println
|
// (main.C).f --> fmt.Println
|
||||||
// main.init --> fmt.init
|
// main.init --> fmt.init
|
||||||
// main.main --> (main.C).f
|
// main.main --> (main.C).f
|
||||||
|
//
|
||||||
|
// m may point to:
|
||||||
|
// myprog.go:18:21: makemap
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,18 @@ func (a *analysis) setValueNode(v ssa.Value, id nodeid, cgn *cgnode) {
|
||||||
fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v)
|
fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): due to context-sensitivity, we may
|
||||||
|
// encounter the same Value in many contexts. In a follow-up,
|
||||||
|
// let's merge them to a canonical node, since that's what all
|
||||||
|
// clients want.
|
||||||
|
// ptr, ok := a.result.Queries[v]
|
||||||
|
// if !ok {
|
||||||
|
// // First time? Create the canonical probe node.
|
||||||
|
// ptr = Pointer{a, nil, a.addNodes(t, "query")}
|
||||||
|
// a.result.Queries[v] = ptr
|
||||||
|
// }
|
||||||
|
// a.copy(ptr.n, id, a.sizeof(v.Type()))
|
||||||
|
|
||||||
// Record the (v, id) relation if the client has queried pts(v).
|
// Record the (v, id) relation if the client has queried pts(v).
|
||||||
if _, ok := a.config.Queries[v]; ok {
|
if _, ok := a.config.Queries[v]; ok {
|
||||||
a.result.Queries[v] = append(a.result.Queries[v], Pointer{a, cgn, id})
|
a.result.Queries[v] = append(a.result.Queries[v], Pointer{a, cgn, id})
|
||||||
|
|
@ -173,7 +185,7 @@ func (a *analysis) makeRtype(T types.Type) nodeid {
|
||||||
|
|
||||||
id := a.makeTagged(a.reflectRtypePtr, nil, T)
|
id := a.makeTagged(a.reflectRtypePtr, nil, T)
|
||||||
a.nodes[id+1].typ = T // trick (each *rtype tagged object is a singleton)
|
a.nodes[id+1].typ = T // trick (each *rtype tagged object is a singleton)
|
||||||
a.addressOf(id+1, obj)
|
a.addressOf(a.reflectRtypePtr, id+1, obj)
|
||||||
|
|
||||||
a.rtypes.Set(T, id)
|
a.rtypes.Set(T, id)
|
||||||
return id
|
return id
|
||||||
|
|
@ -207,7 +219,7 @@ func (a *analysis) valueNode(v ssa.Value) nodeid {
|
||||||
}
|
}
|
||||||
id = a.addOneNode(v.Type(), comment, nil)
|
id = a.addOneNode(v.Type(), comment, nil)
|
||||||
if obj := a.objectNode(nil, v); obj != 0 {
|
if obj := a.objectNode(nil, v); obj != 0 {
|
||||||
a.addressOf(id, obj)
|
a.addressOf(v.Type(), id, obj)
|
||||||
}
|
}
|
||||||
a.setValueNode(v, id, nil)
|
a.setValueNode(v, id, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -290,15 +302,18 @@ func (a *analysis) copy(dst, src nodeid, sizeof uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addressOf creates a constraint of the form id = &obj.
|
// addressOf creates a constraint of the form id = &obj.
|
||||||
func (a *analysis) addressOf(id, obj nodeid) {
|
// T is the type of the address.
|
||||||
|
func (a *analysis) addressOf(T types.Type, id, obj nodeid) {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
panic("addressOf: zero id")
|
panic("addressOf: zero id")
|
||||||
}
|
}
|
||||||
if obj == 0 {
|
if obj == 0 {
|
||||||
panic("addressOf: zero obj")
|
panic("addressOf: zero obj")
|
||||||
}
|
}
|
||||||
|
if a.shouldTrack(T) {
|
||||||
a.addConstraint(&addrConstraint{id, obj})
|
a.addConstraint(&addrConstraint{id, obj})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// load creates a load constraint of the form dst = src[offset].
|
// load creates a load constraint of the form dst = src[offset].
|
||||||
// offset is the pointer offset in logical fields.
|
// offset is the pointer offset in logical fields.
|
||||||
|
|
@ -344,8 +359,12 @@ func (a *analysis) store(dst, src nodeid, offset uint32, sizeof uint32) {
|
||||||
|
|
||||||
// offsetAddr creates an offsetAddr constraint of the form dst = &src.#offset.
|
// offsetAddr creates an offsetAddr constraint of the form dst = &src.#offset.
|
||||||
// offset is the field offset in logical fields.
|
// offset is the field offset in logical fields.
|
||||||
|
// T is the type of the address.
|
||||||
//
|
//
|
||||||
func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) {
|
func (a *analysis) offsetAddr(T types.Type, dst, src nodeid, offset uint32) {
|
||||||
|
if !a.shouldTrack(T) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
// Simplify dst = &src->f0
|
// Simplify dst = &src->f0
|
||||||
// to dst = src
|
// to dst = src
|
||||||
|
|
@ -432,7 +451,7 @@ func (a *analysis) genConv(conv *ssa.Convert, cgn *cgnode) {
|
||||||
// unsafe conversions soundly; see TODO file.
|
// unsafe conversions soundly; see TODO file.
|
||||||
obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion")
|
obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion")
|
||||||
a.endObject(obj, cgn, conv)
|
a.endObject(obj, cgn, conv)
|
||||||
a.addressOf(res, obj)
|
a.addressOf(tDst, res, obj)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,7 +460,7 @@ func (a *analysis) genConv(conv *ssa.Convert, cgn *cgnode) {
|
||||||
if utSrc.Info()&types.IsString != 0 {
|
if utSrc.Info()&types.IsString != 0 {
|
||||||
obj := a.addNodes(sliceToArray(tDst), "convert")
|
obj := a.addNodes(sliceToArray(tDst), "convert")
|
||||||
a.endObject(obj, cgn, conv)
|
a.endObject(obj, cgn, conv)
|
||||||
a.addressOf(res, obj)
|
a.addressOf(tDst, res, obj)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,7 +519,7 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) {
|
||||||
a.endObject(w, cgn, instr)
|
a.endObject(w, cgn, instr)
|
||||||
|
|
||||||
a.copyElems(cgn, tArray.Elem(), z, y) // *z = *y
|
a.copyElems(cgn, tArray.Elem(), z, y) // *z = *y
|
||||||
a.addressOf(a.valueNode(z), w) // z = &w
|
a.addressOf(instr.Type(), a.valueNode(z), w) // z = &w
|
||||||
}
|
}
|
||||||
|
|
||||||
// genBuiltinCall generates contraints for a call to a built-in.
|
// genBuiltinCall generates contraints for a call to a built-in.
|
||||||
|
|
@ -523,29 +542,8 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
|
||||||
a.copy(a.valueNode(v), a.panicNode, 1)
|
a.copy(a.valueNode(v), a.panicNode, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "print":
|
|
||||||
// Analytically print is a no-op, but it's a convenient hook
|
|
||||||
// for testing the pts of an expression, so we notify the client.
|
|
||||||
// Existing uses in Go core libraries are few and harmless.
|
|
||||||
if a.config.QueryPrintCalls {
|
|
||||||
// Due to context-sensitivity, we may encounter
|
|
||||||
// the same print() call in many contexts, so
|
|
||||||
// we merge them to a canonical node.
|
|
||||||
|
|
||||||
t := call.Args[0].Type()
|
|
||||||
|
|
||||||
ptr, ok := a.result.PrintCalls[call]
|
|
||||||
if !ok {
|
|
||||||
// First time? Create the canonical probe node.
|
|
||||||
ptr = Pointer{a, nil, a.addNodes(t, "print")}
|
|
||||||
a.result.PrintCalls[call] = ptr
|
|
||||||
}
|
|
||||||
probe := ptr.n
|
|
||||||
a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// No-ops: close len cap real imag complex println delete.
|
// No-ops: close len cap real imag complex print println delete.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,7 +603,7 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC
|
||||||
dotdotdot := false
|
dotdotdot := false
|
||||||
ret := reflectCallImpl(a, caller, site, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]), dotdotdot)
|
ret := reflectCallImpl(a, caller, site, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]), dotdotdot)
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
a.addressOf(result, ret)
|
a.addressOf(fn.Signature.Results().At(0).Type(), result, ret)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -720,8 +718,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
||||||
a.typeAssert(a.reflectRtypePtr, rtype, recv, true)
|
a.typeAssert(a.reflectRtypePtr, rtype, recv, true)
|
||||||
|
|
||||||
// Look up the concrete method.
|
// Look up the concrete method.
|
||||||
meth := a.prog.MethodSets.MethodSet(a.reflectRtypePtr).Lookup(call.Method.Pkg(), call.Method.Name())
|
fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name())
|
||||||
fn := a.prog.Method(meth)
|
|
||||||
|
|
||||||
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
||||||
a.callEdge(site, obj)
|
a.callEdge(site, obj)
|
||||||
|
|
@ -731,7 +728,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
||||||
|
|
||||||
sig := fn.Signature // concrete method
|
sig := fn.Signature // concrete method
|
||||||
targets := a.addOneNode(sig, "call.targets", nil)
|
targets := a.addOneNode(sig, "call.targets", nil)
|
||||||
a.addressOf(targets, obj) // (a singleton)
|
a.addressOf(sig, targets, obj) // (a singleton)
|
||||||
|
|
||||||
// Copy receiver.
|
// Copy receiver.
|
||||||
params := a.funcParams(obj)
|
params := a.funcParams(obj)
|
||||||
|
|
@ -814,12 +811,7 @@ func (a *analysis) objectNode(cgn *cgnode, v ssa.Value) nodeid {
|
||||||
obj = a.makeFunctionObject(v, nil)
|
obj = a.makeFunctionObject(v, nil)
|
||||||
|
|
||||||
case *ssa.Const:
|
case *ssa.Const:
|
||||||
if t, ok := v.Type().Underlying().(*types.Slice); ok && !v.IsNil() {
|
// The only pointer-like Consts are nil.
|
||||||
// Non-nil []byte or []rune constant.
|
|
||||||
obj = a.nextNode()
|
|
||||||
a.addNodes(sliceToArray(t), "array in slice constant")
|
|
||||||
a.endObject(obj, nil, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ssa.Capture:
|
case *ssa.Capture:
|
||||||
// For now, Captures have the same cardinality as globals.
|
// For now, Captures have the same cardinality as globals.
|
||||||
|
|
@ -912,9 +904,9 @@ func (a *analysis) genOffsetAddr(cgn *cgnode, v ssa.Value, ptr nodeid, offset ui
|
||||||
dst := a.valueNode(v)
|
dst := a.valueNode(v)
|
||||||
if obj := a.objectNode(cgn, v); obj != 0 {
|
if obj := a.objectNode(cgn, v); obj != 0 {
|
||||||
// Pre-apply offsetAddrConstraint.solve().
|
// Pre-apply offsetAddrConstraint.solve().
|
||||||
a.addressOf(dst, obj)
|
a.addressOf(v.Type(), dst, obj)
|
||||||
} else {
|
} else {
|
||||||
a.offsetAddr(dst, ptr, offset)
|
a.offsetAddr(v.Type(), dst, ptr, offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1018,7 +1010,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
|
||||||
|
|
||||||
case *ssa.Alloc, *ssa.MakeSlice, *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeInterface:
|
case *ssa.Alloc, *ssa.MakeSlice, *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeInterface:
|
||||||
v := instr.(ssa.Value)
|
v := instr.(ssa.Value)
|
||||||
a.addressOf(a.valueNode(v), a.objectNode(cgn, v))
|
a.addressOf(v.Type(), a.valueNode(v), a.objectNode(cgn, v))
|
||||||
|
|
||||||
case *ssa.ChangeInterface:
|
case *ssa.ChangeInterface:
|
||||||
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
|
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
|
||||||
|
|
@ -1251,9 +1243,10 @@ func (a *analysis) generate() *cgnode {
|
||||||
// The runtime magically allocates os.Args; so should we.
|
// The runtime magically allocates os.Args; so should we.
|
||||||
if os := a.prog.ImportedPackage("os"); os != nil {
|
if os := a.prog.ImportedPackage("os"); os != nil {
|
||||||
// In effect: os.Args = new([1]string)[:]
|
// In effect: os.Args = new([1]string)[:]
|
||||||
obj := a.addNodes(types.NewArray(types.Typ[types.String], 1), "<command-line args>")
|
T := types.NewSlice(types.Typ[types.String])
|
||||||
|
obj := a.addNodes(sliceToArray(T), "<command-line args>")
|
||||||
a.endObject(obj, nil, "<command-line args>")
|
a.endObject(obj, nil, "<command-line args>")
|
||||||
a.addressOf(a.objectNode(nil, os.Var("Args")), obj)
|
a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ func init() {
|
||||||
"crypto/md5.block": ext۰NoEffect,
|
"crypto/md5.block": ext۰NoEffect,
|
||||||
"crypto/rc4.xorKeyStream": ext۰NoEffect,
|
"crypto/rc4.xorKeyStream": ext۰NoEffect,
|
||||||
"crypto/sha1.block": ext۰NoEffect,
|
"crypto/sha1.block": ext۰NoEffect,
|
||||||
|
"crypto/sha256.block": ext۰NoEffect,
|
||||||
"hash/crc32.castagnoliSSE42": ext۰NoEffect,
|
"hash/crc32.castagnoliSSE42": ext۰NoEffect,
|
||||||
"hash/crc32.haveSSE42": ext۰NoEffect,
|
"hash/crc32.haveSSE42": ext۰NoEffect,
|
||||||
"math.Abs": ext۰NoEffect,
|
"math.Abs": ext۰NoEffect,
|
||||||
|
|
@ -214,11 +215,13 @@ func init() {
|
||||||
"net.runtime_pollSetDeadline": ext۰NoEffect,
|
"net.runtime_pollSetDeadline": ext۰NoEffect,
|
||||||
"net.runtime_pollUnblock": ext۰NoEffect,
|
"net.runtime_pollUnblock": ext۰NoEffect,
|
||||||
"net.runtime_pollWait": ext۰NoEffect,
|
"net.runtime_pollWait": ext۰NoEffect,
|
||||||
|
"net.runtime_pollWaitCanceled": ext۰NoEffect,
|
||||||
"os.epipecheck": ext۰NoEffect,
|
"os.epipecheck": ext۰NoEffect,
|
||||||
"runtime.BlockProfile": ext۰NoEffect,
|
"runtime.BlockProfile": ext۰NoEffect,
|
||||||
"runtime.Breakpoint": ext۰NoEffect,
|
"runtime.Breakpoint": ext۰NoEffect,
|
||||||
"runtime.CPUProfile": ext۰NotYetImplemented,
|
"runtime.CPUProfile": ext۰NoEffect, // good enough
|
||||||
"runtime.Caller": ext۰NoEffect,
|
"runtime.Caller": ext۰NoEffect,
|
||||||
|
"runtime.Callers": ext۰NoEffect, // good enough
|
||||||
"runtime.FuncForPC": ext۰NoEffect,
|
"runtime.FuncForPC": ext۰NoEffect,
|
||||||
"runtime.GC": ext۰NoEffect,
|
"runtime.GC": ext۰NoEffect,
|
||||||
"runtime.GOMAXPROCS": ext۰NoEffect,
|
"runtime.GOMAXPROCS": ext۰NoEffect,
|
||||||
|
|
@ -234,6 +237,7 @@ func init() {
|
||||||
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
||||||
"runtime.Stack": ext۰NoEffect,
|
"runtime.Stack": ext۰NoEffect,
|
||||||
"runtime.ThreadCreateProfile": ext۰NoEffect,
|
"runtime.ThreadCreateProfile": ext۰NoEffect,
|
||||||
|
"runtime.cstringToGo": ext۰NoEffect,
|
||||||
"runtime.funcentry_go": ext۰NoEffect,
|
"runtime.funcentry_go": ext۰NoEffect,
|
||||||
"runtime.funcline_go": ext۰NoEffect,
|
"runtime.funcline_go": ext۰NoEffect,
|
||||||
"runtime.funcname_go": ext۰NoEffect,
|
"runtime.funcname_go": ext۰NoEffect,
|
||||||
|
|
@ -245,17 +249,28 @@ func init() {
|
||||||
"sync.runtime_Syncsemacquire": ext۰NoEffect,
|
"sync.runtime_Syncsemacquire": ext۰NoEffect,
|
||||||
"sync.runtime_Syncsemcheck": ext۰NoEffect,
|
"sync.runtime_Syncsemcheck": ext۰NoEffect,
|
||||||
"sync.runtime_Syncsemrelease": ext۰NoEffect,
|
"sync.runtime_Syncsemrelease": ext۰NoEffect,
|
||||||
|
"sync.runtime_procPin": ext۰NoEffect,
|
||||||
|
"sync.runtime_procUnpin": ext۰NoEffect,
|
||||||
|
"sync.runtime_registerPool": ext۰NoEffect,
|
||||||
"sync/atomic.AddInt32": ext۰NoEffect,
|
"sync/atomic.AddInt32": ext۰NoEffect,
|
||||||
|
"sync/atomic.AddInt64": ext۰NoEffect,
|
||||||
"sync/atomic.AddUint32": ext۰NoEffect,
|
"sync/atomic.AddUint32": ext۰NoEffect,
|
||||||
|
"sync/atomic.AddUint64": ext۰NoEffect,
|
||||||
|
"sync/atomic.AddUintptr": ext۰NoEffect,
|
||||||
"sync/atomic.CompareAndSwapInt32": ext۰NoEffect,
|
"sync/atomic.CompareAndSwapInt32": ext۰NoEffect,
|
||||||
"sync/atomic.CompareAndSwapUint32": ext۰NoEffect,
|
"sync/atomic.CompareAndSwapUint32": ext۰NoEffect,
|
||||||
"sync/atomic.CompareAndSwapUint64": ext۰NoEffect,
|
"sync/atomic.CompareAndSwapUint64": ext۰NoEffect,
|
||||||
"sync/atomic.CompareAndSwapUintptr": ext۰NoEffect,
|
"sync/atomic.CompareAndSwapUintptr": ext۰NoEffect,
|
||||||
"sync/atomic.LoadInt32": ext۰NoEffect,
|
"sync/atomic.LoadInt32": ext۰NoEffect,
|
||||||
|
"sync/atomic.LoadInt64": ext۰NoEffect,
|
||||||
|
"sync/atomic.LoadPointer": ext۰NoEffect, // ignore unsafe.Pointer for now
|
||||||
"sync/atomic.LoadUint32": ext۰NoEffect,
|
"sync/atomic.LoadUint32": ext۰NoEffect,
|
||||||
"sync/atomic.LoadUint64": ext۰NoEffect,
|
"sync/atomic.LoadUint64": ext۰NoEffect,
|
||||||
|
"sync/atomic.LoadUintptr": ext۰NoEffect,
|
||||||
"sync/atomic.StoreInt32": ext۰NoEffect,
|
"sync/atomic.StoreInt32": ext۰NoEffect,
|
||||||
|
"sync/atomic.StorePointer": ext۰NoEffect, // ignore unsafe.Pointer for now
|
||||||
"sync/atomic.StoreUint32": ext۰NoEffect,
|
"sync/atomic.StoreUint32": ext۰NoEffect,
|
||||||
|
"sync/atomic.StoreUintptr": ext۰NoEffect,
|
||||||
"syscall.Close": ext۰NoEffect,
|
"syscall.Close": ext۰NoEffect,
|
||||||
"syscall.Exit": ext۰NoEffect,
|
"syscall.Exit": ext۰NoEffect,
|
||||||
"syscall.Getpid": ext۰NoEffect,
|
"syscall.Getpid": ext۰NoEffect,
|
||||||
|
|
@ -267,6 +282,7 @@ func init() {
|
||||||
"syscall.Syscall6": ext۰NoEffect,
|
"syscall.Syscall6": ext۰NoEffect,
|
||||||
"syscall.runtime_AfterFork": ext۰NoEffect,
|
"syscall.runtime_AfterFork": ext۰NoEffect,
|
||||||
"syscall.runtime_BeforeFork": ext۰NoEffect,
|
"syscall.runtime_BeforeFork": ext۰NoEffect,
|
||||||
|
"syscall.setenv_c": ext۰NoEffect,
|
||||||
"time.Sleep": ext۰NoEffect,
|
"time.Sleep": ext۰NoEffect,
|
||||||
"time.now": ext۰NoEffect,
|
"time.now": ext۰NoEffect,
|
||||||
"time.startTimer": ext۰NoEffect,
|
"time.startTimer": ext۰NoEffect,
|
||||||
|
|
@ -284,7 +300,7 @@ func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
|
||||||
if !ok {
|
if !ok {
|
||||||
impl = intrinsicsByName[fn.String()] // may be nil
|
impl = intrinsicsByName[fn.String()] // may be nil
|
||||||
|
|
||||||
if fn.Pkg != nil && a.reflectValueObj != nil && a.reflectValueObj.Pkg() == fn.Pkg.Object {
|
if a.isReflect(fn) {
|
||||||
if !a.config.Reflection {
|
if !a.config.Reflection {
|
||||||
impl = ext۰NoEffect // reflection disabled
|
impl = ext۰NoEffect // reflection disabled
|
||||||
} else if impl == nil {
|
} else if impl == nil {
|
||||||
|
|
@ -298,6 +314,28 @@ func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
|
||||||
return impl
|
return impl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isReflect reports whether fn belongs to the "reflect" package.
|
||||||
|
func (a *analysis) isReflect(fn *ssa.Function) bool {
|
||||||
|
if a.reflectValueObj == nil {
|
||||||
|
return false // "reflect" package not loaded
|
||||||
|
}
|
||||||
|
reflectPackage := a.reflectValueObj.Pkg()
|
||||||
|
if fn.Pkg != nil && fn.Pkg.Object == reflectPackage {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Synthetic wrappers have a nil Pkg, so they slip through the
|
||||||
|
// previous check. Check the receiver package.
|
||||||
|
// TODO(adonovan): should synthetic wrappers have a non-nil Pkg?
|
||||||
|
if recv := fn.Signature.Recv(); recv != nil {
|
||||||
|
if named, ok := deref(recv.Type()).(*types.Named); ok {
|
||||||
|
if named.Obj().Pkg() == reflectPackage {
|
||||||
|
return true // e.g. wrapper of (reflect.Value).f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// A trivial intrinsic suitable for any function that does not:
|
// A trivial intrinsic suitable for any function that does not:
|
||||||
// 1) induce aliases between its arguments or any global variables;
|
// 1) induce aliases between its arguments or any global variables;
|
||||||
// 2) call any functions; or
|
// 2) call any functions; or
|
||||||
|
|
@ -316,7 +354,10 @@ func ext۰NoEffect(a *analysis, cgn *cgnode) {}
|
||||||
func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
||||||
// TODO(adonovan): enable this warning when we've implemented
|
// TODO(adonovan): enable this warning when we've implemented
|
||||||
// enough that it's not unbearably annoying.
|
// enough that it's not unbearably annoying.
|
||||||
// a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
if true {
|
||||||
|
fn := cgn.Func()
|
||||||
|
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- func runtime.SetFinalizer(x, f interface{}) ----------
|
// ---------- func runtime.SetFinalizer(x, f interface{}) ----------
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"code.google.com/p/go.tools/go/loader"
|
"code.google.com/p/go.tools/go/loader"
|
||||||
"code.google.com/p/go.tools/go/pointer"
|
"code.google.com/p/go.tools/go/pointer"
|
||||||
"code.google.com/p/go.tools/go/ssa"
|
"code.google.com/p/go.tools/go/ssa"
|
||||||
|
"code.google.com/p/go.tools/go/ssa/ssautil"
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
"code.google.com/p/go.tools/go/types/typemap"
|
"code.google.com/p/go.tools/go/types/typemap"
|
||||||
)
|
)
|
||||||
|
|
@ -135,16 +136,15 @@ func (e *expectation) needsProbe() bool {
|
||||||
return e.kind == "pointsto" || e.kind == "types"
|
return e.kind == "pointsto" || e.kind == "types"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find probe (call to print(x)) of same source
|
// Find probe (call to print(x)) of same source file/line as expectation.
|
||||||
// file/line as expectation.
|
func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]bool, queries map[ssa.Value][]pointer.Pointer, e *expectation) (site *ssa.CallCommon, pts pointer.PointsToSet) {
|
||||||
func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e *expectation) (site *ssa.CallCommon, ptr pointer.Pointer) {
|
for call := range probes {
|
||||||
for call, ptr := range probes {
|
|
||||||
pos := prog.Fset.Position(call.Pos())
|
pos := prog.Fset.Position(call.Pos())
|
||||||
if pos.Line == e.linenum && pos.Filename == e.filename {
|
if pos.Line == e.linenum && pos.Filename == e.filename {
|
||||||
// TODO(adonovan): send this to test log (display only on failure).
|
// TODO(adonovan): send this to test log (display only on failure).
|
||||||
// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
|
// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
|
||||||
// e.filename, e.linenum, e, p.arg0) // debugging
|
// e.filename, e.linenum, e, p.arg0) // debugging
|
||||||
return call, ptr
|
return call, pointer.PointsToCombined(queries[call.Args[0]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return // e.g. analysis didn't reach this call
|
return // e.g. analysis didn't reach this call
|
||||||
|
|
@ -167,18 +167,37 @@ func doOneInput(input, filename string) bool {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
mainPkgInfo := iprog.Created[0].Pkg
|
||||||
|
|
||||||
// SSA creation + building.
|
// SSA creation + building.
|
||||||
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
mainpkg := prog.Package(iprog.Created[0].Pkg)
|
mainpkg := prog.Package(mainPkgInfo)
|
||||||
ptrmain := mainpkg // main package for the pointer analysis
|
ptrmain := mainpkg // main package for the pointer analysis
|
||||||
if mainpkg.Func("main") == nil {
|
if mainpkg.Func("main") == nil {
|
||||||
// No main function; assume it's a test.
|
// No main function; assume it's a test.
|
||||||
ptrmain = prog.CreateTestMainPackage(mainpkg)
|
ptrmain = prog.CreateTestMainPackage(mainpkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find all calls to the built-in print(x). Analytically,
|
||||||
|
// print is a no-op, but it's a convenient hook for testing
|
||||||
|
// the PTS of an expression, so our tests use it.
|
||||||
|
probes := make(map[*ssa.CallCommon]bool)
|
||||||
|
for fn := range ssautil.AllFunctions(prog) {
|
||||||
|
if fn.Pkg == mainpkg {
|
||||||
|
for _, b := range fn.Blocks {
|
||||||
|
for _, instr := range b.Instrs {
|
||||||
|
if instr, ok := instr.(ssa.CallInstruction); ok {
|
||||||
|
if b, ok := instr.Common().Value.(*ssa.Builtin); ok && b.Name() == "print" {
|
||||||
|
probes[instr.Common()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ok := true
|
ok := true
|
||||||
|
|
||||||
lineMapping := make(map[string]string) // maps "file:line" to @line tag
|
lineMapping := make(map[string]string) // maps "file:line" to @line tag
|
||||||
|
|
@ -273,10 +292,12 @@ func doOneInput(input, filename string) bool {
|
||||||
config := &pointer.Config{
|
config := &pointer.Config{
|
||||||
Reflection: true,
|
Reflection: true,
|
||||||
BuildCallGraph: true,
|
BuildCallGraph: true,
|
||||||
QueryPrintCalls: true,
|
|
||||||
Mains: []*ssa.Package{ptrmain},
|
Mains: []*ssa.Package{ptrmain},
|
||||||
Log: &log,
|
Log: &log,
|
||||||
}
|
}
|
||||||
|
for probe := range probes {
|
||||||
|
config.AddQuery(probe.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
// Print the log is there was an error or a panic.
|
// Print the log is there was an error or a panic.
|
||||||
complete := false
|
complete := false
|
||||||
|
|
@ -291,10 +312,10 @@ func doOneInput(input, filename string) bool {
|
||||||
// Check the expectations.
|
// Check the expectations.
|
||||||
for _, e := range exps {
|
for _, e := range exps {
|
||||||
var call *ssa.CallCommon
|
var call *ssa.CallCommon
|
||||||
var ptr pointer.Pointer
|
var pts pointer.PointsToSet
|
||||||
var tProbe types.Type
|
var tProbe types.Type
|
||||||
if e.needsProbe() {
|
if e.needsProbe() {
|
||||||
if call, ptr = findProbe(prog, result.PrintCalls, e); call == nil {
|
if call, pts = findProbe(prog, probes, result.Queries, e); call == nil {
|
||||||
ok = false
|
ok = false
|
||||||
e.errorf("unreachable print() statement has expectation %s", e)
|
e.errorf("unreachable print() statement has expectation %s", e)
|
||||||
continue
|
continue
|
||||||
|
|
@ -309,12 +330,12 @@ func doOneInput(input, filename string) bool {
|
||||||
|
|
||||||
switch e.kind {
|
switch e.kind {
|
||||||
case "pointsto":
|
case "pointsto":
|
||||||
if !checkPointsToExpectation(e, ptr, lineMapping, prog) {
|
if !checkPointsToExpectation(e, pts, lineMapping, prog) {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
|
|
||||||
case "types":
|
case "types":
|
||||||
if !checkTypesExpectation(e, ptr, tProbe) {
|
if !checkTypesExpectation(e, pts, tProbe) {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,7 +380,7 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPointsToExpectation(e *expectation, ptr pointer.Pointer, lineMapping map[string]string, prog *ssa.Program) bool {
|
func checkPointsToExpectation(e *expectation, pts pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool {
|
||||||
expected := make(map[string]int)
|
expected := make(map[string]int)
|
||||||
surplus := make(map[string]int)
|
surplus := make(map[string]int)
|
||||||
exact := true
|
exact := true
|
||||||
|
|
@ -372,7 +393,7 @@ func checkPointsToExpectation(e *expectation, ptr pointer.Pointer, lineMapping m
|
||||||
}
|
}
|
||||||
// Find the set of labels that the probe's
|
// Find the set of labels that the probe's
|
||||||
// argument (x in print(x)) may point to.
|
// argument (x in print(x)) may point to.
|
||||||
for _, label := range ptr.PointsTo().Labels() {
|
for _, label := range pts.Labels() {
|
||||||
name := labelString(label, lineMapping, prog)
|
name := labelString(label, lineMapping, prog)
|
||||||
if expected[name] > 0 {
|
if expected[name] > 0 {
|
||||||
expected[name]--
|
expected[name]--
|
||||||
|
|
@ -410,7 +431,7 @@ func underlyingType(typ types.Type) types.Type {
|
||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTypesExpectation(e *expectation, ptr pointer.Pointer, typ types.Type) bool {
|
func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Type) bool {
|
||||||
var expected typemap.M
|
var expected typemap.M
|
||||||
var surplus typemap.M
|
var surplus typemap.M
|
||||||
exact := true
|
exact := true
|
||||||
|
|
@ -429,7 +450,7 @@ func checkTypesExpectation(e *expectation, ptr pointer.Pointer, typ types.Type)
|
||||||
|
|
||||||
// Find the set of types that the probe's
|
// Find the set of types that the probe's
|
||||||
// argument (x in print(x)) may contain.
|
// argument (x in print(x)) may contain.
|
||||||
for _, T := range ptr.PointsTo().DynamicTypes().Keys() {
|
for _, T := range pts.DynamicTypes().Keys() {
|
||||||
if expected.At(T) != nil {
|
if expected.At(T) != nil {
|
||||||
expected.Delete(T)
|
expected.Delete(T)
|
||||||
} else if exact {
|
} else if exact {
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ func reflectCall(a *analysis, cgn *cgnode, dotdotdot bool) {
|
||||||
recv := a.funcParams(cgn.obj)
|
recv := a.funcParams(cgn.obj)
|
||||||
arg := recv + 1
|
arg := recv + 1
|
||||||
ret := reflectCallImpl(a, cgn, site, recv, arg, dotdotdot)
|
ret := reflectCallImpl(a, cgn, site, recv, arg, dotdotdot)
|
||||||
a.addressOf(a.funcResults(cgn.obj), ret)
|
a.addressOf(cgn.fn.Signature.Results().At(0).Type(), a.funcResults(cgn.obj), ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ext۰reflect۰Value۰Call(a *analysis, cgn *cgnode) {
|
func ext۰reflect۰Value۰Call(a *analysis, cgn *cgnode) {
|
||||||
|
|
@ -484,9 +484,10 @@ func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) {
|
||||||
func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) {
|
func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) {
|
||||||
// Allocate an array for the result.
|
// Allocate an array for the result.
|
||||||
obj := a.nextNode()
|
obj := a.nextNode()
|
||||||
a.addNodes(types.NewArray(a.reflectValueObj.Type(), 1), "reflect.MapKeys result")
|
T := types.NewSlice(a.reflectValueObj.Type())
|
||||||
|
a.addNodes(sliceToArray(T), "reflect.MapKeys result")
|
||||||
a.endObject(obj, cgn, nil)
|
a.endObject(obj, cgn, nil)
|
||||||
a.addressOf(a.funcResults(cgn.obj), obj)
|
a.addressOf(T, a.funcResults(cgn.obj), obj)
|
||||||
|
|
||||||
a.addConstraint(&rVMapKeysConstraint{
|
a.addConstraint(&rVMapKeysConstraint{
|
||||||
cgn: cgn,
|
cgn: cgn,
|
||||||
|
|
|
||||||
|
|
@ -312,14 +312,9 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the concrete method.
|
// Look up the concrete method.
|
||||||
meth := a.prog.MethodSets.MethodSet(tDyn).Lookup(c.method.Pkg(), c.method.Name())
|
fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name())
|
||||||
if meth == nil {
|
|
||||||
panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)",
|
|
||||||
c.iface, tDyn, c.method, ifaceObj))
|
|
||||||
}
|
|
||||||
fn := a.prog.Method(meth)
|
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, meth))
|
panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, c.method))
|
||||||
}
|
}
|
||||||
sig := fn.Signature
|
sig := fn.Signature
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,48 @@ func (a *analysis) sizeof(t types.Type) uint32 {
|
||||||
return uint32(len(a.flatten(t)))
|
return uint32(len(a.flatten(t)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldTrack reports whether object type T contains (recursively)
|
||||||
|
// any fields whose addresses should be tracked.
|
||||||
|
func (a *analysis) shouldTrack(T types.Type) bool {
|
||||||
|
if a.track == trackAll {
|
||||||
|
return true // fast path
|
||||||
|
}
|
||||||
|
track, ok := a.trackTypes[T]
|
||||||
|
if !ok {
|
||||||
|
a.trackTypes[T] = true // break cycles conservatively
|
||||||
|
// NB: reflect.Value, reflect.Type are pre-populated to true.
|
||||||
|
for _, fi := range a.flatten(T) {
|
||||||
|
switch ft := fi.typ.Underlying().(type) {
|
||||||
|
case *types.Interface, *types.Signature:
|
||||||
|
track = true // needed for callgraph
|
||||||
|
case *types.Basic:
|
||||||
|
// no-op
|
||||||
|
case *types.Chan:
|
||||||
|
track = a.track&trackChan != 0 || a.shouldTrack(ft.Elem())
|
||||||
|
case *types.Map:
|
||||||
|
track = a.track&trackMap != 0 || a.shouldTrack(ft.Key()) || a.shouldTrack(ft.Elem())
|
||||||
|
case *types.Slice:
|
||||||
|
track = a.track&trackSlice != 0 || a.shouldTrack(ft.Elem())
|
||||||
|
case *types.Pointer:
|
||||||
|
track = a.track&trackPtr != 0 || a.shouldTrack(ft.Elem())
|
||||||
|
case *types.Array, *types.Struct:
|
||||||
|
// No need to look at field types since they will follow (flattened).
|
||||||
|
default:
|
||||||
|
// Includes *types.Tuple, which are never address-taken.
|
||||||
|
panic(ft)
|
||||||
|
}
|
||||||
|
if track {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.trackTypes[T] = track
|
||||||
|
if !track && a.log != nil {
|
||||||
|
fmt.Fprintf(a.log, "Type not tracked: %s\n", T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
// offsetOf returns the (abstract) offset of field index within struct
|
// offsetOf returns the (abstract) offset of field index within struct
|
||||||
// or tuple typ.
|
// or tuple typ.
|
||||||
func (a *analysis) offsetOf(typ types.Type, index int) uint32 {
|
func (a *analysis) offsetOf(typ types.Type, index int) uint32 {
|
||||||
|
|
|
||||||
|
|
@ -177,11 +177,7 @@ func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Functio
|
||||||
case errorType:
|
case errorType:
|
||||||
return i.errorMethods[meth.Id()]
|
return i.errorMethods[meth.Id()]
|
||||||
}
|
}
|
||||||
sel := i.prog.MethodSets.MethodSet(typ).Lookup(meth.Pkg(), meth.Name())
|
return i.prog.LookupMethod(typ, meth.Pkg(), meth.Name())
|
||||||
if sel == nil {
|
|
||||||
panic(fmt.Sprintf("%s has no method %s (of type %s)", typ, meth.Id(), meth.Type()))
|
|
||||||
}
|
|
||||||
return i.prog.Method(sel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// visitInstr interprets a single ssa.Instruction within the activation
|
// visitInstr interprets a single ssa.Instruction within the activation
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,17 @@ func (prog *Program) Method(meth *types.Selection) *Function {
|
||||||
return prog.addMethod(prog.createMethodSet(T), meth)
|
return prog.addMethod(prog.createMethodSet(T), meth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupMethod returns the implementation of the method of type T
|
||||||
|
// identified by (pkg, name). It panics if there is no such method.
|
||||||
|
//
|
||||||
|
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
|
||||||
|
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
|
||||||
|
if sel == nil {
|
||||||
|
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
|
||||||
|
}
|
||||||
|
return prog.Method(sel)
|
||||||
|
}
|
||||||
|
|
||||||
// makeMethods ensures that all wrappers in the complete method set of
|
// makeMethods ensures that all wrappers in the complete method set of
|
||||||
// T are generated. It is equivalent to calling prog.Method() on all
|
// T are generated. It is equivalent to calling prog.Method() on all
|
||||||
// members of T.methodSet(), but acquires fewer locks.
|
// members of T.methodSet(), but acquires fewer locks.
|
||||||
|
|
@ -128,7 +139,7 @@ func (prog *Program) TypesWithMethodSets() []types.Type {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypesWithMethodSets returns a new unordered slice containing the
|
// TypesWithMethodSets returns an unordered slice containing the
|
||||||
// set of all types referenced within package pkg and not belonging to
|
// set of all types referenced within package pkg and not belonging to
|
||||||
// some other package, for which a complete (non-empty) method set is
|
// some other package, for which a complete (non-empty) method set is
|
||||||
// required at run-time.
|
// required at run-time.
|
||||||
|
|
|
||||||
|
|
@ -202,8 +202,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||||
return v.(*Function)
|
return v.(*Function)
|
||||||
}
|
}
|
||||||
// Interface method wrapper?
|
// Interface method wrapper?
|
||||||
meth := prog.MethodSets.MethodSet(recvType(obj)).Lookup(obj.Pkg(), obj.Name())
|
return prog.LookupMethod(recvType(obj), obj.Pkg(), obj.Name())
|
||||||
return prog.Method(meth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConstValue returns the SSA Value denoted by the source-level named
|
// ConstValue returns the SSA Value denoted by the source-level named
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue