Home | History | Annotate | Download | only in proptools
      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 
     15 package proptools
     16 
     17 import (
     18 	"fmt"
     19 	"reflect"
     20 	"sync"
     21 	"sync/atomic"
     22 )
     23 
     24 func CloneProperties(structValue reflect.Value) reflect.Value {
     25 	result := reflect.New(structValue.Type())
     26 	CopyProperties(result.Elem(), structValue)
     27 	return result
     28 }
     29 
     30 func 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 
    132 func 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 
    185 func CloneEmptyProperties(structValue reflect.Value) reflect.Value {
    186 	result := reflect.New(structValue.Type())
    187 	cloneEmptyProperties(result.Elem(), structValue)
    188 	return result
    189 }
    190 
    191 func 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.
    258 type typeFieldMap map[reflect.Type][]reflect.StructField
    259 
    260 var (
    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 
    267 func init() {
    268 	typeFieldCache.Store(make(typeFieldMap))
    269 }
    270 
    271 func 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