From 633ab7426a906b72dcf6f1d54e87f4ae926dc4e1 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Sat, 5 Mar 2016 13:37:38 +0100 Subject: [PATCH] reflect: implement StructOf This change exposes a facility to create new struct types from a slice of reflect.StructFields. - reflect: first stab at implementing StructOf - reflect: tests for StructOf StructOf creates new struct types in the form of structTypeWithMethods to accomodate the GC (especially the uncommonType.methods slice field.) Creating struct types with embedded interfaces with unexported methods is not supported yet and will panic. Creating struct types with non-ASCII field names or types is not yet supported (see #15064.) Binaries' sizes for linux_amd64: old=tip (0104a31) old bytes new bytes delta bin/go 9911336 9915456 +0.04% reflect 781704 830048 +6.18% Updates #5748. Updates #15064. Change-Id: I3b8fd4fadd6ce3b1b922e284f0ae72a3a8e3ce44 Reviewed-on: https://go-review.googlesource.com/9251 Reviewed-by: David Crawshaw Run-TryBot: David Crawshaw --- src/cmd/compile/internal/gc/reflect.go | 2 +- src/reflect/all_test.go | 575 +++++++++++++++++++++++++ src/reflect/type.go | 473 +++++++++++++++++++- 3 files changed, 1043 insertions(+), 7 deletions(-) diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 4bad490a641..a2fdf0449ba 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -1307,7 +1307,7 @@ ok: // we want be able to find. if t.Sym == nil { switch t.Etype { - case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP: + case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT: slink := typelinksym(t) dsymptr(slink, 0, s, 0) ggloblsym(slink, int32(Widthptr), int16(dupok|obj.RODATA)) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 0ce6588e988..4a76ef86084 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -21,6 +21,8 @@ import ( "sync" "testing" "time" + "unicode" + "unicode/utf8" "unsafe" ) @@ -3958,6 +3960,579 @@ func TestSliceOfGC(t *testing.T) { } } +func TestStructOf(t *testing.T) { + // check construction and use of type not in binary + fields := []StructField{ + StructField{ + Name: "S", + Tag: "s", + Type: TypeOf(""), + }, + StructField{ + Name: "X", + Tag: "x", + Type: TypeOf(byte(0)), + }, + StructField{ + Name: "Y", + Type: TypeOf(uint64(0)), + }, + StructField{ + Name: "Z", + Type: TypeOf([3]uint16{}), + }, + } + + st := StructOf(fields) + v := New(st).Elem() + runtime.GC() + v.FieldByName("X").Set(ValueOf(byte(2))) + v.FieldByIndex([]int{1}).Set(ValueOf(byte(1))) + runtime.GC() + + s := fmt.Sprint(v.Interface()) + want := `{ 1 0 [0 0 0]}` + if s != want { + t.Errorf("constructed struct = %s, want %s", s, want) + } + + // check the size, alignment and field offsets + stt := TypeOf(struct { + String string + X byte + Y uint64 + Z [3]uint16 + }{}) + if st.Size() != stt.Size() { + t.Errorf("constructed struct size = %v, want %v", st.Size(), stt.Size()) + } + if st.Align() != stt.Align() { + t.Errorf("constructed struct align = %v, want %v", st.Align(), stt.Align()) + } + if st.FieldAlign() != stt.FieldAlign() { + t.Errorf("constructed struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) + } + for i := 0; i < st.NumField(); i++ { + o1 := st.Field(i).Offset + o2 := stt.Field(i).Offset + if o1 != o2 { + t.Errorf("constructed struct field %v offset = %v, want %v", i, o1, o2) + } + } + + // check duplicate names + shouldPanic(func() { + StructOf([]StructField{ + StructField{Name: "string", Type: TypeOf("")}, + StructField{Name: "string", Type: TypeOf("")}, + }) + }) + shouldPanic(func() { + StructOf([]StructField{ + StructField{Type: TypeOf("")}, + StructField{Name: "string", Type: TypeOf("")}, + }) + }) + shouldPanic(func() { + StructOf([]StructField{ + StructField{Type: TypeOf("")}, + StructField{Type: TypeOf("")}, + }) + }) + // check that type already in binary is found + checkSameType(t, Zero(StructOf(fields[2:3])).Interface(), struct{ Y uint64 }{}) +} + +func TestStructOfExportRules(t *testing.T) { + type S1 struct{} + type s2 struct{} + type ΦType struct{} + type φType struct{} + + testPanic := func(i int, mustPanic bool, f func()) { + defer func() { + err := recover() + if err == nil && mustPanic { + t.Errorf("test-%d did not panic", i) + } + if err != nil && !mustPanic { + t.Errorf("test-%d panicked: %v\n", i, err) + } + }() + f() + } + + for i, test := range []struct { + field StructField + mustPanic bool + exported bool + }{ + { + field: StructField{Name: "", Type: TypeOf(S1{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf((*S1)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(s2{})}, + mustPanic: false, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf((*s2)(nil))}, + mustPanic: false, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "S", Type: TypeOf(S1{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf(s2{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{})}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{})}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf(ΦType{})}, + mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(φType{})}, + mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported + exported: false, + }, + { + field: StructField{Name: "Φ", Type: TypeOf(0)}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "φ", Type: TypeOf(0)}, + mustPanic: false, + exported: false, + }, + } { + testPanic(i, test.mustPanic, func() { + typ := StructOf([]StructField{test.field}) + if typ == nil { + t.Errorf("test-%d: error creating struct type", i) + return + } + field := typ.Field(0) + n := field.Name + if n == "" { + n = field.Type.Name() + } + exported := isExported(n) + if exported != test.exported { + t.Errorf("test-%d: got exported=%v want exported=%v", exported, test.exported) + } + }) + } +} + +// isExported reports whether name is an exported Go symbol +// (that is, whether it begins with an upper-case letter). +// +func isExported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +func TestStructOfGC(t *testing.T) { + type T *uintptr + tt := TypeOf(T(nil)) + fields := []StructField{ + {Name: "X", Type: tt}, + {Name: "Y", Type: tt}, + } + st := StructOf(fields) + + const n = 10000 + var x []interface{} + for i := 0; i < n; i++ { + v := New(st).Elem() + for j := 0; j < v.NumField(); j++ { + p := new(uintptr) + *p = uintptr(i*n + j) + v.Field(j).Set(ValueOf(p).Convert(tt)) + } + x = append(x, v.Interface()) + } + runtime.GC() + + for i, xi := range x { + v := ValueOf(xi) + for j := 0; j < v.NumField(); j++ { + k := v.Field(j).Elem().Interface() + if k != uintptr(i*n+j) { + t.Errorf("lost x[%d].%c = %d, want %d", i, "XY"[j], k, i*n+j) + } + } + } +} + +func TestStructOfAlg(t *testing.T) { + st := StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf(int(0))}}) + v1 := New(st).Elem() + v2 := New(st).Elem() + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + v1.FieldByName("X").Set(ValueOf(int(1))) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + st = StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf([]int(nil))}}) + v1 = New(st).Elem() + shouldPanic(func() { _ = v1.Interface() == v1.Interface() }) +} + +func TestStructOfGenericAlg(t *testing.T) { + st1 := StructOf([]StructField{ + {Name: "X", Tag: "x", Type: TypeOf(int64(0))}, + {Name: "Y", Type: TypeOf(string(""))}, + }) + st := StructOf([]StructField{ + {Name: "S0", Type: st1}, + {Name: "S1", Type: st1}, + }) + + for _, table := range []struct { + rt Type + idx []int + }{ + { + rt: st, + idx: []int{0, 1}, + }, + { + rt: st1, + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([1]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([2]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(byte(0))}, + {Name: "ZZ", Type: TypeOf("")}, + }, + ), + idx: []int{2}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(int64(0))}, + {Name: "ZZ", Type: TypeOf("")}, + {Name: "AA", Type: TypeOf([1]int64{})}, + }, + ), + idx: []int{2}, + }, + } { + v1 := New(table.rt).Elem() + v2 := New(table.rt).Elem() + + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + + v1.FieldByIndex(table.idx).Set(ValueOf("abc")) + v2.FieldByIndex(table.idx).Set(ValueOf("def")) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + abc := "abc" + v1.FieldByIndex(table.idx).Set(ValueOf(abc)) + val := "+" + abc + "-" + v2.FieldByIndex(table.idx).Set(ValueOf(val[1:4])) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + // Test hash + m := MakeMap(MapOf(table.rt, TypeOf(int(0)))) + m.SetMapIndex(v1, ValueOf(1)) + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %#v and %#v have different hashes", i1, i2) + } + + v2.FieldByIndex(table.idx).Set(ValueOf("abc")) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %v and %v have different hashes", i1, i2) + } + } +} + +func TestStructOfDirectIface(t *testing.T) { + { + type T struct{ X [1]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(1, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 != 0 { + t.Errorf("got p1=%v. want=%v", p1, nil) + } + + if p2 != 0 { + t.Errorf("got p2=%v. want=%v", p2, nil) + } + } + { + type T struct{ X [0]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(0, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 == 0 { + t.Errorf("got p1=%v. want=not-%v", p1, nil) + } + + if p2 == 0 { + t.Errorf("got p2=%v. want=not-%v", p2, nil) + } + } +} + +type StructI int + +func (i StructI) Get() int { return int(i) } + +type StructIPtr int + +func (i *StructIPtr) Get() int { return int(*i) } + +func TestStructOfWithInterface(t *testing.T) { + const want = 42 + type Iface interface { + Get() int + } + for i, table := range []struct { + typ Type + val Value + impl bool + }{ + { + typ: TypeOf(StructI(want)), + val: ValueOf(StructI(want)), + impl: true, + }, + { + typ: PtrTo(TypeOf(StructI(want))), + val: ValueOf(func() interface{} { + v := StructI(want) + return &v + }()), + impl: true, + }, + { + typ: PtrTo(TypeOf(StructIPtr(want))), + val: ValueOf(func() interface{} { + v := StructIPtr(want) + return &v + }()), + impl: true, + }, + { + typ: TypeOf(StructIPtr(want)), + val: ValueOf(StructIPtr(want)), + impl: false, + }, + // { + // typ: TypeOf((*Iface)(nil)).Elem(), // FIXME(sbinet): fix method.ifn/tfn + // val: ValueOf(StructI(want)), + // impl: true, + // }, + } { + rt := StructOf( + []StructField{ + { + Name: "", + PkgPath: "", + Type: table.typ, + }, + }, + ) + rv := New(rt).Elem() + rv.Field(0).Set(table.val) + + if _, ok := rv.Interface().(Iface); ok != table.impl { + if table.impl { + t.Errorf("test-%d: type=%v fails to implement Iface.\n", i, table.typ) + } else { + t.Errorf("test-%d: type=%v should NOT implement Iface\n", table.typ) + } + continue + } + + if !table.impl { + continue + } + + v := rv.Interface().(Iface).Get() + if v != want { + t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, v, want) + } + + fct := rv.MethodByName("Get") + out := fct.Call(nil) + if !DeepEqual(out[0].Interface(), want) { + t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, out[0].Interface(), want) + } + } +} + func TestChanOf(t *testing.T) { // check construction and use of type not in binary type T string diff --git a/src/reflect/type.go b/src/reflect/type.go index c7a03138096..8f13acf26ee 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -484,6 +484,46 @@ func round(n, a uintptr) uintptr { return (n + a - 1) &^ (a - 1) } +func newName(n, tag, pkgPath string, exported bool) name { + if len(n) > 1<<16-1 { + panic("reflect.nameFrom: name too long: " + n) + } + if len(tag) > 1<<16-1 { + panic("reflect.nameFrom: tag too long: " + tag) + } + + var bits byte + l := 1 + 2 + len(n) + if exported { + bits |= 1 << 0 + } + if len(tag) > 0 { + l += 2 + len(tag) + bits |= 1 << 1 + } + if pkgPath != "" { + bits |= 1 << 2 + } + + b := make([]byte, l) + b[0] = bits + b[1] = uint8(len(n) >> 8) + b[2] = uint8(len(n)) + copy(b[3:], n) + if len(tag) > 0 { + tb := b[3+len(n):] + tb[0] = uint8(len(tag) >> 8) + tb[1] = uint8(len(tag)) + copy(tb[2:], tag) + } + + if pkgPath != "" { + panic("reflect: creating a name with a package path is not supported") + } + + return name{bytes: &b[0]} +} + /* * The compiler knows the exact layout of all the data structures above. * The compiler does not know about the data structures and methods below. @@ -562,11 +602,7 @@ func (t *rtype) uncommon() *uncommonType { } switch t.Kind() { case Struct: - type u struct { - structType - u uncommonType - } - return &(*u)(unsafe.Pointer(t)).u + return &(*structTypeWithMethods)(unsafe.Pointer(t)).u case Ptr: type u struct { ptrType @@ -1572,7 +1608,7 @@ func typesByString(s string) []*rtype { return ret } -// The lookupCache caches ChanOf, MapOf, and SliceOf lookups. +// The lookupCache caches ArrayOf, ChanOf, MapOf and SliceOf lookups. var lookupCache struct { sync.RWMutex m map[cacheKey]*rtype @@ -2112,6 +2148,431 @@ func SliceOf(t Type) Type { return cachePut(ckey, &slice.rtype) } +// structTypeWithMethods is a structType created at runtime with StructOf. +// It is needed to pin the []method slice from its associated uncommonType struct. +// Keep in sync with the memory layout of structType. +type structTypeWithMethods struct { + structType + u uncommonType +} + +// The structLookupCache caches StructOf lookups. +// StructOf does not share the common lookupCache since we need to pin +// the *structType and its associated *uncommonType (especially the +// []method slice field of that uncommonType.) +var structLookupCache struct { + sync.RWMutex + m map[uint32][]*structTypeWithMethods // keyed by hash calculated in StructOf +} + +// StructOf returns the struct type containing fields. +// The Offset and Index fields are ignored and computed as they would be +// by the compiler. +// +// StructOf does not support creating structs with UTF-8 field names or +// UTF-8 (embedded) type names. +// This limitation may be lifted eventually. +func StructOf(fields []StructField) Type { + var ( + hash = fnv1(0, []byte("struct {")...) + size uintptr + typalign uint8 + comparable = true + hashable = true + typ = new(structTypeWithMethods) + + fs = make([]structField, len(fields)) + repr = make([]byte, 0, 64) + fset = map[string]struct{}{} // fields' names + + hasPtr = false // records whether at least one struct-field is a pointer + hasGCProg = false // records whether a struct-field type has a GCProg + ) + + repr = append(repr, "struct {"...) + for i, field := range fields { + if field.Type == nil { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") + } + f := runtimeStructField(field) + ft := f.typ + if ft.kind&kindGCProg != 0 { + hasGCProg = true + } + if ft.pointers() { + hasPtr = true + } + + name := "" + // Update string and hash + if f.name.nameLen() > 0 { + hash = fnv1(hash, []byte(f.name.name())...) + repr = append(repr, (" " + f.name.name())...) + name = f.name.name() + } else { + // Embedded field + if f.typ.Kind() == Ptr { + // Embedded ** and *interface{} are illegal + elem := ft.Elem() + if k := elem.Kind(); k == Ptr || k == Interface { + panic("reflect.StructOf: illegal anonymous field type " + ft.string) + } + name = elem.String() + } else { + name = ft.string + } + // TODO(sbinet) check for syntactically impossible type names? + + switch f.typ.Kind() { + case Interface: + ift := (*interfaceType)(unsafe.Pointer(ft)) + for im, m := range ift.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + + var ( + ifield = i + imethod = im + ifn Value + tfn Value + ) + + if ft.kind&kindDirectIface != 0 { + tfn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + ifn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + + } else { + tfn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + ifn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = Indirect(in[0]) + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + + } + + typ.u.methods = append( + typ.u.methods, + method{ + name: m.name, + mtyp: m.typ, + ifn: unsafe.Pointer(&ifn), + tfn: unsafe.Pointer(&tfn), + }, + ) + } + case Ptr: + ptr := (*ptrType)(unsafe.Pointer(ft)) + if unt := ptr.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + if unt := ptr.elem.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + default: + if unt := ft.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + } + } + if _, dup := fset[name]; dup { + panic("reflect.StructOf: duplicate field " + name) + } + fset[name] = struct{}{} + + hash = fnv1(hash, byte(ft.hash>>24), byte(ft.hash>>16), byte(ft.hash>>8), byte(ft.hash)) + + repr = append(repr, (" " + ft.string)...) + if f.name.tagLen() > 0 { + hash = fnv1(hash, []byte(f.name.tag())...) + repr = append(repr, (" " + strconv.Quote(f.name.tag()))...) + } + if i < len(fields)-1 { + repr = append(repr, ';') + } + + comparable = comparable && (ft.alg.equal != nil) + hashable = hashable && (ft.alg.hash != nil) + + f.offset = align(size, uintptr(ft.align)) + if ft.align > typalign { + typalign = ft.align + } + size = f.offset + ft.size + + fs[i] = f + } + if len(fs) > 0 { + repr = append(repr, ' ') + } + repr = append(repr, '}') + hash = fnv1(hash, '}') + str := string(repr) + + // Round the size up to be a multiple of the alignment. + size = align(size, uintptr(typalign)) + + // Make the struct type. + var istruct interface{} = struct{}{} + prototype := *(**structType)(unsafe.Pointer(&istruct)) + typ.structType = *prototype + typ.structType.fields = fs + + // Look in cache + structLookupCache.RLock() + for _, t := range structLookupCache.m[hash] { + if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) { + structLookupCache.RUnlock() + return &t.rtype + } + } + structLookupCache.RUnlock() + + // not in cache, lock and retry + structLookupCache.Lock() + defer structLookupCache.Unlock() + if structLookupCache.m == nil { + structLookupCache.m = make(map[uint32][]*structTypeWithMethods) + } + for _, t := range structLookupCache.m[hash] { + if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) { + return &t.rtype + } + } + + // Look in known types. + for _, t := range typesByString(str) { + if haveIdenticalUnderlyingType(&typ.rtype, t) { + // even if 't' wasn't a structType with methods, we should be ok + // as the 'u uncommonType' field won't be accessed except when + // tflag&tflagUncommon is set. + tt := (*structTypeWithMethods)(unsafe.Pointer(t)) + structLookupCache.m[hash] = append(structLookupCache.m[hash], tt) + return &tt.rtype + } + } + + typ.string = str + typ.hash = hash + typ.size = size + typ.align = typalign + typ.fieldAlign = typalign + if len(typ.u.methods) > 0 { + typ.tflag |= tflagUncommon + } + if !hasPtr { + typ.kind |= kindNoPointers + } else { + typ.kind &^= kindNoPointers + } + + if hasGCProg { + lastPtrField := 0 + for i, ft := range fs { + if ft.typ.pointers() { + lastPtrField = i + } + } + prog := []byte{0, 0, 0, 0} // will be length of prog + for i, ft := range fs { + if i > lastPtrField { + // gcprog should not include anything for any field after + // the last field that contains pointer data + break + } + // FIXME(sbinet) handle padding, fields smaller than a word + elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:] + elemPtrs := ft.typ.ptrdata / ptrSize + switch { + case ft.typ.kind&kindGCProg == 0 && ft.typ.ptrdata != 0: + // Element is small with pointer mask; use as literal bits. + mask := elemGC + // Emit 120-bit chunks of full bytes (max is 127 but we avoid using partial bytes). + var n uintptr + for n := elemPtrs; n > 120; n -= 120 { + prog = append(prog, 120) + prog = append(prog, mask[:15]...) + mask = mask[15:] + } + prog = append(prog, byte(n)) + prog = append(prog, mask[:(n+7)/8]...) + case ft.typ.kind&kindGCProg != 0: + // Element has GC program; emit one element. + elemProg := elemGC[4 : 4+*(*uint32)(unsafe.Pointer(&elemGC[0]))-1] + prog = append(prog, elemProg...) + } + // Pad from ptrdata to size. + elemWords := ft.typ.size / ptrSize + if elemPtrs < elemWords { + // Emit literal 0 bit, then repeat as needed. + prog = append(prog, 0x01, 0x00) + if elemPtrs+1 < elemWords { + prog = append(prog, 0x81) + prog = appendVarint(prog, elemWords-elemPtrs-1) + } + } + } + *(*uint32)(unsafe.Pointer(&prog[0])) = uint32(len(prog) - 4) + typ.kind |= kindGCProg + typ.gcdata = &prog[0] + } else { + typ.kind &^= kindGCProg + bv := new(bitVector) + addTypeBits(bv, 0, typ.common()) + if len(bv.data) > 0 { + typ.gcdata = &bv.data[0] + } + } + typ.ptrdata = typeptrdata(typ.common()) + typ.alg = new(typeAlg) + if hashable { + typ.alg.hash = func(p unsafe.Pointer, seed uintptr) uintptr { + o := seed + for _, ft := range typ.fields { + pi := unsafe.Pointer(uintptr(p) + ft.offset) + o = ft.typ.alg.hash(pi, o) + } + return o + } + } + + if comparable { + typ.alg.equal = func(p, q unsafe.Pointer) bool { + for _, ft := range typ.fields { + pi := unsafe.Pointer(uintptr(p) + ft.offset) + qi := unsafe.Pointer(uintptr(q) + ft.offset) + if !ft.typ.alg.equal(pi, qi) { + return false + } + } + return true + } + } + + switch { + case len(fs) == 1 && !ifaceIndir(fs[0].typ): + // structs of 1 direct iface type can be direct + typ.kind |= kindDirectIface + default: + typ.kind &^= kindDirectIface + } + + structLookupCache.m[hash] = append(structLookupCache.m[hash], typ) + return &typ.rtype +} + +func runtimeStructField(field StructField) structField { + exported := field.PkgPath == "" + if field.Name == "" { + t := field.Type + if t.Kind() == Ptr { + t = t.Elem() + } + exported = isExported(t.Name()) + } else if exported { + b0 := field.Name[0] + if ('a' <= b0 && b0 <= 'z') || b0 == '_' { + panic("reflect.StructOf: field \"" + field.Name + "\" is unexported but has no PkgPath") + } + } + + return structField{ + name: newName(field.Name, string(field.Tag), field.PkgPath, exported), + typ: field.Type.common(), + offset: 0, + } +} + +func isExported(s string) bool { + if s == "" { + return false + } + // FIXME(sbinet): handle utf8/runes (see https://golang.org/issue/15064) + // TODO: turn rtype.string into a reflect.name type, and put the exported + // bit on there which can be checked here with field.Type.(*rtype).string.isExported() + // When done, remove the documented limitation of StructOf. + r := s[0] + switch { + case 'A' <= r && r <= 'Z': + return true + case r == '_' || 'a' <= r && r <= 'z': + return false + default: + panic("reflect.StructOf: creating a struct with UTF-8 fields is not supported yet") + } +} + +// typeptrdata returns the length in bytes of the prefix of t +// containing pointer data. Anything after this offset is scalar data. +// keep in sync with ../cmd/compile/internal/gc/reflect.go +func typeptrdata(t *rtype) uintptr { + if !t.pointers() { + return 0 + } + switch t.Kind() { + case Struct: + st := (*structType)(unsafe.Pointer(t)) + // find the last field that has pointers. + field := 0 + for i := range st.fields { + ft := st.fields[i].typ + if ft.pointers() { + field = i + } + } + f := st.fields[field] + return f.offset + f.typ.ptrdata + + default: + panic("reflect.typeptrdata: unexpected type, " + t.String()) + } + return 0 +} + // See cmd/compile/internal/gc/reflect.go for derivation of constant. const maxPtrmaskBytes = 2048