• 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	"sync/atomic"
22)
23
24func CloneProperties(structValue reflect.Value) reflect.Value {
25	result := reflect.New(structValue.Type())
26	CopyProperties(result.Elem(), structValue)
27	return result
28}
29
30func CopyProperties(dstValue, srcValue reflect.Value) {
31	typ := dstValue.Type()
32	if srcValue.Type() != typ {
33		panic(fmt.Errorf("can't copy mismatching types (%s <- %s)",
34			dstValue.Kind(), srcValue.Kind()))
35	}
36
37	for i, field := range typeFields(typ) {
38		if field.PkgPath != "" {
39			// The field is not exported so just skip it.
40			continue
41		}
42
43		srcFieldValue := srcValue.Field(i)
44		dstFieldValue := dstValue.Field(i)
45		dstFieldInterfaceValue := reflect.Value{}
46		origDstFieldValue := dstFieldValue
47
48		switch srcFieldValue.Kind() {
49		case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
50			dstFieldValue.Set(srcFieldValue)
51		case reflect.Struct:
52			CopyProperties(dstFieldValue, srcFieldValue)
53		case reflect.Slice:
54			if !srcFieldValue.IsNil() {
55				if field.Type.Elem().Kind() != reflect.String {
56					panic(fmt.Errorf("can't copy field %q: slice elements are not strings", field.Name))
57				}
58				if srcFieldValue != dstFieldValue {
59					newSlice := reflect.MakeSlice(field.Type, srcFieldValue.Len(),
60						srcFieldValue.Len())
61					reflect.Copy(newSlice, srcFieldValue)
62					dstFieldValue.Set(newSlice)
63				}
64			} else {
65				dstFieldValue.Set(srcFieldValue)
66			}
67		case reflect.Interface:
68			if srcFieldValue.IsNil() {
69				dstFieldValue.Set(srcFieldValue)
70				break
71			}
72
73			srcFieldValue = srcFieldValue.Elem()
74
75			if srcFieldValue.Kind() != reflect.Ptr {
76				panic(fmt.Errorf("can't clone field %q: interface refers to a non-pointer",
77					field.Name))
78			}
79			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
80				panic(fmt.Errorf("can't clone field %q: interface points to a non-struct",
81					field.Name))
82			}
83
84			if dstFieldValue.IsNil() || dstFieldValue.Elem().Type() != srcFieldValue.Type() {
85				// We can't use the existing destination allocation, so
86				// clone a new one.
87				newValue := reflect.New(srcFieldValue.Type()).Elem()
88				dstFieldValue.Set(newValue)
89				dstFieldInterfaceValue = dstFieldValue
90				dstFieldValue = newValue
91			} else {
92				dstFieldValue = dstFieldValue.Elem()
93			}
94			fallthrough
95		case reflect.Ptr:
96			if srcFieldValue.IsNil() {
97				origDstFieldValue.Set(srcFieldValue)
98				break
99			}
100
101			srcFieldValue := srcFieldValue.Elem()
102
103			switch srcFieldValue.Kind() {
104			case reflect.Struct:
105				if !dstFieldValue.IsNil() {
106					// Re-use the existing allocation.
107					CopyProperties(dstFieldValue.Elem(), srcFieldValue)
108					break
109				} else {
110					newValue := CloneProperties(srcFieldValue)
111					if dstFieldInterfaceValue.IsValid() {
112						dstFieldInterfaceValue.Set(newValue)
113					} else {
114						origDstFieldValue.Set(newValue)
115					}
116				}
117			case reflect.Bool, reflect.String:
118				newValue := reflect.New(srcFieldValue.Type())
119				newValue.Elem().Set(srcFieldValue)
120				origDstFieldValue.Set(newValue)
121			default:
122				panic(fmt.Errorf("can't clone field %q: points to a %s",
123					field.Name, srcFieldValue.Kind()))
124			}
125		default:
126			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
127				field.Name, srcFieldValue.Kind()))
128		}
129	}
130}
131
132func ZeroProperties(structValue reflect.Value) {
133	typ := structValue.Type()
134
135	for i, field := range typeFields(typ) {
136		if field.PkgPath != "" {
137			// The field is not exported so just skip it.
138			continue
139		}
140
141		fieldValue := structValue.Field(i)
142
143		switch fieldValue.Kind() {
144		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
145			fieldValue.Set(reflect.Zero(fieldValue.Type()))
146		case reflect.Interface:
147			if fieldValue.IsNil() {
148				break
149			}
150
151			// We leave the pointer intact and zero out the struct that's
152			// pointed to.
153			fieldValue = fieldValue.Elem()
154			if fieldValue.Kind() != reflect.Ptr {
155				panic(fmt.Errorf("can't zero field %q: interface refers to a non-pointer",
156					field.Name))
157			}
158			if fieldValue.Type().Elem().Kind() != reflect.Struct {
159				panic(fmt.Errorf("can't zero field %q: interface points to a non-struct",
160					field.Name))
161			}
162			fallthrough
163		case reflect.Ptr:
164			switch fieldValue.Type().Elem().Kind() {
165			case reflect.Struct:
166				if fieldValue.IsNil() {
167					break
168				}
169				ZeroProperties(fieldValue.Elem())
170			case reflect.Bool, reflect.String:
171				fieldValue.Set(reflect.Zero(fieldValue.Type()))
172			default:
173				panic(fmt.Errorf("can't zero field %q: points to a %s",
174					field.Name, fieldValue.Elem().Kind()))
175			}
176		case reflect.Struct:
177			ZeroProperties(fieldValue)
178		default:
179			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
180				field.Name, fieldValue.Kind()))
181		}
182	}
183}
184
185func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
186	result := reflect.New(structValue.Type())
187	cloneEmptyProperties(result.Elem(), structValue)
188	return result
189}
190
191func cloneEmptyProperties(dstValue, srcValue reflect.Value) {
192	typ := srcValue.Type()
193	for i, field := range typeFields(typ) {
194		if field.PkgPath != "" {
195			// The field is not exported so just skip it.
196			continue
197		}
198
199		srcFieldValue := srcValue.Field(i)
200		dstFieldValue := dstValue.Field(i)
201		dstFieldInterfaceValue := reflect.Value{}
202
203		switch srcFieldValue.Kind() {
204		case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
205			// Nothing
206		case reflect.Struct:
207			cloneEmptyProperties(dstFieldValue, srcFieldValue)
208		case reflect.Interface:
209			if srcFieldValue.IsNil() {
210				break
211			}
212
213			srcFieldValue = srcFieldValue.Elem()
214			if srcFieldValue.Kind() != reflect.Ptr {
215				panic(fmt.Errorf("can't clone empty field %q: interface refers to a non-pointer",
216					field.Name))
217			}
218			if srcFieldValue.Type().Elem().Kind() != reflect.Struct {
219				panic(fmt.Errorf("can't clone empty field %q: interface points to a non-struct",
220					field.Name))
221			}
222
223			newValue := reflect.New(srcFieldValue.Type()).Elem()
224			dstFieldValue.Set(newValue)
225			dstFieldInterfaceValue = dstFieldValue
226			dstFieldValue = newValue
227			fallthrough
228		case reflect.Ptr:
229			switch srcFieldValue.Type().Elem().Kind() {
230			case reflect.Struct:
231				if srcFieldValue.IsNil() {
232					break
233				}
234				newValue := CloneEmptyProperties(srcFieldValue.Elem())
235				if dstFieldInterfaceValue.IsValid() {
236					dstFieldInterfaceValue.Set(newValue)
237				} else {
238					dstFieldValue.Set(newValue)
239				}
240			case reflect.Bool, reflect.String:
241				// Nothing
242			default:
243				panic(fmt.Errorf("can't clone empty field %q: points to a %s",
244					field.Name, srcFieldValue.Elem().Kind()))
245			}
246
247		default:
248			panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
249				field.Name, srcFieldValue.Kind()))
250		}
251	}
252}
253
254// reflect.Type.Field allocates a []int{} to hold the index every time it is called, which ends up
255// being a significant portion of the GC pressure.  It can't reuse the same one in case a caller
256// modifies the backing array through the slice.  Since we don't modify it, cache the result
257// locally to reduce allocations.
258type typeFieldMap map[reflect.Type][]reflect.StructField
259
260var (
261	// Stores an atomic pointer to map caching Type to its StructField
262	typeFieldCache atomic.Value
263	// Lock used by slow path updating the cache pointer
264	typeFieldCacheWriterLock sync.Mutex
265)
266
267func init() {
268	typeFieldCache.Store(make(typeFieldMap))
269}
270
271func typeFields(typ reflect.Type) []reflect.StructField {
272	// Fast path
273	cache := typeFieldCache.Load().(typeFieldMap)
274	if typeFields, ok := cache[typ]; ok {
275		return typeFields
276	}
277
278	// Slow path
279	typeFields := make([]reflect.StructField, typ.NumField())
280
281	for i := range typeFields {
282		typeFields[i] = typ.Field(i)
283	}
284
285	typeFieldCacheWriterLock.Lock()
286	defer typeFieldCacheWriterLock.Unlock()
287
288	old := typeFieldCache.Load().(typeFieldMap)
289	cache = make(typeFieldMap)
290	for k, v := range old {
291		cache[k] = v
292	}
293
294	cache[typ] = typeFields
295
296	typeFieldCache.Store(cache)
297
298	return typeFields
299}
300