From 94c387c61089ea5fae8f92c5eb0cc7dd3f250d18 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 29 Oct 2013 21:57:53 -0400 Subject: [PATCH] go.tools/pointer: implement (reflect.Value).Call. The implementation follows the basic pattern of an indirect function call (genDynamicCall). We use the same trick as SetFinalizer so that direct calls to (r.V).Call, which are overwhelmingly the norm, are inlined. Bug fix (and simplification): calling untag() to unbox a reflect.Value is wrong for reflect.Values containing interfaces (rare). Now, we call untag for concrete types and typeFilter for interface types, and we can use this pattern in all cases. It corresponds to the ssa.TypeAssert operator, so we call it typeAssert. Added tests to cover this. We also specialize reflect.{In,Out} when the operand is an int literal. + Tests. Also: - make taggedValue() panic, not return nil, eliminating many checks. We call isTaggedValue for the one place that cares. - pointer_test: recover from panics in Analyze() and dump the log. R=crawshaw CC=golang-dev https://golang.org/cl/14426050 --- pointer/api.go | 4 +- pointer/doc.go | 2 +- pointer/gen.go | 55 +++++---- pointer/intrinsics.go | 5 +- pointer/pointer_test.go | 3 +- pointer/reflect.go | 195 +++++++++++++++++++++++++++----- pointer/solve.go | 13 +-- pointer/testdata/chanreflect.go | 11 ++ pointer/testdata/funcreflect.go | 77 +++++++++---- pointer/testdata/mapreflect.go | 14 +++ pointer/testdata/reflect.go | 31 +++++ pointer/util.go | 8 +- 12 files changed, 326 insertions(+), 92 deletions(-) diff --git a/pointer/api.go b/pointer/api.go index 583d4a16..10103b94 100644 --- a/pointer/api.go +++ b/pointer/api.go @@ -175,10 +175,10 @@ func (s ptset) DynamicTypes() *typemap.M { var tmap typemap.M tmap.SetHasher(s.a.hasher) for ifaceObjId := range s.pts { - tDyn, v, indirect := s.a.taggedValue(ifaceObjId) - if tDyn == nil { + if !s.a.isTaggedObject(ifaceObjId) { continue // !CanHaveDynamicTypes(tDyn) } + tDyn, v, indirect := s.a.taggedValue(ifaceObjId) if indirect { panic("indirect tagged object") // implement later } diff --git a/pointer/doc.go b/pointer/doc.go index 443e8d29..8cb83972 100644 --- a/pointer/doc.go +++ b/pointer/doc.go @@ -300,7 +300,7 @@ reflect.Value v3 := v2.FieldByName("X") // v3 points to an indirect int-tagged object, pointing to s.X v3.Set(y) // pts(s.X) ⊇ pts(y) - Whether indirect or not, the concrete type of the tagged value + Whether indirect or not, the concrete type of the tagged object corresponds to the user-visible dynamic type, and the existence of a pointer is an implementation detail. diff --git a/pointer/gen.go b/pointer/gen.go index 7329d32d..5a995342 100644 --- a/pointer/gen.go +++ b/pointer/gen.go @@ -182,7 +182,7 @@ func (a *analysis) makeRtype(T types.Type) nodeid { func (a *analysis) rtypeTaggedValue(obj nodeid) types.Type { tDyn, t, _ := a.taggedValue(obj) if tDyn != a.reflectRtypePtr { - panic(fmt.Sprintf("not a *reflect.rtype-tagged value: obj=n%d tag=%v payload=n%d", obj, tDyn, t)) + panic(fmt.Sprintf("not a *reflect.rtype-tagged object: obj=n%d tag=%v payload=n%d", obj, tDyn, t)) } return a.nodes[t].typ } @@ -224,17 +224,22 @@ func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid { return id + nodeid(a.offsetOf(v.Type(), index)) } +// isTaggedObject reports whether object obj is a tagged object. +func (a *analysis) isTaggedObject(obj nodeid) bool { + return a.nodes[obj].obj.flags&otTagged != 0 +} + // taggedValue returns the dynamic type tag, the (first node of the) // payload, and the indirect flag of the tagged object starting at id. -// It returns tDyn==nil if obj is not a tagged object. +// Panic ensues if !isTaggedObject(id). // -func (a *analysis) taggedValue(id nodeid) (tDyn types.Type, v nodeid, indirect bool) { - n := a.nodes[id] +func (a *analysis) taggedValue(obj nodeid) (tDyn types.Type, v nodeid, indirect bool) { + n := a.nodes[obj] flags := n.obj.flags - if flags&otTagged != 0 { - return n.typ, id + 1, flags&otIndirect != 0 + if flags&otTagged == 0 { + panic(fmt.Sprintf("not a tagged object: n%d", obj)) } - return + return n.typ, obj + 1, flags&otIndirect != 0 } // funcParams returns the first node of the params block of the @@ -351,14 +356,16 @@ func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) { } } -// typeFilter creates a typeFilter constraint of the form dst = src.(I). -func (a *analysis) typeFilter(I types.Type, dst, src nodeid) { - a.addConstraint(&typeFilterConstraint{I, dst, src}) -} - -// untag creates an untag constraint of the form dst = src.(C). -func (a *analysis) untag(C types.Type, dst, src nodeid, exact bool) { - a.addConstraint(&untagConstraint{C, dst, src, exact}) +// typeAssert creates a typeFilter or untag constraint of the form dst = src.(T): +// typeFilter for an interface, untag for a concrete type. +// The exact flag is specified as for untagConstraint. +// +func (a *analysis) typeAssert(T types.Type, dst, src nodeid, exact bool) { + if isInterface(T) { + a.addConstraint(&typeFilterConstraint{T, dst, src}) + } else { + a.addConstraint(&untagConstraint{T, dst, src, exact}) + } } // addConstraint adds c to the constraint set. @@ -591,6 +598,15 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC f: a.valueNode(call.Args[1]), }) return + + case a.reflectValueCall: + // Inline (reflect.Value).Call so the call appears direct. + 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) + } + return } // Ascertain the context (contour/cgnode) for a particular call. @@ -700,7 +716,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss // Unpack receiver into rtype rtype := a.addOneNode(a.reflectRtypePtr, "rtype.recv", nil) recv := a.valueNode(call.Value) - a.untag(a.reflectRtypePtr, rtype, recv, true) + a.typeAssert(a.reflectRtypePtr, rtype, recv, true) // Look up the concrete method. meth := a.reflectRtypePtr.MethodSet().Lookup(call.Method.Pkg(), call.Method.Name()) @@ -1007,12 +1023,7 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) { a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) case *ssa.TypeAssert: - T := instr.AssertedType - if _, ok := T.Underlying().(*types.Interface); ok { - a.typeFilter(T, a.valueNode(instr), a.valueNode(instr.X)) - } else { - a.untag(T, a.valueNode(instr), a.valueNode(instr.X), true) - } + a.typeAssert(instr.AssertedType, a.valueNode(instr), a.valueNode(instr.X), true) case *ssa.Slice: a.copy(a.valueNode(instr), a.valueNode(instr.X), 1) diff --git a/pointer/intrinsics.go b/pointer/intrinsics.go index ed412fdf..eb9c7356 100644 --- a/pointer/intrinsics.go +++ b/pointer/intrinsics.go @@ -339,9 +339,6 @@ func (c *runtimeSetFinalizerConstraint) ptr() nodeid { 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. @@ -362,7 +359,7 @@ func (c *runtimeSetFinalizerConstraint) solve(a *analysis, _ *node, delta nodese // Extract x to tmp. tx := tSig.Params().At(0).Type() tmp := a.addNodes(tx, "SetFinalizer.tmp") - a.untag(tx, tmp, c.x, false) + a.typeAssert(tx, tmp, c.x, false) // Call f(tmp). a.store(f, tmp, 1, a.sizeof(tx)) diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go index 2ac988a5..2c833e79 100644 --- a/pointer/pointer_test.go +++ b/pointer/pointer_test.go @@ -289,7 +289,6 @@ func doOneInput(input, filename string) bool { probes = append(probes, probe{site, p}) }, } - result := pointer.Analyze(config) // Print the log is there was an error or a panic. complete := false @@ -299,6 +298,8 @@ func doOneInput(input, filename string) bool { } }() + result := pointer.Analyze(config) + // Check the expectations. for _, e := range exps { var pr *probe diff --git a/pointer/reflect.go b/pointer/reflect.go index cc80c230..05f5643d 100644 --- a/pointer/reflect.go +++ b/pointer/reflect.go @@ -82,9 +82,146 @@ func ext۰reflect۰Value۰Bytes(a *analysis, cgn *cgnode) { }) } -func ext۰reflect۰Value۰Call(a *analysis, cgn *cgnode) {} -func ext۰reflect۰Value۰CallSlice(a *analysis, cgn *cgnode) {} -func ext۰reflect۰Value۰Convert(a *analysis, cgn *cgnode) {} +// ---------- func (Value).Call(in []Value) []Value ---------- + +// result = v.Call(in) +type rVCallConstraint struct { + cgn *cgnode + targets nodeid + v nodeid // (ptr) + arg nodeid // = in[*] + result nodeid + dotdotdot bool // interpret last arg as a "..." slice +} + +func (c *rVCallConstraint) String() string { + return fmt.Sprintf("n%d = reflect n%d.Call(n%d)", c.result, c.v, c.arg) +} + +func (c *rVCallConstraint) ptr() nodeid { + return c.v +} + +func (c *rVCallConstraint) solve(a *analysis, _ *node, delta nodeset) { + if c.targets == 0 { + panic("no targets") + } + + changed := false + for vObj := range delta { + tDyn, fn, indirect := a.taggedValue(vObj) + 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) // TODO(adonovan): rethink when we implement Method() + } + + // Add dynamic call target. + if a.onlineCopy(c.targets, fn) { + a.addWork(c.targets) + // TODO(adonovan): is 'else continue' a sound optimisation here? + } + + // Allocate a P/R block. + tParams := tSig.Params() + tResults := tSig.Results() + params := a.addNodes(tParams, "rVCall.params") + results := a.addNodes(tResults, "rVCall.results") + + // Make a dynamic call to 'fn'. + a.store(fn, params, 1, a.sizeof(tParams)) + a.load(results, fn, 1+a.sizeof(tParams), a.sizeof(tResults)) + + // Populate P by type-asserting each actual arg (all merged in c.arg). + for i, n := 0, tParams.Len(); i < n; i++ { + T := tParams.At(i).Type() + a.typeAssert(T, params, c.arg, false) + params += nodeid(a.sizeof(T)) + } + + // Use R by tagging and copying each actual result to c.result. + for i, n := 0, tResults.Len(); i < n; i++ { + T := tResults.At(i).Type() + // Convert from an arbitrary type to a reflect.Value + // (like MakeInterface followed by reflect.ValueOf). + if isInterface(T) { + // (don't tag) + if a.onlineCopy(c.result, results) { + changed = true + } + } else { + obj := a.makeTagged(T, c.cgn, nil) + a.onlineCopyN(obj+1, results, a.sizeof(T)) + if a.addLabel(c.result, obj) { // (true) + changed = true + } + } + results += nodeid(a.sizeof(T)) + } + } + if changed { + a.addWork(c.result) + } +} + +// Common code for direct (inlined) and indirect calls to (reflect.Value).Call. +func reflectCallImpl(a *analysis, cgn *cgnode, site *callsite, recv, arg nodeid, dotdotdot bool) nodeid { + // Allocate []reflect.Value array for the result. + ret := a.nextNode() + a.addNodes(types.NewArray(a.reflectValueObj.Type(), 1), "rVCall.ret") + a.endObject(ret, cgn, nil) + + // pts(targets) will be the set of possible call targets. + site.targets = a.addOneNode(tInvalid, "rvCall.targets", nil) + + // All arguments are merged since they arrive in a slice. + argelts := a.addOneNode(a.reflectValueObj.Type(), "rVCall.args", nil) + a.load(argelts, arg, 1, 1) // slice elements + + a.addConstraint(&rVCallConstraint{ + cgn: cgn, + targets: site.targets, + v: recv, + arg: argelts, + result: ret + 1, // results go into elements of ret + dotdotdot: dotdotdot, + }) + return ret +} + +func reflectCall(a *analysis, cgn *cgnode, dotdotdot bool) { + // This is the shared contour implementation of (reflect.Value).Call + // and CallSlice, as used by indirect calls (rare). + // Direct calls are inlined in gen.go, eliding the + // intermediate cgnode for Call. + site := new(callsite) + cgn.sites = append(cgn.sites, site) + recv := a.funcParams(cgn.obj) + arg := recv + 1 + ret := reflectCallImpl(a, cgn, site, recv, arg, dotdotdot) + a.addressOf(a.funcResults(cgn.obj), ret) +} + +func ext۰reflect۰Value۰Call(a *analysis, cgn *cgnode) { + reflectCall(a, cgn, false) +} + +func ext۰reflect۰Value۰CallSlice(a *analysis, cgn *cgnode) { + // TODO(adonovan): implement. Also, inline direct calls in gen.go too. + if false { + reflectCall(a, cgn, true) + } +} + +func ext۰reflect۰Value۰Convert(a *analysis, cgn *cgnode) {} // ---------- func (Value).Elem() Value ---------- @@ -225,17 +362,13 @@ func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) { changed := false for vObj := range delta { tDyn, payload, indirect := a.taggedValue(vObj) - 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") } - if _, ok := tDyn.Underlying().(*types.Interface); ok { + if isInterface(tDyn) { if a.onlineCopy(c.result, payload) { a.addWork(c.result) } @@ -450,7 +583,7 @@ func (c *rVSendConstraint) solve(a *analysis, _ *node, delta nodeset) { // Extract x's payload to xtmp, then store to channel. tElem := tChan.Elem() xtmp := a.addNodes(tElem, "Send.xtmp") - a.untag(tElem, xtmp, c.x, false) + a.typeAssert(tElem, xtmp, c.x, false) a.store(ch, xtmp, 0, a.sizeof(tElem)) } } @@ -545,12 +678,12 @@ func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) { // Extract key's payload to keytmp, then store to map key. keytmp := a.addNodes(tMap.Key(), "SetMapIndex.keytmp") - a.untag(tMap.Key(), keytmp, c.key, false) + a.typeAssert(tMap.Key(), keytmp, c.key, false) a.store(m, keytmp, 0, keysize) // Extract val's payload to vtmp, then store to map value. valtmp := a.addNodes(tMap.Elem(), "SetMapIndex.valtmp") - a.untag(tMap.Elem(), valtmp, c.val, false) + a.typeAssert(tMap.Elem(), valtmp, c.val, false) a.store(m, valtmp, keysize, a.sizeof(tMap.Elem())) } } @@ -727,10 +860,6 @@ func (c *reflectIndirectConstraint) solve(a *analysis, _ *node, delta nodeset) { changed := false for vObj := range delta { tDyn, _, _ := a.taggedValue(vObj) - if tDyn == nil { - panic("not a tagged value") - } - var res nodeid if tPtr, ok := tDyn.Underlying().(*types.Pointer); ok { // load the payload of the pointer's tagged object @@ -1077,10 +1206,6 @@ func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) { changed := false for iObj := range delta { tDyn, _, _ := a.taggedValue(iObj) - if tDyn == nil { - panic("not a tagged value") - } - if a.addLabel(c.result, a.makeRtype(tDyn)) { changed = true } @@ -1132,7 +1257,7 @@ func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) { // 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 + // we'll need another flag on indirect tagged objects // that marks whether they are addressable or // readonly, just like the reflect package does. @@ -1293,18 +1418,19 @@ func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) { func ext۰reflect۰rtype۰FieldByIndex(a *analysis, cgn *cgnode) {} func ext۰reflect۰rtype۰FieldByNameFunc(a *analysis, cgn *cgnode) {} -// ---------- func (*rtype) In/Out() Type ---------- +// ---------- func (*rtype) In/Out(i int) Type ---------- -// result = In/Out(t) +// result = In/Out(t, i) type rtypeInOutConstraint struct { cgn *cgnode t nodeid // (ptr) result nodeid out bool + i int // -ve if not a constant } func (c *rtypeInOutConstraint) String() string { - return fmt.Sprintf("n%d = (*reflect.rtype).InOut(n%d)", c.result, c.t) + return fmt.Sprintf("n%d = (*reflect.rtype).InOut(n%d, %d)", c.result, c.t, c.i) } func (c *rtypeInOutConstraint) ptr() nodeid { @@ -1324,15 +1450,11 @@ func (c *rtypeInOutConstraint) solve(a *analysis, _ *node, delta nodeset) { if c.out { tuple = sig.Results() } - // TODO(adonovan): when a function is analyzed - // context-sensitively, we should be able to see its - // caller's actual parameter's ssa.Values. Refactor - // the intrinsic mechanism to allow this. Then if the - // value is an int const K, skip the loop and use - // tuple.At(K). for i, n := 0, tuple.Len(); i < n; i++ { - if a.addLabel(c.result, a.makeRtype(tuple.At(i).Type())) { - changed = true + if c.i < 0 || c.i == i { + if a.addLabel(c.result, a.makeRtype(tuple.At(i).Type())) { + changed = true + } } } } @@ -1342,11 +1464,22 @@ func (c *rtypeInOutConstraint) solve(a *analysis, _ *node, delta nodeset) { } func ext۰reflect۰rtype۰InOut(a *analysis, cgn *cgnode, out bool) { + // If we have access to the callsite, + // and the argument is an int constant, + // return only that parameter. + index := -1 + if site := cgn.callersite; site != nil { + if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok { + v, _ := exact.Int64Val(c.Value) + index = int(v) + } + } a.addConstraint(&rtypeInOutConstraint{ cgn: cgn, t: a.funcParams(cgn.obj), result: a.funcResults(cgn.obj), out: out, + i: index, }) } diff --git a/pointer/solve.go b/pointer/solve.go index 05b4a515..1e4beed0 100644 --- a/pointer/solve.go +++ b/pointer/solve.go @@ -50,6 +50,10 @@ func (a *analysis) solve() { } } + if len(a.nodes[0].pts) > 0 { + panic(fmt.Sprintf("pts(0) is nonempty: %s", a.nodes[0].pts)) + } + if a.log != nil { fmt.Fprintf(a.log, "Solver done\n") } @@ -258,9 +262,6 @@ func (c *offsetAddrConstraint) solve(a *analysis, n *node, delta nodeset) { func (c *typeFilterConstraint) solve(a *analysis, n *node, delta nodeset) { for ifaceObj := range delta { tDyn, _, indirect := a.taggedValue(ifaceObj) - if tDyn == nil { - panic("not a tagged value") - } if indirect { // TODO(adonovan): we'll need to implement this // when we start creating indirect tagged objects. @@ -282,9 +283,6 @@ func (c *untagConstraint) solve(a *analysis, n *node, delta nodeset) { } for ifaceObj := range delta { tDyn, v, indirect := a.taggedValue(ifaceObj) - if tDyn == nil { - panic("not a tagged value") - } if indirect { // TODO(adonovan): we'll need to implement this // when we start creating indirect tagged objects. @@ -306,9 +304,6 @@ func (c *untagConstraint) solve(a *analysis, n *node, delta nodeset) { func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) { for ifaceObj := range delta { tDyn, v, indirect := a.taggedValue(ifaceObj) - if tDyn == nil { - panic("not a tagged value") - } if indirect { // TODO(adonovan): we may need to implement this if // we ever apply invokeConstraints to reflect.Value PTSs, diff --git a/pointer/testdata/chanreflect.go b/pointer/testdata/chanreflect.go index 5f3b1138..7d22efeb 100644 --- a/pointer/testdata/chanreflect.go +++ b/pointer/testdata/chanreflect.go @@ -17,6 +17,16 @@ func chanreflect1() { print(<-ch) // @pointsto main.a } +func chanreflect1i() { + // Exercises reflect.Value conversions to/from interfaces: + // a different code path than for concrete types. + ch := make(chan interface{}, 0) + reflect.ValueOf(ch).Send(reflect.ValueOf(&a)) + v := <-ch + print(v) // @types *int + print(v.(*int)) // @pointsto main.a +} + func chanreflect2() { ch := make(chan *int, 0) ch <- &b @@ -66,6 +76,7 @@ func chanOfUnknown() { func main() { chanreflect1() + chanreflect1i() chanreflect2() chanOfRecv() chanOfSend() diff --git a/pointer/testdata/funcreflect.go b/pointer/testdata/funcreflect.go index c3846b2f..f6cff52f 100644 --- a/pointer/testdata/funcreflect.go +++ b/pointer/testdata/funcreflect.go @@ -5,39 +5,73 @@ package main import "reflect" var zero, a, b int +var false2 bool -// func f(p *int) *int { -// print(p) // #@pointsto -// return &b -// } +func f(p *int, q hasF) *int { + print(p) // @pointsto main.a + print(q) // @types *T + print(q.(*T)) // @pointsto new@newT1:22 + return &b +} -// func g(p *bool) { -// } +func g(p *bool) (*int, *bool, hasF) { + return &b, p, new(T) // @line newT2 +} -// func reflectValueCall() { -// rvf := reflect.ValueOf(f) -// res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)}) -// print(res[0].Interface()) // #@types -// print(res[0].Interface().(*int)) // #@pointsto -// } +func reflectValueCall() { + rvf := reflect.ValueOf(f) + res := rvf.Call([]reflect.Value{ + // argument order is not significant: + reflect.ValueOf(new(T)), // @line newT1 + reflect.ValueOf(&a), + }) + print(res[0].Interface()) // @types *int + print(res[0].Interface().(*int)) // @pointsto main.b +} -// #@calls main.reflectValueCall -> main.f +// @calls main.reflectValueCall -> main.f + +func reflectValueCallIndirect() { + rvf := reflect.ValueOf(g) + call := rvf.Call // kids, don't try this at home + + // Indirect call uses shared contour. + // + // Also notice that argument position doesn't matter, and args + // of inappropriate type (e.g. 'a') are ignored. + res := call([]reflect.Value{ + reflect.ValueOf(&a), + reflect.ValueOf(&false2), + }) + res0 := res[0].Interface() + print(res0) // @types *int | *bool | *T + print(res0.(*int)) // @pointsto main.b + print(res0.(*bool)) // @pointsto main.false2 + print(res0.(hasF)) // @types *T + print(res0.(*T)) // @pointsto new@newT2:19 +} + +// @calls main.reflectValueCallIndirect -> bound$(reflect.Value).Call +// @calls bound$(reflect.Value).Call -> main.g func reflectTypeInOut() { var f func(float64, bool) (string, int) - // TODO(adonovan): when the In/Out argument is a valid index constant, - // only include a single type in the result. Needs some work. - print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @types float64 | bool - print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @types float64 | bool + print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface()) // @types float64 + print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface()) // @types bool print(reflect.Zero(reflect.TypeOf(f).In(-1)).Interface()) // @types float64 | bool print(reflect.Zero(reflect.TypeOf(f).In(zero)).Interface()) // @types float64 | bool - print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @types string | int - print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @types string | int - print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @types string | int + print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @types string + print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @types int + print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @types + print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @types } +type hasF interface { + F() +} + type T struct{} func (T) F() {} @@ -76,7 +110,8 @@ func reflectTypeMethod() { } func main() { - //reflectValueCall() + reflectValueCall() + reflectValueCallIndirect() reflectTypeInOut() reflectTypeMethodByName() reflectTypeMethod() diff --git a/pointer/testdata/mapreflect.go b/pointer/testdata/mapreflect.go index f1f14a24..bc5e7e6b 100644 --- a/pointer/testdata/mapreflect.go +++ b/pointer/testdata/mapreflect.go @@ -64,6 +64,19 @@ func reflectSetMapIndex() { print(reflect.Zero(tmap.Elem()).Interface()) // @types *bool } +func reflectSetMapIndexInterface() { + // Exercises reflect.Value conversions to/from interfaces: + // a different code path than for concrete types. + m := make(map[interface{}]interface{}) + reflect.ValueOf(m).SetMapIndex(reflect.ValueOf(&a), reflect.ValueOf(&b)) + for k, v := range m { + print(k) // @types *int + print(k.(*int)) // @pointsto main.a + print(v) // @types *bool + print(v.(*bool)) // @pointsto main.b + } +} + func reflectSetMapIndexAssignable() { // SetMapIndex performs implicit assignability conversions. type I *int @@ -97,6 +110,7 @@ func reflectMakeMap() { func main() { reflectMapKeysIndex() reflectSetMapIndex() + reflectSetMapIndexInterface() reflectSetMapIndexAssignable() reflectMakeMap() // TODO(adonovan): reflect.MapOf(Type) diff --git a/pointer/testdata/reflect.go b/pointer/testdata/reflect.go index 4e7e9814..ec254db0 100644 --- a/pointer/testdata/reflect.go +++ b/pointer/testdata/reflect.go @@ -48,9 +48,40 @@ func reflectTypeElem() { print(reflect.Zero(reflect.TypeOf(new(interface{})).Elem()).Interface()) // @types } +// reflect.Values within reflect.Values. +func metareflection() { + // "box" a *int twice, unbox it twice. + v0 := reflect.ValueOf(&a) + print(v0) // @types *int + v1 := reflect.ValueOf(v0) // box + print(v1) // @types reflect.Value + v2 := reflect.ValueOf(v1) // box + print(v2) // @types reflect.Value + v1a := v2.Interface().(reflect.Value) // unbox + print(v1a) // @types reflect.Value + v0a := v1a.Interface().(reflect.Value) // unbox + print(v0a) // @types *int + print(v0a.Interface().(*int)) // @pointsto main.a + + // "box" an interface{} lvalue twice, unbox it twice. + var iface interface{} = 3 + x0 := reflect.ValueOf(&iface).Elem() + print(x0) // @types interface{} + x1 := reflect.ValueOf(x0) // box + print(x1) // @types reflect.Value + x2 := reflect.ValueOf(x1) // box + print(x2) // @types reflect.Value + x1a := x2.Interface().(reflect.Value) // unbox + print(x1a) // @types reflect.Value + x0a := x1a.Interface().(reflect.Value) // unbox + print(x0a) // @types interface{} + print(x0a.Interface()) // @types int +} + func main() { reflectIndirect() reflectNewAt() reflectTypeOf() reflectTypeElem() + metareflection() } diff --git a/pointer/util.go b/pointer/util.go index 0d37a80c..aa30f585 100644 --- a/pointer/util.go +++ b/pointer/util.go @@ -44,6 +44,12 @@ func CanHaveDynamicTypes(T types.Type) bool { return false } +// isInterface reports whether T is an interface type. +func isInterface(T types.Type) bool { + _, ok := T.Underlying().(*types.Interface) + return ok +} + // mustDeref returns the element type of its argument, which must be a // pointer; panic ensues otherwise. func mustDeref(typ types.Type) types.Type { @@ -113,7 +119,7 @@ func (a *analysis) flatten(t types.Type) []*fieldInfo { switch t := t.(type) { case *types.Named: u := t.Underlying() - if _, ok := u.(*types.Interface); ok { + if isInterface(u) { // Debuggability hack: don't remove // the named type from interfaces as // they're very verbose.