diff --git a/pointer/analysis.go b/pointer/analysis.go index e3677c69..0f21424d 100644 --- a/pointer/analysis.go +++ b/pointer/analysis.go @@ -205,14 +205,16 @@ type analysis struct { work worklist // solver's worklist result *Result // results of the analysis - // Reflection: - hasher typemap.Hasher // cache of type hashes - reflectValueObj types.Object // type symbol for reflect.Value (if present) - reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present) - reflectRtypePtr *types.Pointer // *reflect.rtype - reflectType *types.Named // reflect.Type - rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T - reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value + // Reflection & intrinsics: + hasher typemap.Hasher // cache of type hashes + reflectValueObj types.Object // type symbol for reflect.Value (if present) + reflectValueCall *ssa.Function // (reflect.Value).Call + reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present) + reflectRtypePtr *types.Pointer // *reflect.rtype + reflectType *types.Named // reflect.Type + rtypes typemap.M // nodeid of canonical *rtype-tagged object for type T + reflectZeros typemap.M // nodeid of canonical T-tagged object for zero value + runtimeSetFinalizer *ssa.Function // runtime.SetFinalizer } // enclosingObj returns the object (addressible memory object) that encloses node id. @@ -273,7 +275,9 @@ func Analyze(config *Config) *Result { } if reflect := a.prog.ImportedPackage("reflect"); reflect != nil { - a.reflectValueObj = reflect.Object.Scope().Lookup("Value") + rV := reflect.Object.Scope().Lookup("Value") + a.reflectValueObj = rV + a.reflectValueCall = a.prog.Method(rV.Type().MethodSet().Lookup(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()) @@ -285,6 +289,9 @@ func Analyze(config *Config) *Result { a.rtypes.SetHasher(a.hasher) a.reflectZeros.SetHasher(a.hasher) } + if runtime := a.prog.ImportedPackage("runtime"); runtime != nil { + a.runtimeSetFinalizer = runtime.Func("SetFinalizer") + } root := a.generate() @@ -314,22 +321,11 @@ func Analyze(config *Config) *Result { } } - // Visit discovered call graph. + // Add dynamic edges to call graph. for _, caller := range a.cgnodes { for _, site := range caller.sites { - for nid := range a.nodes[site.targets].pts { - callee := a.nodes[nid].obj.cgn - - if a.config.BuildCallGraph { - site.callees = append(site.callees, callee) - } - - // TODO(adonovan): de-dup these messages. - // Warn about calls to non-intrinsic external functions. - if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil { - a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn) - a.warnf(fn.Pos(), " (declared here)") - } + for callee := range a.nodes[site.targets].pts { + a.callEdge(site, callee) } } } @@ -340,3 +336,29 @@ func Analyze(config *Config) *Result { return a.result } + +// callEdge is called for each edge in the callgraph. +// calleeid is the callee's object node (has otFunction flag). +// +func (a *analysis) callEdge(site *callsite, calleeid nodeid) { + obj := a.nodes[calleeid].obj + if obj.flags&otFunction == 0 { + panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid)) + } + callee := obj.cgn + + if a.config.BuildCallGraph { + site.callees = append(site.callees, callee) + } + + if a.log != nil { + fmt.Fprintf(a.log, "\tcall edge %s -> %s\n", site, callee) + } + + // Warn about calls to non-intrinsic external functions. + // TODO(adonovan): de-dup these messages. + if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil { + a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn) + a.warnf(fn.Pos(), " (declared here)") + } +} diff --git a/pointer/callgraph.go b/pointer/callgraph.go index b5cedfab..c17812a9 100644 --- a/pointer/callgraph.go +++ b/pointer/callgraph.go @@ -77,7 +77,7 @@ func (n *cgnode) String() string { // they are handled as intrinsics. // type callsite struct { - targets nodeid // pts(targets) contains identities of all called functions. + targets nodeid // pts(·) contains objects for dynamically called functions instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic callees []*cgnode // unordered set of callees of this site } diff --git a/pointer/gen.go b/pointer/gen.go index 72e7e66c..7b7ca60e 100644 --- a/pointer/gen.go +++ b/pointer/gen.go @@ -578,18 +578,31 @@ func (a *analysis) shouldUseContext(fn *ssa.Function) bool { // genStaticCall generates constraints for a statically dispatched function call. func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) { - // Ascertain the context (contour/CGNode) for a particular call. - var obj nodeid fn := call.StaticCallee() + + // Special cases for inlined intrinsics. + switch fn { + case a.runtimeSetFinalizer: + // Inline SetFinalizer so the call appears direct. + site.targets = a.addOneNode(tInvalid, "SetFinalizer.targets", nil) + a.addConstraint(&runtimeSetFinalizerConstraint{ + targets: site.targets, + x: a.valueNode(call.Args[0]), + f: a.valueNode(call.Args[1]), + }) + return + } + + // Ascertain the context (contour/cgnode) for a particular call. + var obj nodeid if a.shouldUseContext(fn) { obj = a.makeFunctionObject(fn, site) // new contour } else { obj = a.objectNode(nil, fn) // shared contour } + a.callEdge(site, obj) sig := call.Signature() - targets := a.addOneNode(sig, "call.targets", nil) - a.addressOf(targets, obj) // (a singleton) // Copy receiver, if any. params := a.funcParams(obj) @@ -613,20 +626,18 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC if result != 0 { a.copy(result, a.funcResults(obj), a.sizeof(sig.Results())) } - - // pts(targets) will be the (singleton) set of possible call targets. - site.targets = targets } // genDynamicCall generates constraints for a dynamic function call. func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) { - fn := a.valueNode(call.Value) - sig := call.Signature() + // pts(targets) will be the set of possible call targets. + site.targets = a.valueNode(call.Value) // We add dynamic closure rules that store the arguments into, // and load the results from, the P/R block of each function - // discovered in pts(fn). + // discovered in pts(targets). + sig := call.Signature() var offset uint32 = 1 // P/R block starts at offset 1 for i, arg := range call.Args { sz := a.sizeof(sig.Params().At(i).Type()) @@ -636,9 +647,6 @@ func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.Call if result != 0 { a.genLoad(caller, result, call.Value, offset, a.sizeof(sig.Results())) } - - // pts(targets) will be the (singleton) set of possible call targets. - site.targets = fn } // genInvoke generates constraints for a dynamic method invocation. @@ -699,9 +707,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss fn := a.prog.Method(meth) obj := a.makeFunctionObject(fn, site) // new contour for this call - - // pts(targets) will be the (singleton) set of possible call targets. - site.targets = obj + a.callEdge(site, obj) // From now on, it's essentially a static call, but little is // gained by factoring together the code for both cases. @@ -745,13 +751,11 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) { } site := &callsite{instr: instr} - - switch { - case call.StaticCallee() != nil: + if call.StaticCallee() != nil { a.genStaticCall(caller, site, call, result) - case call.IsInvoke(): + } else if call.IsInvoke() { a.genInvoke(caller, site, call, result) - default: + } else { a.genDynamicCall(caller, site, call, result) } diff --git a/pointer/intrinsics.go b/pointer/intrinsics.go index 762429e0..ed412fdf 100644 --- a/pointer/intrinsics.go +++ b/pointer/intrinsics.go @@ -16,6 +16,9 @@ package pointer // own C functions using a snippet of Go. import ( + "fmt" + + "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/ssa" ) @@ -216,7 +219,7 @@ func init() { "runtime.Breakpoint": ext۰NoEffect, "runtime.CPUProfile": ext۰NotYetImplemented, "runtime.Caller": ext۰NoEffect, - "runtime.FuncForPC": ext۰NotYetImplemented, + "runtime.FuncForPC": ext۰NoEffect, "runtime.GC": ext۰NoEffect, "runtime.GOMAXPROCS": ext۰NoEffect, "runtime.Goexit": ext۰NoEffect, @@ -228,7 +231,7 @@ func init() { "runtime.ReadMemStats": ext۰NoEffect, "runtime.SetBlockProfileRate": ext۰NoEffect, "runtime.SetCPUProfileRate": ext۰NoEffect, - "runtime.SetFinalizer": ext۰NotYetImplemented, + "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, "runtime.Stack": ext۰NoEffect, "runtime.ThreadCreateProfile": ext۰NoEffect, "runtime.funcentry_go": ext۰NoEffect, @@ -315,3 +318,70 @@ func ext۰NotYetImplemented(a *analysis, cgn *cgnode) { // enough that it's not unbearably annoying. // a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn) } + +// ---------- func runtime.SetFinalizer(x, f interface{}) ---------- + +// runtime.SetFinalizer(x, f) +type runtimeSetFinalizerConstraint struct { + targets nodeid + f nodeid // (ptr) + x nodeid +} + +func (c *runtimeSetFinalizerConstraint) String() string { + return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f) +} + +func (c *runtimeSetFinalizerConstraint) ptr() nodeid { + return c.f +} + +func (c *runtimeSetFinalizerConstraint) solve(a *analysis, _ *node, delta nodeset) { + for fObj := range delta { + tDyn, f, indirect := a.taggedValue(fObj) + if tDyn == nil { + panic("not a tagged object") + } + if indirect { + // TODO(adonovan): we'll need to implement this + // when we start creating indirect tagged objects. + panic("indirect tagged object") + } + + tSig, ok := tDyn.Underlying().(*types.Signature) + if !ok { + continue // not a function + } + if tSig.Recv() != nil { + panic(tSig) + } + if tSig.Params().Len() != 1 { + continue // not a unary function + } + + // Extract x to tmp. + tx := tSig.Params().At(0).Type() + tmp := a.addNodes(tx, "SetFinalizer.tmp") + a.untag(tx, tmp, c.x, false) + + // Call f(tmp). + a.store(f, tmp, 1, a.sizeof(tx)) + + // Add dynamic call target. + if a.onlineCopy(c.targets, f) { + a.addWork(c.targets) + } + } +} + +func ext۰runtime۰SetFinalizer(a *analysis, cgn *cgnode) { + // This is the shared contour, used for dynamic calls. + targets := a.addOneNode(tInvalid, "SetFinalizer.targets", nil) + cgn.sites = append(cgn.sites, &callsite{targets: targets}) + params := a.funcParams(cgn.obj) + a.addConstraint(&runtimeSetFinalizerConstraint{ + targets: targets, + x: params, + f: params + 1, + }) +} diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go index ebb2e02a..1023643e 100644 --- a/pointer/pointer_test.go +++ b/pointer/pointer_test.go @@ -31,10 +31,6 @@ import ( ) var inputs = []string{ - // Currently debugging: - // "testdata/tmp.go", - - // Working: "testdata/a_test.go", "testdata/another.go", "testdata/arrayreflect.go", @@ -43,6 +39,7 @@ var inputs = []string{ "testdata/chanreflect.go", "testdata/context.go", "testdata/conv.go", + "testdata/finalizer.go", "testdata/flow.go", "testdata/fmtexcerpt.go", "testdata/func.go", @@ -54,13 +51,8 @@ var inputs = []string{ "testdata/panic.go", "testdata/recur.go", "testdata/reflect.go", + "testdata/structreflect.go", "testdata/structs.go", - - // TODO(adonovan): get these tests (of reflection) passing. - // (The tests are mostly sound since they were used for a - // previous implementation.) - // "testdata/finalizer.go", - // "testdata/structreflect.go", } // Expectation grammar: @@ -376,35 +368,41 @@ func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Prog } func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string]string, prog *ssa.Program) bool { - expected := make(map[string]struct{}) - surplus := make(map[string]struct{}) + expected := make(map[string]int) + surplus := make(map[string]int) exact := true for _, g := range e.args { if g == "..." { exact = false continue } - expected[g] = struct{}{} + expected[g]++ } // Find the set of labels that the probe's // argument (x in print(x)) may point to. for _, label := range pr.arg0.PointsTo().Labels() { name := labelString(label, lineMapping, prog) - if _, ok := expected[name]; ok { - delete(expected, name) + if expected[name] > 0 { + expected[name]-- } else if exact { - surplus[name] = struct{}{} + surplus[name]++ } } - // Report set difference: + // Report multiset difference: ok := true - if len(expected) > 0 { - ok = false - e.errorf("value does not alias these expected labels: %s", join(expected)) + for _, count := range expected { + if count > 0 { + ok = false + e.errorf("value does not alias these expected labels: %s", join(expected)) + break + } } - if len(surplus) > 0 { - ok = false - e.errorf("value may additionally alias these labels: %s", join(surplus)) + for _, count := range surplus { + if count > 0 { + ok = false + e.errorf("value may additionally alias these labels: %s", join(surplus)) + break + } } return ok } @@ -462,7 +460,7 @@ func checkTypesExpectation(e *expectation, pr *probe) bool { var errOK = errors.New("OK") func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Graph) bool { - found := make(map[string]struct{}) + found := make(map[string]int) err := call.GraphVisitEdges(callgraph, func(edge call.Edge) error { // Name-based matching is inefficient but it allows us to // match functions whose names that would not appear in an @@ -472,7 +470,7 @@ func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph call.Gra if calleeStr == e.args[1] { return errOK // expectation satisified; stop the search } - found[calleeStr] = struct{}{} + found[calleeStr]++ } return nil }) @@ -544,14 +542,16 @@ func TestInput(t *testing.T) { } } -// join joins the elements of set with " | "s. -func join(set map[string]struct{}) string { +// join joins the elements of multiset with " | "s. +func join(set map[string]int) string { var buf bytes.Buffer sep := "" - for name := range set { - buf.WriteString(sep) - sep = " | " - buf.WriteString(name) + for name, count := range set { + for i := 0; i < count; i++ { + buf.WriteString(sep) + sep = " | " + buf.WriteString(name) + } } return buf.String() } diff --git a/pointer/reflect.go b/pointer/reflect.go index 39eb717f..2aea5d03 100644 --- a/pointer/reflect.go +++ b/pointer/reflect.go @@ -849,8 +849,58 @@ func ext۰reflect۰MakeMap(a *analysis, cgn *cgnode) { }) } -func ext۰reflect۰MakeSlice(a *analysis, cgn *cgnode) {} -func ext۰reflect۰MapOf(a *analysis, cgn *cgnode) {} +// ---------- func MakeSlice(Type) Value ---------- + +// result = MakeSlice(typ) +type reflectMakeSliceConstraint struct { + cgn *cgnode + typ nodeid // (ptr) + result nodeid +} + +func (c *reflectMakeSliceConstraint) String() string { + return fmt.Sprintf("n%d = reflect.MakeSlice(n%d)", c.result, c.typ) +} + +func (c *reflectMakeSliceConstraint) ptr() nodeid { + return c.typ +} + +func (c *reflectMakeSliceConstraint) solve(a *analysis, _ *node, delta nodeset) { + changed := false + for typObj := range delta { + T := a.rtypeTaggedValue(typObj) + if _, ok := T.Underlying().(*types.Slice); !ok { + continue // not a slice type + } + + obj := a.nextNode() + a.addNodes(sliceToArray(T), "reflect.MakeSlice") + a.endObject(obj, c.cgn, nil) + + // put its address in a new T-tagged object + id := a.makeTagged(T, c.cgn, nil) + a.addLabel(id+1, obj) + + // flow the T-tagged object to the result + if a.addLabel(c.result, id) { + changed = true + } + } + if changed { + a.addWork(c.result) + } +} + +func ext۰reflect۰MakeSlice(a *analysis, cgn *cgnode) { + a.addConstraint(&reflectMakeSliceConstraint{ + cgn: cgn, + typ: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +func ext۰reflect۰MapOf(a *analysis, cgn *cgnode) {} // ---------- func New(Type) Value ---------- @@ -1063,6 +1113,13 @@ func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) { for typObj := range delta { T := a.rtypeTaggedValue(typObj) + // TODO(adonovan): if T is an interface type, we need + // to create an indirect tagged object containing + // new(T). To avoid updates of such shared values, + // we'll need another flag on indirect tagged values + // that marks whether they are addressable or + // readonly, just like the reflect package does. + // memoize using a.reflectZeros[T] var id nodeid if z := a.reflectZeros.At(T); false && z != nil { @@ -1134,9 +1191,90 @@ func ext۰reflect۰rtype۰Elem(a *analysis, cgn *cgnode) { }) } -func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {} +// ---------- func (*rtype) Field(int) StructField ---------- +// ---------- func (*rtype) FieldByName(string) (StructField, bool) ---------- + +// result = FieldByName(t, name) +// result = Field(t, _) +type rtypeFieldByNameConstraint struct { + cgn *cgnode + name string // name of field; "" for unknown + t nodeid // (ptr) + result nodeid +} + +func (c *rtypeFieldByNameConstraint) String() string { + return fmt.Sprintf("n%d = (*reflect.rtype).FieldByName(n%d, %q)", c.result, c.t, c.name) +} + +func (c *rtypeFieldByNameConstraint) ptr() nodeid { + return c.t +} + +func (c *rtypeFieldByNameConstraint) solve(a *analysis, _ *node, delta nodeset) { + // type StructField struct { + // 0 __identity__ + // 1 Name string + // 2 PkgPath string + // 3 Type Type + // 4 Tag StructTag + // 5 Offset uintptr + // 6 Index []int + // 7 Anonymous bool + // } + + for tObj := range delta { + T := a.nodes[tObj].obj.data.(types.Type) + tStruct, ok := T.Underlying().(*types.Struct) + if !ok { + continue // not a struct type + } + + n := tStruct.NumFields() + for i := 0; i < n; i++ { + f := tStruct.Field(i) + if c.name == "" || c.name == f.Name() { + + // a.offsetOf(Type) is 3. + if id := c.result + 3; a.addLabel(id, a.makeRtype(f.Type())) { + a.addWork(id) + } + // TODO(adonovan): StructField.Index should be non-nil. + } + } + } +} + +func ext۰reflect۰rtype۰FieldByName(a *analysis, cgn *cgnode) { + // If we have access to the callsite, + // and the argument is a string constant, + // return only that field. + var name string + if site := cgn.callersite; site != nil { + if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok { + name = exact.StringVal(c.Value) + } + } + + a.addConstraint(&rtypeFieldByNameConstraint{ + cgn: cgn, + name: name, + t: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + +func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) { + // No-one ever calls Field with a constant argument, + // so we don't specialize that case. + a.addConstraint(&rtypeFieldByNameConstraint{ + cgn: cgn, + t: a.funcParams(cgn.obj), + result: a.funcResults(cgn.obj), + }) +} + func ext۰reflect۰rtype۰FieldByIndex(a *analysis, cgn *cgnode) {} -func ext۰reflect۰rtype۰FieldByName(a *analysis, cgn *cgnode) {} func ext۰reflect۰rtype۰FieldByNameFunc(a *analysis, cgn *cgnode) {} // ---------- func (*rtype) In/Out() Type ---------- @@ -1264,27 +1402,6 @@ func (c *rtypeMethodByNameConstraint) ptr() nodeid { return c.t } -func (c *rtypeMethodByNameConstraint) addMethod(a *analysis, meth *types.Selection) { - // type Method struct { - // 0 __identity__ - // 1 Name string - // 2 PkgPath string - // 3 Type Type - // 4 Func Value - // 5 Index int - // } - fn := a.prog.Method(meth) - - // a.offsetOf(Type) is 3. - if id := c.result + 3; a.addLabel(id, a.makeRtype(changeRecv(fn.Signature))) { - a.addWork(id) - } - // a.offsetOf(Func) is 4. - if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) { - a.addWork(id) - } -} - // changeRecv returns sig with Recv prepended to Params(). func changeRecv(sig *types.Signature) *types.Signature { params := sig.Params() @@ -1307,7 +1424,24 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset) for i, n := 0, mset.Len(); i < n; i++ { sel := mset.At(i) if c.name == "" || c.name == sel.Obj().Name() { - c.addMethod(a, sel) + // type Method struct { + // 0 __identity__ + // 1 Name string + // 2 PkgPath string + // 3 Type Type + // 4 Func Value + // 5 Index int + // } + fn := a.prog.Method(sel) + + // a.offsetOf(Type) is 3. + if id := c.result + 3; a.addLabel(id, a.makeRtype(changeRecv(fn.Signature))) { + a.addWork(id) + } + // a.offsetOf(Func) is 4. + if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) { + a.addWork(id) + } } } } diff --git a/pointer/testdata/arrayreflect.go b/pointer/testdata/arrayreflect.go index 125843a4..68d5b658 100644 --- a/pointer/testdata/arrayreflect.go +++ b/pointer/testdata/arrayreflect.go @@ -160,6 +160,20 @@ func reflectSliceOf() { print(reflect.Zero(tSliceInt)) // @types []int } +type T struct{ x int } + +func reflectMakeSlice() { + rt := []reflect.Type{ + reflect.TypeOf(3), + reflect.TypeOf([]int{}), + reflect.TypeOf([]T{}), + }[0] + sl := reflect.MakeSlice(rt, 0, 0) + print(sl) // @types []int | []T + print(sl) // @pointsto | + print(&sl.Interface().([]T)[0].x) // @pointsto [*].x +} + func main() { reflectValueSlice() reflectValueBytes() @@ -168,4 +182,5 @@ func main() { reflectTypeElem() reflectPtrTo() reflectSliceOf() + reflectMakeSlice() } diff --git a/pointer/testdata/finalizer.go b/pointer/testdata/finalizer.go index c2709b5f..28afacfb 100644 --- a/pointer/testdata/finalizer.go +++ b/pointer/testdata/finalizer.go @@ -3,7 +3,7 @@ package main import "runtime" func final1a(x *int) int { - print(x) // @pointsto alloc@newint:10 + print(x) // @pointsto new@newint:10 return *x } @@ -11,24 +11,24 @@ func final1b(x *bool) { print(x) // @pointsto } -func setfinalizer1() { +func runtimeSetFinalizer1() { x := new(int) // @line newint runtime.SetFinalizer(x, final1a) // ok: final1a's result is ignored runtime.SetFinalizer(x, final1b) // param type mismatch: no effect } -// @calls runtime.SetFinalizer -> main.final1a -// @calls main.setfinalizer1 -> runtime.SetFinalizer +// @calls main.runtimeSetFinalizer1 -> main.final1a +// @calls main.runtimeSetFinalizer1 -> main.final1b func final2a(x *bool) { - print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10 + print(x) // @pointsto new@newbool1:10 | new@newbool2:10 } func final2b(x *bool) { - print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10 + print(x) // @pointsto new@newbool1:10 | new@newbool2:10 } -func setfinalizer2() { +func runtimeSetFinalizer2() { x := new(bool) // @line newbool1 f := final2a if unknown { @@ -38,33 +38,44 @@ func setfinalizer2() { runtime.SetFinalizer(x, f) } -// @calls runtime.SetFinalizer -> main.final2a -// @calls runtime.SetFinalizer -> main.final2b -// @calls main.setfinalizer2 -> runtime.SetFinalizer +// @calls main.runtimeSetFinalizer2 -> main.final2a +// @calls main.runtimeSetFinalizer2 -> main.final2b -// type T int +type T int -// func (t *T) finalize() { -// print(t) // #@pointsto x -// } - -// func setfinalizer3() { -// x := new(T) -// runtime.SetFinalizer(x, (*T).finalize) // go/types gives wrong type to f. -// } - -// #@calls runtime.SetFinalizer -> (*T) finalize - -func funcForPC() { - f := runtime.FuncForPC(0) // @line funcforpc - print(f) // @pointsto reflectAlloc@funcforpc:25 +func (t *T) finalize() { + print(t) // @pointsto new@final3:10 } +func runtimeSetFinalizer3() { + x := new(T) // @line final3 + runtime.SetFinalizer(x, (*T).finalize) +} + +// @calls main.runtimeSetFinalizer3 -> (*main.T).finalize + +// I hope I never live to see this code in the wild. +var setFinalizer = runtime.SetFinalizer + +func final4(x *int) { + print(x) // @pointsto new@finalIndirect:10 +} + +func runtimeSetFinalizerIndirect() { + // In an indirect call, the shared contour for SetFinalizer is + // used, i.e. the call is not inlined and appears in the call graph. + x := new(int) // @line finalIndirect + setFinalizer(x, final4) +} + +// @calls main.runtimeSetFinalizerIndirect -> runtime.SetFinalizer +// @calls runtime.SetFinalizer -> main.final4 + func main() { - setfinalizer1() - setfinalizer2() - // setfinalizer3() - funcForPC() + runtimeSetFinalizer1() + runtimeSetFinalizer2() + runtimeSetFinalizer3() + runtimeSetFinalizerIndirect() } var unknown bool // defeat dead-code elimination diff --git a/pointer/testdata/reflect.go b/pointer/testdata/reflect.go index ad494c98..03bf28f1 100644 --- a/pointer/testdata/reflect.go +++ b/pointer/testdata/reflect.go @@ -44,6 +44,7 @@ func reflectTypeElem() { print(reflect.Zero(reflect.TypeOf(make(map[string]float64)).Elem()).Interface()) // @types float64 print(reflect.Zero(reflect.TypeOf([3]complex64{}).Elem()).Interface()) // @types complex64 print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface()) // @types + print(reflect.Zero(reflect.TypeOf(new(interface{})).Elem()).Interface()) // @types interface{} } func main() { diff --git a/pointer/testdata/structreflect.go b/pointer/testdata/structreflect.go index 980ce777..5cb6658b 100644 --- a/pointer/testdata/structreflect.go +++ b/pointer/testdata/structreflect.go @@ -4,22 +4,46 @@ package main import "reflect" -var a, b int - type A struct { f *int g interface{} h bool } -func structReflect1() { - var a A - fld, _ := reflect.TypeOf(a).FieldByName("f") // "f" is ignored - // TODO(adonovan): what does interface{} even mean here? - print(reflect.Zero(fld.Type).Interface()) // @types *int | bool | interface{} - // TODO(adonovan): test promotion/embedding. +var dyn string + +func reflectTypeFieldByName() { + f, _ := reflect.TypeOf(A{}).FieldByName("f") + print(f.Type) // @pointsto *int + + g, _ := reflect.TypeOf(A{}).FieldByName("g") + print(g.Type) // @pointsto interface{} + print(reflect.Zero(g.Type)) // @pointsto + print(reflect.Zero(g.Type)) // @types interface{} + + // TODO(adonovan): fix: the following should return a zero + // value of the empty interface (i.e. pts is empty), but that + // requires fixing the TODO comment in + // reflectZeroConstraint.solve, which in turn requires that we + // add a "settable" flag to tagged objects. + print(reflect.Zero(g.Type).Interface()) // @types interface{} + + h, _ := reflect.TypeOf(A{}).FieldByName("h") + print(h.Type) // @pointsto bool + + missing, _ := reflect.TypeOf(A{}).FieldByName("missing") + print(missing.Type) // @pointsto + + dyn, _ := reflect.TypeOf(A{}).FieldByName(dyn) + print(dyn.Type) // @pointsto *int | bool | interface{} +} + +func reflectTypeField() { + fld := reflect.TypeOf(A{}).Field(0) + print(fld.Type) // @pointsto *int | bool | interface{} } func main() { - structReflect1() + reflectTypeFieldByName() + reflectTypeField() }