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