From 5f96644dbf29b1c6dcc9676c4b5f680bba1433bd Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 18 Feb 2014 12:40:44 -0800 Subject: [PATCH] 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 --- go/pointer/analysis.go | 48 +++++++++++++++++++-- go/pointer/api.go | 50 ++++++++++++++-------- go/pointer/example_test.go | 35 ++++++++++++++-- go/pointer/gen.go | 85 +++++++++++++++++--------------------- go/pointer/intrinsics.go | 47 +++++++++++++++++++-- go/pointer/pointer_test.go | 59 +++++++++++++++++--------- go/pointer/reflect.go | 7 ++-- go/pointer/solve.go | 9 +--- go/pointer/util.go | 42 +++++++++++++++++++ go/ssa/interp/interp.go | 6 +-- go/ssa/promote.go | 13 +++++- go/ssa/source.go | 3 +- 12 files changed, 293 insertions(+), 111 deletions(-) diff --git a/go/pointer/analysis.go b/go/pointer/analysis.go index 4cc310b1..8896ada9 100644 --- a/go/pointer/analysis.go +++ b/go/pointer/analysis.go @@ -193,6 +193,7 @@ type analysis struct { panicNode nodeid // sink for panic, source for recover nodes []*node // indexed by nodeid flattenMemo map[types.Type][]*fieldInfo // memoization of flatten() + trackTypes map[types.Type]bool // memoization of shouldTrack() constraints []constraint // set of constraints cgnodes []*cgnode // all cgnodes 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 work worklist // solver's worklist result *Result // results of the analysis + track track // pointerlike types whose aliasing we track // Reflection & intrinsics: 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{}) { - 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 @@ -257,13 +293,13 @@ func Analyze(config *Config) *Result { globalval: make(map[ssa.Value]nodeid), globalobj: make(map[ssa.Value]nodeid), flattenMemo: make(map[types.Type][]*fieldInfo), + trackTypes: make(map[types.Type]bool), hasher: typemap.MakeHasher(), intrinsics: make(map[*ssa.Function]intrinsic), work: makeMapWorklist(), result: &Result{ Queries: 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 { rV := reflect.Object.Scope().Lookup("Value") 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.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype") a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type()) @@ -287,12 +323,18 @@ func Analyze(config *Config) *Result { tReflectValue := a.reflectValueObj.Type() 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.reflectZeros.SetHasher(a.hasher) } if runtime := a.prog.ImportedPackage("runtime"); runtime != nil { a.runtimeSetFinalizer = runtime.Func("SetFinalizer") } + a.computeTrackBits() root := a.generate() diff --git a/go/pointer/api.go b/go/pointer/api.go index b9ce6b0b..6cc32ec6 100644 --- a/go/pointer/api.go +++ b/go/pointer/api.go @@ -33,14 +33,6 @@ type Config struct { // If enabled, the graph will be available in Result.CallGraph. 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] // for each ssa.Value v of interest, to request that 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 // slice may have multiple Pointers, one per distinct context. // 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 // 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{} IndirectQueries map[ssa.Value]struct{} @@ -74,8 +67,26 @@ type Config struct { 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. +// 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) { + if !CanPoint(v.Type()) { + panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type())) + } if c.Queries == nil { c.Queries = make(map[ssa.Value]struct{}) } @@ -83,10 +94,14 @@ func (c *Config) AddQuery(v ssa.Value) { } // AddQuery adds v to Config.IndirectQueries. +// Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()). func (c *Config) AddIndirectQuery(v ssa.Value) { if c.IndirectQueries == nil { 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{}{} } @@ -107,14 +122,13 @@ type Warning struct { // See Config for how to request the various Result components. // type Result struct { - CallGraph callgraph.Graph // discovered call graph - 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. - Warnings []Warning // warnings of unsoundness - PrintCalls map[*ssa.CallCommon]Pointer // pts(x) for each call to print(x) + CallGraph callgraph.Graph // discovered call graph + 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. + Warnings []Warning // warnings of unsoundness } -// 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 // types may alias the same object. diff --git a/go/pointer/example_test.go b/go/pointer/example_test.go index 07ec3a8c..71210b4f 100644 --- a/go/pointer/example_test.go +++ b/go/pointer/example_test.go @@ -16,6 +16,8 @@ import ( // This program demonstrates how to use the pointer analysis to // 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() { const myprog = ` @@ -24,18 +26,19 @@ package main import "fmt" type I interface { - f() + f(map[string]int) } type C struct{} -func (C) f() { +func (C) f(m map[string]int) { fmt.Println("C.f()") } func main() { var i I = C{} - i.f() // dynamic method call + x := map[string]int{"one":1} + i.f(x) // dynamic method call } ` // Construct a loader. @@ -64,11 +67,18 @@ func main() { // Build SSA code for bodies of all functions in the whole program. prog.BuildAll() - // Run the pointer analysis and build the complete callgraph. + // Configure the pointer analysis to build a call-graph. config := &pointer.Config{ Mains: []*ssa.Package{mainPkg}, 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) // Find edges originating from the main package. @@ -88,9 +98,26 @@ func main() { for _, edge := range edges { 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: // (main.C).f --> fmt.Println // main.init --> fmt.init // main.main --> (main.C).f + // + // m may point to: + // myprog.go:18:21: makemap } diff --git a/go/pointer/gen.go b/go/pointer/gen.go index ce190d1c..dcaf76cb 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -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) } + // 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). if _, ok := a.config.Queries[v]; ok { 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) 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) return id @@ -207,7 +219,7 @@ func (a *analysis) valueNode(v ssa.Value) nodeid { } id = a.addOneNode(v.Type(), comment, nil) if obj := a.objectNode(nil, v); obj != 0 { - a.addressOf(id, obj) + a.addressOf(v.Type(), id, obj) } a.setValueNode(v, id, nil) } @@ -290,14 +302,17 @@ func (a *analysis) copy(dst, src nodeid, sizeof uint32) { } // 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 { panic("addressOf: zero id") } if obj == 0 { panic("addressOf: zero obj") } - a.addConstraint(&addrConstraint{id, obj}) + if a.shouldTrack(T) { + a.addConstraint(&addrConstraint{id, obj}) + } } // load creates a load constraint of the form dst = src[offset]. @@ -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. // 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 { // Simplify dst = &src->f0 // to dst = src @@ -432,7 +451,7 @@ func (a *analysis) genConv(conv *ssa.Convert, cgn *cgnode) { // unsafe conversions soundly; see TODO file. obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion") a.endObject(obj, cgn, conv) - a.addressOf(res, obj) + a.addressOf(tDst, res, obj) return } @@ -441,7 +460,7 @@ func (a *analysis) genConv(conv *ssa.Convert, cgn *cgnode) { if utSrc.Info()&types.IsString != 0 { obj := a.addNodes(sliceToArray(tDst), "convert") a.endObject(obj, cgn, conv) - a.addressOf(res, obj) + a.addressOf(tDst, res, obj) return } @@ -499,8 +518,8 @@ func (a *analysis) genAppend(instr *ssa.Call, cgn *cgnode) { a.addNodes(tArray, "append") a.endObject(w, cgn, instr) - a.copyElems(cgn, tArray.Elem(), z, y) // *z = *y - a.addressOf(a.valueNode(z), w) // z = &w + a.copyElems(cgn, tArray.Elem(), z, y) // *z = *y + a.addressOf(instr.Type(), a.valueNode(z), w) // z = &w } // 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) } - 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: - // 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 ret := reflectCallImpl(a, caller, site, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]), dotdotdot) if result != 0 { - a.addressOf(result, ret) + a.addressOf(fn.Signature.Results().At(0).Type(), result, ret) } return } @@ -720,8 +718,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss a.typeAssert(a.reflectRtypePtr, rtype, recv, true) // Look up the concrete method. - meth := a.prog.MethodSets.MethodSet(a.reflectRtypePtr).Lookup(call.Method.Pkg(), call.Method.Name()) - fn := a.prog.Method(meth) + fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name()) obj := a.makeFunctionObject(fn, site) // new contour for this call a.callEdge(site, obj) @@ -731,7 +728,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss sig := fn.Signature // concrete method targets := a.addOneNode(sig, "call.targets", nil) - a.addressOf(targets, obj) // (a singleton) + a.addressOf(sig, targets, obj) // (a singleton) // Copy receiver. params := a.funcParams(obj) @@ -814,12 +811,7 @@ func (a *analysis) objectNode(cgn *cgnode, v ssa.Value) nodeid { obj = a.makeFunctionObject(v, nil) case *ssa.Const: - if t, ok := v.Type().Underlying().(*types.Slice); ok && !v.IsNil() { - // Non-nil []byte or []rune constant. - obj = a.nextNode() - a.addNodes(sliceToArray(t), "array in slice constant") - a.endObject(obj, nil, v) - } + // The only pointer-like Consts are nil. case *ssa.Capture: // 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) if obj := a.objectNode(cgn, v); obj != 0 { // Pre-apply offsetAddrConstraint.solve(). - a.addressOf(dst, obj) + a.addressOf(v.Type(), dst, obj) } 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: 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: 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. if os := a.prog.ImportedPackage("os"); os != nil { // In effect: os.Args = new([1]string)[:] - obj := a.addNodes(types.NewArray(types.Typ[types.String], 1), "") + T := types.NewSlice(types.Typ[types.String]) + obj := a.addNodes(sliceToArray(T), "") a.endObject(obj, nil, "") - a.addressOf(a.objectNode(nil, os.Var("Args")), obj) + a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj) } return root diff --git a/go/pointer/intrinsics.go b/go/pointer/intrinsics.go index 25c70871..c4a5a850 100644 --- a/go/pointer/intrinsics.go +++ b/go/pointer/intrinsics.go @@ -158,6 +158,7 @@ func init() { "crypto/md5.block": ext۰NoEffect, "crypto/rc4.xorKeyStream": ext۰NoEffect, "crypto/sha1.block": ext۰NoEffect, + "crypto/sha256.block": ext۰NoEffect, "hash/crc32.castagnoliSSE42": ext۰NoEffect, "hash/crc32.haveSSE42": ext۰NoEffect, "math.Abs": ext۰NoEffect, @@ -214,11 +215,13 @@ func init() { "net.runtime_pollSetDeadline": ext۰NoEffect, "net.runtime_pollUnblock": ext۰NoEffect, "net.runtime_pollWait": ext۰NoEffect, + "net.runtime_pollWaitCanceled": ext۰NoEffect, "os.epipecheck": ext۰NoEffect, "runtime.BlockProfile": ext۰NoEffect, "runtime.Breakpoint": ext۰NoEffect, - "runtime.CPUProfile": ext۰NotYetImplemented, + "runtime.CPUProfile": ext۰NoEffect, // good enough "runtime.Caller": ext۰NoEffect, + "runtime.Callers": ext۰NoEffect, // good enough "runtime.FuncForPC": ext۰NoEffect, "runtime.GC": ext۰NoEffect, "runtime.GOMAXPROCS": ext۰NoEffect, @@ -234,6 +237,7 @@ func init() { "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, "runtime.Stack": ext۰NoEffect, "runtime.ThreadCreateProfile": ext۰NoEffect, + "runtime.cstringToGo": ext۰NoEffect, "runtime.funcentry_go": ext۰NoEffect, "runtime.funcline_go": ext۰NoEffect, "runtime.funcname_go": ext۰NoEffect, @@ -245,17 +249,28 @@ func init() { "sync.runtime_Syncsemacquire": ext۰NoEffect, "sync.runtime_Syncsemcheck": 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.AddInt64": ext۰NoEffect, "sync/atomic.AddUint32": ext۰NoEffect, + "sync/atomic.AddUint64": ext۰NoEffect, + "sync/atomic.AddUintptr": ext۰NoEffect, "sync/atomic.CompareAndSwapInt32": ext۰NoEffect, "sync/atomic.CompareAndSwapUint32": ext۰NoEffect, "sync/atomic.CompareAndSwapUint64": ext۰NoEffect, "sync/atomic.CompareAndSwapUintptr": 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.LoadUint64": ext۰NoEffect, + "sync/atomic.LoadUintptr": ext۰NoEffect, "sync/atomic.StoreInt32": ext۰NoEffect, + "sync/atomic.StorePointer": ext۰NoEffect, // ignore unsafe.Pointer for now "sync/atomic.StoreUint32": ext۰NoEffect, + "sync/atomic.StoreUintptr": ext۰NoEffect, "syscall.Close": ext۰NoEffect, "syscall.Exit": ext۰NoEffect, "syscall.Getpid": ext۰NoEffect, @@ -267,6 +282,7 @@ func init() { "syscall.Syscall6": ext۰NoEffect, "syscall.runtime_AfterFork": ext۰NoEffect, "syscall.runtime_BeforeFork": ext۰NoEffect, + "syscall.setenv_c": ext۰NoEffect, "time.Sleep": ext۰NoEffect, "time.now": ext۰NoEffect, "time.startTimer": ext۰NoEffect, @@ -284,7 +300,7 @@ func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic { if !ok { 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 { impl = ext۰NoEffect // reflection disabled } else if impl == nil { @@ -298,6 +314,28 @@ func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic { 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: // 1) induce aliases between its arguments or any global variables; // 2) call any functions; or @@ -316,7 +354,10 @@ func ext۰NoEffect(a *analysis, cgn *cgnode) {} func ext۰NotYetImplemented(a *analysis, cgn *cgnode) { // TODO(adonovan): enable this warning when we've implemented // 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{}) ---------- diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go index 6e00f6f1..863a2236 100644 --- a/go/pointer/pointer_test.go +++ b/go/pointer/pointer_test.go @@ -25,6 +25,7 @@ import ( "code.google.com/p/go.tools/go/loader" "code.google.com/p/go.tools/go/pointer" "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/typemap" ) @@ -135,16 +136,15 @@ func (e *expectation) needsProbe() bool { return e.kind == "pointsto" || e.kind == "types" } -// Find probe (call to print(x)) of same source -// file/line as expectation. -func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e *expectation) (site *ssa.CallCommon, ptr pointer.Pointer) { - for call, ptr := range probes { +// Find probe (call to print(x)) of same source 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) { + for call := range probes { pos := prog.Fset.Position(call.Pos()) if pos.Line == e.linenum && pos.Filename == e.filename { // TODO(adonovan): send this to test log (display only on failure). // fmt.Printf("%s:%d: info: found probe for %s: %s\n", // 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 @@ -167,18 +167,37 @@ func doOneInput(input, filename string) bool { fmt.Println(err) return false } + mainPkgInfo := iprog.Created[0].Pkg // SSA creation + building. prog := ssa.Create(iprog, ssa.SanityCheckFunctions) prog.BuildAll() - mainpkg := prog.Package(iprog.Created[0].Pkg) + mainpkg := prog.Package(mainPkgInfo) ptrmain := mainpkg // main package for the pointer analysis if mainpkg.Func("main") == nil { // No main function; assume it's a test. 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 lineMapping := make(map[string]string) // maps "file:line" to @line tag @@ -271,11 +290,13 @@ func doOneInput(input, filename string) bool { // Run the analysis. config := &pointer.Config{ - Reflection: true, - BuildCallGraph: true, - QueryPrintCalls: true, - Mains: []*ssa.Package{ptrmain}, - Log: &log, + Reflection: true, + BuildCallGraph: true, + Mains: []*ssa.Package{ptrmain}, + Log: &log, + } + for probe := range probes { + config.AddQuery(probe.Args[0]) } // Print the log is there was an error or a panic. @@ -291,10 +312,10 @@ func doOneInput(input, filename string) bool { // Check the expectations. for _, e := range exps { var call *ssa.CallCommon - var ptr pointer.Pointer + var pts pointer.PointsToSet var tProbe types.Type 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 e.errorf("unreachable print() statement has expectation %s", e) continue @@ -309,12 +330,12 @@ func doOneInput(input, filename string) bool { switch e.kind { case "pointsto": - if !checkPointsToExpectation(e, ptr, lineMapping, prog) { + if !checkPointsToExpectation(e, pts, lineMapping, prog) { ok = false } case "types": - if !checkTypesExpectation(e, ptr, tProbe) { + if !checkTypesExpectation(e, pts, tProbe) { ok = false } @@ -359,7 +380,7 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog 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) surplus := make(map[string]int) exact := true @@ -372,7 +393,7 @@ func checkPointsToExpectation(e *expectation, ptr pointer.Pointer, lineMapping m } // Find the set of labels that the probe's // argument (x in print(x)) may point to. - for _, label := range ptr.PointsTo().Labels() { + for _, label := range pts.Labels() { name := labelString(label, lineMapping, prog) if expected[name] > 0 { expected[name]-- @@ -410,7 +431,7 @@ func underlyingType(typ types.Type) types.Type { 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 surplus typemap.M 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 // 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 { expected.Delete(T) } else if exact { diff --git a/go/pointer/reflect.go b/go/pointer/reflect.go index 6238910b..b0dd9d05 100644 --- a/go/pointer/reflect.go +++ b/go/pointer/reflect.go @@ -206,7 +206,7 @@ func reflectCall(a *analysis, cgn *cgnode, dotdotdot bool) { recv := a.funcParams(cgn.obj) arg := recv + 1 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) { @@ -484,9 +484,10 @@ func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) { func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) { // Allocate an array for the result. 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.addressOf(a.funcResults(cgn.obj), obj) + a.addressOf(T, a.funcResults(cgn.obj), obj) a.addConstraint(&rVMapKeysConstraint{ cgn: cgn, diff --git a/go/pointer/solve.go b/go/pointer/solve.go index 8ed4aee3..458e66c5 100644 --- a/go/pointer/solve.go +++ b/go/pointer/solve.go @@ -312,14 +312,9 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { } // Look up the concrete method. - meth := a.prog.MethodSets.MethodSet(tDyn).Lookup(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) + fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name()) 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 diff --git a/go/pointer/util.go b/go/pointer/util.go index 10350169..06d271d7 100644 --- a/go/pointer/util.go +++ b/go/pointer/util.go @@ -176,6 +176,48 @@ func (a *analysis) sizeof(t types.Type) uint32 { 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 // or tuple typ. func (a *analysis) offsetOf(typ types.Type, index int) uint32 { diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go index 369fc8f5..53acb0db 100644 --- a/go/ssa/interp/interp.go +++ b/go/ssa/interp/interp.go @@ -177,11 +177,7 @@ func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Functio case errorType: return i.errorMethods[meth.Id()] } - sel := i.prog.MethodSets.MethodSet(typ).Lookup(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) + return i.prog.LookupMethod(typ, meth.Pkg(), meth.Name()) } // visitInstr interprets a single ssa.Instruction within the activation diff --git a/go/ssa/promote.go b/go/ssa/promote.go index 5b17e666..ea5b6afb 100644 --- a/go/ssa/promote.go +++ b/go/ssa/promote.go @@ -43,6 +43,17 @@ func (prog *Program) Method(meth *types.Selection) *Function { 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 // T are generated. It is equivalent to calling prog.Method() on all // members of T.methodSet(), but acquires fewer locks. @@ -128,7 +139,7 @@ func (prog *Program) TypesWithMethodSets() []types.Type { 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 // some other package, for which a complete (non-empty) method set is // required at run-time. diff --git a/go/ssa/source.go b/go/ssa/source.go index c98dae8c..e4a69a29 100644 --- a/go/ssa/source.go +++ b/go/ssa/source.go @@ -202,8 +202,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function { return v.(*Function) } // Interface method wrapper? - meth := prog.MethodSets.MethodSet(recvType(obj)).Lookup(obj.Pkg(), obj.Name()) - return prog.Method(meth) + return prog.LookupMethod(recvType(obj), obj.Pkg(), obj.Name()) } // ConstValue returns the SSA Value denoted by the source-level named