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:
Alan Donovan 2014-02-18 12:40:44 -08:00
parent 675033b4f3
commit 5f96644dbf
12 changed files with 293 additions and 111 deletions

View File

@ -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()

View File

@ -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.

View File

@ -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
} }

View File

@ -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

View File

@ -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{}) ----------

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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.

View File

@ -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