• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package proptools
16
17import (
18	"fmt"
19	"reflect"
20	"sync"
21)
22
23func CloneProperties(structValue reflect.Value) reflect.Value {
24	result := reflect.New(structValue.Type())
25	CopyProperties(result.Elem(), structValue)
26	return result
27}
28
29func CopyProperties(dstValue, srcValue reflect.Value) {
30	typ := dstValue.Type()
31	if srcValue.Type() != typ {
32		panic(fmt.Errorf("can't copy mismatching types (%s <- %s)",
33			dstValue.Kind(), srcValue.Kind()))
34	}
35
36	for i, field := range typeFields(typ) {
37		if field.PkgPath != "" {
38			panic(fmt.Errorf("can't copy a private field %q", field.Name))
39		}
40
41		srcFieldValue := srcValue.Field(i)
42		dstFieldValue := dstValue.Field(i)
43		dstFieldInterfaceValue := reflect.Value{}
44		origDstFieldValue := dstFieldValue
45
46		switch srcFieldValue.Kind() {
47		case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
48			dstFieldValue.Set(srcFieldValue)
49		case reflect.Struct:
50			CopyProperties(dstFieldValue, srcFieldValue)
51		case reflect.Slice:
52			if !srcFieldValue.IsNil() {
53				if srcFieldValue != dstFieldValue {
54					newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(),
55						srcFieldValue.Len())
56					reflect.Copy(newSlice, srcFieldValue)
57					dstFieldValue.Set(newSlice)
58				}
59			} else {
60				dstFieldValue.Set(srcFieldValue)
61			}
62		case reflect.Interface:
63			if srcFieldValue.IsNil() {
64				dstFieldValue.Set(srcFieldValue)
65				break
66			}
67
68			srcFieldValue = srcFieldValue.Elem()
69
70			if srcFieldValue.Kind() != reflect.Ptr {
71				panic(fmt.Errorf("can't clone field %q: interface refers to a non-pointer",
72					field.Name))
73			}
74			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
75				panic(fmt.Errorf("can't clone field %q: interface points to a non-struct",
76					field.Name))
77			}
78
79			if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() {
80				// We can't use the existing destination allocation, so
81				// clone a new one.
82				newValue := reflect.New(srcFieldValue.Type()).Elem()
83				dstFieldValue.Set(newValue)
84				dstFieldInterfaceValue = dstFieldValue
85				dstFieldValue = newValue
86			} else {
87				dstFieldValue = dstFieldValue.Elem()
88			}
89			fallthrough
90		case reflect.Ptr:
91			if srcFieldValue.IsNil() {
92				origDstFieldValue.Set(srcFieldValue)
93				break
94			}
95
96			srcFieldValue := srcFieldValue.Elem()
97
98			switch srcFieldValue.Kind() {
99			case reflect.Struct:
100				if !dstFieldValue.IsNil() {
101					// Re-use the existing allocation.
102					CopyProperties(dstFieldValue.Elem(), srcFieldValue)
103					break
104				} else {
105					newValue := CloneProperties(srcFieldValue)
106					if dstFieldInterfaceValue.IsValid() {
107						dstFieldInterfaceValue.Set(newValue)
108					} else {
109						origDstFieldValue.Set(newValue)
110					}
111				}
112			case reflect.Bool, reflect.Int64, reflect.String:
113				newValue := reflect.New(srcFieldValue.Type())
114				newValue.Elem().Set(srcFieldValue)
115				origDstFieldValue.Set(newValue)
116			default:
117				panic(fmt.Errorf("can't clone field %q: points to a %s",
118					field.Name, srcFieldValue.Kind()))
119			}
120		default:
121			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
122				field.Name, srcFieldValue.Kind()))
123		}
124	}
125}
126
127func ZeroProperties(structValue reflect.Value) {
128	typ := structValue.Type()
129
130	for i, field := range typeFields(typ) {
131		if field.PkgPath != "" {
132			// The field is not exported so just skip it.
133			continue
134		}
135
136		fieldValue := structValue.Field(i)
137
138		switch fieldValue.Kind() {
139		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
140			fieldValue.Set(reflect.Zero(fieldValue.Type()))
141		case reflect.Interface:
142			if fieldValue.IsNil() {
143				break
144			}
145
146			// We leave the pointer intact and zero out the struct that's
147			// pointed to.
148			fieldValue = fieldValue.Elem()
149			if fieldValue.Kind() != reflect.Ptr {
150				panic(fmt.Errorf("can't zero field %q: interface refers to a non-pointer",
151					field.Name))
152			}
153			if fieldValue.Type().Elem().Kind() != reflect.Struct {
154				panic(fmt.Errorf("can't zero field %q: interface points to a non-struct",
155					field.Name))
156			}
157			fallthrough
158		case reflect.Ptr:
159			switch fieldValue.Type().Elem().Kind() {
160			case reflect.Struct:
161				if fieldValue.IsNil() {
162					break
163				}
164				ZeroProperties(fieldValue.Elem())
165			case reflect.Bool, reflect.Int64, reflect.String:
166				fieldValue.Set(reflect.Zero(fieldValue.Type()))
167			default:
168				panic(fmt.Errorf("can't zero field %q: points to a %s",
169					field.Name, fieldValue.Elem().Kind()))
170			}
171		case reflect.Struct:
172			ZeroProperties(fieldValue)
173		default:
174			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
175				field.Name, fieldValue.Kind()))
176		}
177	}
178}
179
180func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
181	result := reflect.New(structValue.Type())
182	cloneEmptyProperties(result.Elem(), structValue)
183	return result
184}
185
186func cloneEmptyProperties(dstValue, srcValue reflect.Value) {
187	typ := srcValue.Type()
188	for i, field := range typeFields(typ) {
189		if field.PkgPath != "" {
190			// The field is not exported so just skip it.
191			continue
192		}
193
194		srcFieldValue := srcValue.Field(i)
195		dstFieldValue := dstValue.Field(i)
196		dstFieldInterfaceValue := reflect.Value{}
197
198		switch srcFieldValue.Kind() {
199		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
200			// Nothing
201		case reflect.Struct:
202			cloneEmptyProperties(dstFieldValue, srcFieldValue)
203		case reflect.Interface:
204			if srcFieldValue.IsNil() {
205				break
206			}
207
208			srcFieldValue = srcFieldValue.Elem()
209			if srcFieldValue.Kind() != reflect.Ptr {
210				panic(fmt.Errorf("can't clone empty field %q: interface refers to a non-pointer",
211					field.Name))
212			}
213			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
214				panic(fmt.Errorf("can't clone empty field %q: interface points to a non-struct",
215					field.Name))
216			}
217
218			newValue := reflect.New(srcFieldValue.Type()).Elem()
219			dstFieldValue.Set(newValue)
220			dstFieldInterfaceValue = dstFieldValue
221			dstFieldValue = newValue
222			fallthrough
223		case reflect.Ptr:
224			switch srcFieldValue.Type().Elem().Kind() {
225			case reflect.Struct:
226				if srcFieldValue.IsNil() {
227					break
228				}
229				newValue := CloneEmptyProperties(srcFieldValue.Elem())
230				if dstFieldInterfaceValue.IsValid() {
231					dstFieldInterfaceValue.Set(newValue)
232				} else {
233					dstFieldValue.Set(newValue)
234				}
235			case reflect.Bool, reflect.Int64, reflect.String:
236				// Nothing
237			default:
238				panic(fmt.Errorf("can't clone empty field %q: points to a %s",
239					field.Name, srcFieldValue.Elem().Kind()))
240			}
241
242		default:
243			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
244				field.Name, srcFieldValue.Kind()))
245		}
246	}
247}
248
249var typeFieldCache sync.Map
250
251func typeFields(typ reflect.Type) []reflect.StructField {
252	// reflect.Type.Field allocates a []int{} to hold the index every time it is called, which ends up
253	// being a significant portion of the GC pressure.  It can't reuse the same one in case a caller
254	// modifies the backing array through the slice.  Since we don't modify it, cache the result
255	// locally to reduce allocations.
256
257	// Fast path
258	if typeFields, ok := typeFieldCache.Load(typ); ok {
259		return typeFields.([]reflect.StructField)
260	}
261
262	// Slow path
263	typeFields := make([]reflect.StructField, typ.NumField())
264
265	for i := range typeFields {
266		typeFields[i] = typ.Field(i)
267	}
268
269	typeFieldCache.Store(typ, typeFields)
270
271	return typeFields
272}
273