From 4eb375fc501071dce97d5f15215c933260ed7aa1 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Wed, 22 Apr 2015 11:19:27 +0200 Subject: [PATCH] reflect: implement StructOf This change exposes a facility to create new struct types from a slice of reflect.StructFields. - reflect: implement StructOf - reflect: tests for StructOf - runtime: generate typelinks for structs Fixes #5748. Change-Id: I3b8fd4fadd6ce3b1b922e284f0ae72a3a8e3ce44 --- src/cmd/internal/gc/reflect.go | 2 +- src/reflect/all_test.go | 313 +++++++++++++++++++++++++++++++++ src/reflect/type.go | 191 +++++++++++++++++++- 3 files changed, 504 insertions(+), 2 deletions(-) diff --git a/src/cmd/internal/gc/reflect.go b/src/cmd/internal/gc/reflect.go index 47697befba0c85..a6333d8c4b19cc 100644 --- a/src/cmd/internal/gc/reflect.go +++ b/src/cmd/internal/gc/reflect.go @@ -1226,7 +1226,7 @@ ok: break } fallthrough - case TARRAY, TCHAN, TFUNC, TMAP: + case TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT: slink := typelinksym(t) dsymptr(slink, 0, s, 0) ggloblsym(slink, int32(Widthptr), int8(dupok|obj.RODATA)) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 877b2efd847e0e..9a9e0dfddc1cb0 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -3693,6 +3693,319 @@ 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 TestStructOfGC(t *testing.T) { + type T *uintptr + tt := TypeOf(T(nil)) + fields := []StructField{ + {Name: "X", Type: TypeOf(T(nil))}, + {Name: "Y", Type: TypeOf(T(nil))}, + } + 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 += 1 { + 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 += 1 { + 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("a" + "b" + "c")) + 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) + } + } +} + 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 04485235aa96dc..9871305f545f16 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -1355,7 +1355,7 @@ func typesByString(s string) []*rtype { return ret } -// The lookupCache caches ChanOf, MapOf, and SliceOf lookups. +// The lookupCache caches ArrayOf, ChanOf, MapOf, StructOf and SliceOf lookups. var lookupCache struct { sync.RWMutex m map[cacheKey]*rtype @@ -1873,6 +1873,195 @@ func SliceOf(t Type) Type { return cachePut(ckey, &slice.rtype) } +// StructOf returns the struct type containing fields. Field offsets are ignored +// and computed as they would be by the compiler. +func StructOf(fields []StructField) Type { + var ( + hash = fnv1(0, []byte("empty")...) + size uintptr + typalign uint8 + comparable = true + hashable = true + + fs = make([]structField, len(fields)) + repr = make([]byte, 0, 64) + seen = map[string]struct{}{} + ) + + repr = append(repr, "struct {"...) + for i, field := range fields { + f := runtimeStructField(field) + ft := f.typ + + name := "" + // Update string and hash + if f.name != nil { + hash = fnv1(hash, []byte(*f.name)...) + repr = append(repr, (" " + *f.name)...) + name = *f.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 syntacticaly impossible type names? + } + if _, dup := seen[name]; dup { + panic("reflect.StructOf: duplicate field " + name) + } + seen[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.tag != nil { + hash = fnv1(hash, []byte(*f.tag)...) + repr = append(repr, (" " + strconv.Quote(*f.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, '}') + 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 := new(structType) + *typ = *prototype + typ.fields = fs + + // Create a key. + // There is a chance of a hash collision. + ckey := cacheKey{Struct, nil, nil, uintptr(hash)} + if len(fs) >= 1 { + ckey.t1 = fs[0].typ + } + if len(fs) >= 2 { + ckey.t2 = fs[1].typ + } + + if t := cacheGet(ckey); t != nil { + if haveIdenticalUnderlyingType(t.common(), &typ.rtype) { + return t + } + } + + // Look in known types. + for _, t := range typesByString(str) { + if haveIdenticalUnderlyingType(t.common(), &typ.rtype) { + return cachePut(ckey, t) + } + } + + typ.string = &str + typ.hash = hash + typ.size = size + typ.align = typalign + typ.fieldAlign = typalign + typ.uncommonType = nil + if typ.size > 0 { + typ.zero = unsafe.Pointer(&make([]byte, typ.size)[0]) + } + + // ptrToThis must be nil for a new pointer type to be synthesized. + typ.ptrToThis = nil + typ.ptrToThis = typ.rtype.ptrTo() + + var gc gcProg + for _, ft := range fs { + // FIXME(sbinet) handle padding, fields smaller than a word + gc.appendProg(ft.typ) + } + + var hasPtr bool + typ.gc[0], hasPtr = gc.finalize() + if !hasPtr { + typ.kind |= kindNoPointers + } else { + typ.kind &^= kindNoPointers + } + + 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 + } + + return cachePut(ckey, &typ.rtype) +} + +func runtimeStructField(field StructField) structField { + return structField{ + name: strPtrOrNil(field.Name), + pkgPath: strPtrOrNil(field.PkgPath), + typ: field.Type.common(), + tag: strPtrOrNil(string(field.Tag)), + offset: 0, + } +} + +func strPtrOrNil(str string) *string { + if str == "" { + return nil + } + ret := new(string) + *ret = str + return ret +} + // ArrayOf returns the array type with the given count and element type. // For example, if t represents int, ArrayOf(5, t) represents [5]int. //