1
0
mirror of https://github.com/golang/go synced 2024-10-01 01:38:33 -06:00
go/pointer/reflect.go
Alan Donovan 3b5de067a1 go.tools/pointer: reflection, part 1: maps, and some core features.
Core:
        reflect.TypeOf
        reflect.ValueOf
        reflect.Zero
        reflect.Value.Interface
Maps:
        (reflect.Value).MapIndex
        (reflect.Value).MapKeys
        (reflect.Value).SetMapIndex
        (*reflect.rtype).Elem
        (*reflect.rtype).Key

+ tests:
  pointer/testdata/mapreflect.go.
  oracle/testdata/src/main/reflection.go.

Interface objects (T, V...) have been renamed "tagged objects".

Abstraction: we model reflect.Value similar to
interface{}---as a pointer that points only to tagged
objects---but a reflect.Value may also point to an "indirect
tagged object", one in which the payload V is of type *T not T.
These are required because reflect.Values can hold lvalues,
e.g. when derived via Field() or Elem(), though we won't use
them till we get to structs and pointers.

Solving: each reflection intrinsic defines a new constraint
and resolution rule.  Because of the nature of reflection,
generalizing across types, the resolution rules dynamically
create additional complex constraints during solving, where
previously only simple (copy) constraints were created.
This requires some solver changes:

  The work done before the main solver loop (to attach new
  constraints to the graph) is now done before each iteration,
  in processNewConstraints.

  Its loop over constraints is broken into two passes:
  the first handles base (addr-of) constraints,
  the second handles simple and complex constraints.

  constraint.init() has been inlined.  The only behaviour that
  varies across constraints is ptr()

Sadly this will pessimize presolver optimisations, when we get
there; such is the price of reflection.

Objects: reflection intrinsics create objects (i.e. cause
memory allocations) with no SSA operation.  We will represent
them as the cgnode of the instrinsic (e.g. reflect.New), so we
extend Labels and node.data to represent objects as a product
(not sum) of ssa.Value and cgnode and pull this out into its
own type, struct object.  This simplifies a number of
invariants and saves space.  The ntObject flag is now
represented by obj!=nil; the other flags are moved into
object.

cgnodes are now always recorded in objects/Labels for which it
is appropriate (all but those for globals, constants and the
shared contours for functions).

Also:
- Prepopulate the flattenMemo cache to consider reflect.Value
  a fake pointer, not a struct.
- Improve accessors and documentation on type Label.
- @conctypes assertions renamed @types (since dyn. types needn't be concrete).
- add oracle 'describe' test on an interface (missing, an oversight).

R=crawshaw
CC=golang-dev
https://golang.org/cl/13418048
2013-09-16 09:49:10 -04:00

471 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pointer
// This file implements the generation and resolution rules for
// constraints arising from the use of reflection in the target
// program. See doc.go for explanation of the representation.
//
// TODO(adonovan): fix: most of the reflect API permits implicit
// conversions due to assignability, e.g. m.MapIndex(k) is ok if T(k)
// is assignable to T(M).key. It's not yet clear how best to model
// that.
//
// To avoid proliferation of equivalent labels, instrinsics should
// memoize as much as possible, like TypeOf and Zero do for their
// tagged objects.
//
// TODO(adonovan): all {} functions are TODO.
import (
"fmt"
"code.google.com/p/go.tools/go/types"
)
// -------------------- (reflect.Value) --------------------
func ext۰reflect۰Value۰Addr(a *analysis, cgn *cgnode) {}
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 ext۰reflect۰Value۰Elem(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰Field(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰FieldByIndex(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰FieldByName(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰FieldByNameFunc(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰Index(a *analysis, cgn *cgnode) {}
// ---------- func (Value).Interface() Value ----------
// result = rv.Interface()
type rVInterfaceConstraint struct {
rv nodeid // (ptr)
result nodeid
}
func (c *rVInterfaceConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.Interface()", c.result, c.rv)
}
func (c *rVInterfaceConstraint) ptr() nodeid {
return c.rv
}
func (c *rVInterfaceConstraint) solve(a *analysis, _ *node, delta nodeset) {
resultPts := &a.nodes[c.result].pts
changed := false
for obj := range delta {
tDyn, _, indirect := a.taggedValue(obj)
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 resultPts.add(obj) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Value۰Interface(a *analysis, cgn *cgnode) {
a.addConstraint(&rVInterfaceConstraint{
rv: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// ---------- func (Value).MapIndex(Value) Value ----------
// result = rv.MapIndex(key)
type rVMapIndexConstraint struct {
cgn *cgnode
rv nodeid // (ptr)
result nodeid
}
func (c *rVMapIndexConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapIndex(_)", c.result, c.rv)
}
func (c *rVMapIndexConstraint) ptr() nodeid {
return c.rv
}
func (c *rVMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
tDyn, m, indirect := a.taggedValue(obj)
tMap, _ := tDyn.(*types.Map)
if tMap == nil {
continue // not a map
}
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.
panic("indirect tagged object")
}
vObj := a.makeTagged(tMap.Elem(), c.cgn, nil)
a.loadOffset(vObj+1, m, a.sizeof(tMap.Key()), a.sizeof(tMap.Elem()))
if a.nodes[c.result].pts.add(vObj) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Value۰MapIndex(a *analysis, cgn *cgnode) {
a.addConstraint(&rVMapIndexConstraint{
cgn: cgn,
rv: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// ---------- func (Value).MapKeys() []Value ----------
// result = rv.MapKeys()
type rVMapKeysConstraint struct {
cgn *cgnode
rv nodeid // (ptr)
result nodeid
}
func (c *rVMapKeysConstraint) String() string {
return fmt.Sprintf("n%d = reflect n%d.MapKeys()", c.result, c.rv)
}
func (c *rVMapKeysConstraint) ptr() nodeid {
return c.rv
}
func (c *rVMapKeysConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
tDyn, m, indirect := a.taggedValue(obj)
tMap, _ := tDyn.(*types.Map)
if tMap == nil {
continue // not a map
}
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.
panic("indirect tagged object")
}
kObj := a.makeTagged(tMap.Key(), c.cgn, nil)
a.load(kObj+1, m, a.sizeof(tMap.Key()))
if a.nodes[c.result].pts.add(kObj) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Value۰MapKeys(a *analysis, cgn *cgnode) {
// Allocate an array for the result.
obj := a.nextNode()
a.addNodes(types.NewArray(a.reflectValueObj.Type(), 1), "reflect.MapKeys result")
a.endObject(obj, cgn, nil)
a.addressOf(a.funcResults(cgn.obj), obj)
// resolution rule attached to rv
a.addConstraint(&rVMapKeysConstraint{
cgn: cgn,
rv: a.funcParams(cgn.obj),
result: obj + 1, // result is stored in array elems
})
}
func ext۰reflect۰Value۰Method(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰MethodByName(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰Set(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰SetBytes(a *analysis, cgn *cgnode) {}
// ---------- func (Value).SetMapIndex(k Value, v Value) ----------
// rv.SetMapIndex(k, v)
type rVSetMapIndexConstraint struct {
cgn *cgnode
rv nodeid // (ptr)
k nodeid
v nodeid
}
func (c *rVSetMapIndexConstraint) String() string {
return fmt.Sprintf("reflect n%d.SetMapIndex(n%d, n%d)", c.rv, c.k, c.v)
}
func (c *rVSetMapIndexConstraint) ptr() nodeid {
return c.rv
}
func (c *rVSetMapIndexConstraint) solve(a *analysis, _ *node, delta nodeset) {
for obj := range delta {
tDyn, m, indirect := a.taggedValue(obj)
tMap, _ := tDyn.(*types.Map)
if tMap == nil {
continue // not a map
}
if indirect {
// TODO(adonovan): we'll need to implement this
// when we start creating indirect tagged objects.
panic("indirect tagged object")
}
ksize := a.sizeof(tMap.Key())
// Extract k Value's payload to ktmp, then store to map key.
ktmp := a.addNodes(tMap.Key(), "SetMapIndex.ktmp")
a.addConstraint(&typeAssertConstraint{tMap.Key(), ktmp, c.k})
a.store(m, ktmp, ksize)
// Extract v Value's payload to vtmp, then store to map value.
vtmp := a.addNodes(tMap.Elem(), "SetMapIndex.vtmp")
a.addConstraint(&typeAssertConstraint{tMap.Elem(), vtmp, c.v})
a.storeOffset(m, vtmp, ksize, a.sizeof(tMap.Elem()))
}
}
func ext۰reflect۰Value۰SetMapIndex(a *analysis, cgn *cgnode) {
// resolution rule attached to rv
rv := a.funcParams(cgn.obj)
a.addConstraint(&rVSetMapIndexConstraint{
cgn: cgn,
rv: rv,
k: rv + 1,
v: rv + 2,
})
}
func ext۰reflect۰Value۰SetPointer(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Value۰Slice(a *analysis, cgn *cgnode) {}
// -------------------- Standalone reflect functions --------------------
func ext۰reflect۰Append(a *analysis, cgn *cgnode) {}
func ext۰reflect۰AppendSlice(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Copy(a *analysis, cgn *cgnode) {}
func ext۰reflect۰ChanOf(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Indirect(a *analysis, cgn *cgnode) {}
func ext۰reflect۰MakeChan(a *analysis, cgn *cgnode) {}
func ext۰reflect۰MakeFunc(a *analysis, cgn *cgnode) {}
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 ext۰reflect۰New(a *analysis, cgn *cgnode) {}
func ext۰reflect۰NewAt(a *analysis, cgn *cgnode) {}
func ext۰reflect۰PtrTo(a *analysis, cgn *cgnode) {}
func ext۰reflect۰Select(a *analysis, cgn *cgnode) {}
func ext۰reflect۰SliceOf(a *analysis, cgn *cgnode) {}
// ---------- func TypeOf(v Value) Type ----------
// result = TypeOf(v)
type reflectTypeOfConstraint struct {
cgn *cgnode
v nodeid // (ptr)
result nodeid
}
func (c *reflectTypeOfConstraint) String() string {
return fmt.Sprintf("n%d = reflect.TypeOf(n%d)", c.result, c.v)
}
func (c *reflectTypeOfConstraint) ptr() nodeid {
return c.v
}
func (c *reflectTypeOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
tDyn, _, _ := a.taggedValue(obj)
if tDyn == nil {
panic("not a tagged value")
}
if a.nodes[c.result].pts.add(a.makeRtype(tDyn)) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰TypeOf(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectTypeOfConstraint{
cgn: cgn,
v: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// ---------- func ValueOf(interface{}) Value ----------
func ext۰reflect۰ValueOf(a *analysis, cgn *cgnode) {
// TODO(adonovan): when we start creating indirect tagged
// objects, we'll need to handle them specially here since
// they must never appear in the PTS of an interface{}.
a.copy(a.funcResults(cgn.obj), a.funcParams(cgn.obj), 1)
}
// ---------- func Zero(Type) Value ----------
// result = Zero(t)
type reflectZeroConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
}
func (c *reflectZeroConstraint) String() string {
return fmt.Sprintf("n%d = reflect.Zero(n%d)", c.result, c.t)
}
func (c *reflectZeroConstraint) ptr() nodeid {
return c.t
}
func (c *reflectZeroConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
tDyn, v, _ := a.taggedValue(obj)
if tDyn != a.reflectRtype {
panic("not a *reflect.rtype-tagged value")
}
T := a.nodes[v].typ
// memoize using a.reflectZeros[T]
var id nodeid
if z := a.reflectZeros.At(T); false && z != nil {
id = z.(nodeid)
} else {
id = a.makeTagged(T, c.cgn, nil)
a.reflectZeros.Set(T, id)
}
if a.nodes[c.result].pts.add(id) {
changed = true
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰Zero(a *analysis, cgn *cgnode) {
a.addConstraint(&reflectZeroConstraint{
cgn: cgn,
t: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
// -------------------- (*reflect.rtype) methods --------------------
// ---------- func (*rtype) Elem() Type ----------
// result = Elem(t)
type rtypeElemConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
}
func (c *rtypeElemConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).Elem(n%d)", c.result, c.t)
}
func (c *rtypeElemConstraint) ptr() nodeid {
return c.t
}
func (c *rtypeElemConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
T := a.nodes[obj].typ // assume obj is an *rtype
// Works for *types.{Map,Chan,Array,Slice,Pointer}.
if T, ok := T.Underlying().(interface {
Elem() types.Type
}); ok {
if a.nodes[c.result].pts.add(a.makeRtype(T.Elem())) {
changed = true
}
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰rtype۰Elem(a *analysis, cgn *cgnode) {
a.addConstraint(&rtypeElemConstraint{
cgn: cgn,
t: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
func ext۰reflect۰rtype۰Field(a *analysis, cgn *cgnode) {}
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 ext۰reflect۰rtype۰In(a *analysis, cgn *cgnode) {}
// ---------- func (*rtype) Key() Type ----------
// result = Key(t)
type rtypeKeyConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
}
func (c *rtypeKeyConstraint) String() string {
return fmt.Sprintf("n%d = (*reflect.rtype).Key(n%d)", c.result, c.t)
}
func (c *rtypeKeyConstraint) ptr() nodeid {
return c.t
}
func (c *rtypeKeyConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for obj := range delta {
T := a.nodes[obj].typ // assume obj is an *rtype
if tMap, ok := T.Underlying().(*types.Map); ok {
if a.nodes[c.result].pts.add(a.makeRtype(tMap.Key())) {
changed = true
}
}
}
if changed {
a.addWork(c.result)
}
}
func ext۰reflect۰rtype۰Key(a *analysis, cgn *cgnode) {
a.addConstraint(&rtypeKeyConstraint{
cgn: cgn,
t: a.funcParams(cgn.obj),
result: a.funcResults(cgn.obj),
})
}
func ext۰reflect۰rtype۰Method(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰MethodByName(a *analysis, cgn *cgnode) {}
func ext۰reflect۰rtype۰Out(a *analysis, cgn *cgnode) {}