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