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