1
0
mirror of https://github.com/golang/go synced 2024-11-18 20:04:52 -07:00

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
This commit is contained in:
Alan Donovan 2013-10-29 21:57:53 -04:00
parent e1b710c31e
commit 94c387c610
12 changed files with 326 additions and 92 deletions

View File

@ -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
}

View File

@ -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.

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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,
})
}

View File

@ -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,

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()
}

View File

@ -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.