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.