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 blueprint 16 17import ( 18 "fmt" 19 "reflect" 20 "strconv" 21 "strings" 22 23 "github.com/google/blueprint/parser" 24 "github.com/google/blueprint/proptools" 25) 26 27type packedProperty struct { 28 property *parser.Property 29 unpacked bool 30} 31 32func unpackProperties(propertyDefs []*parser.Property, 33 propertiesStructs ...interface{}) (map[string]*parser.Property, []error) { 34 35 propertyMap := make(map[string]*packedProperty) 36 errs := buildPropertyMap("", propertyDefs, propertyMap) 37 if len(errs) > 0 { 38 return nil, errs 39 } 40 41 for _, properties := range propertiesStructs { 42 propertiesValue := reflect.ValueOf(properties) 43 if propertiesValue.Kind() != reflect.Ptr { 44 panic("properties must be a pointer to a struct") 45 } 46 47 propertiesValue = propertiesValue.Elem() 48 if propertiesValue.Kind() != reflect.Struct { 49 panic("properties must be a pointer to a struct") 50 } 51 52 newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "") 53 errs = append(errs, newErrs...) 54 55 if len(errs) >= maxErrors { 56 return nil, errs 57 } 58 } 59 60 // Report any properties that didn't have corresponding struct fields as 61 // errors. 62 result := make(map[string]*parser.Property) 63 for name, packedProperty := range propertyMap { 64 result[name] = packedProperty.property 65 if !packedProperty.unpacked { 66 err := &BlueprintError{ 67 Err: fmt.Errorf("unrecognized property %q", name), 68 Pos: packedProperty.property.ColonPos, 69 } 70 errs = append(errs, err) 71 } 72 } 73 74 if len(errs) > 0 { 75 return nil, errs 76 } 77 78 return result, nil 79} 80 81func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property, 82 propertyMap map[string]*packedProperty) (errs []error) { 83 84 for _, propertyDef := range propertyDefs { 85 name := namePrefix + propertyDef.Name 86 if first, present := propertyMap[name]; present { 87 if first.property == propertyDef { 88 // We've already added this property. 89 continue 90 } 91 errs = append(errs, &BlueprintError{ 92 Err: fmt.Errorf("property %q already defined", name), 93 Pos: propertyDef.ColonPos, 94 }) 95 errs = append(errs, &BlueprintError{ 96 Err: fmt.Errorf("<-- previous definition here"), 97 Pos: first.property.ColonPos, 98 }) 99 if len(errs) >= maxErrors { 100 return errs 101 } 102 continue 103 } 104 105 propertyMap[name] = &packedProperty{ 106 property: propertyDef, 107 unpacked: false, 108 } 109 110 // We intentionally do not rescursively add MapValue properties to the 111 // property map here. Instead we add them when we encounter a struct 112 // into which they can be unpacked. We do this so that if we never 113 // encounter such a struct then the "unrecognized property" error will 114 // be reported only once for the map property and not for each of its 115 // sub-properties. 116 } 117 118 return 119} 120 121func unpackStructValue(namePrefix string, structValue reflect.Value, 122 propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { 123 124 structType := structValue.Type() 125 126 var errs []error 127 for i := 0; i < structValue.NumField(); i++ { 128 fieldValue := structValue.Field(i) 129 field := structType.Field(i) 130 131 // In Go 1.7, runtime-created structs are unexported, so it's not 132 // possible to create an exported anonymous field with a generated 133 // type. So workaround this by special-casing "BlueprintEmbed" to 134 // behave like an anonymous field for structure unpacking. 135 if field.Name == "BlueprintEmbed" { 136 field.Name = "" 137 field.Anonymous = true 138 } 139 140 if field.PkgPath != "" { 141 // This is an unexported field, so just skip it. 142 continue 143 } 144 145 propertyName := namePrefix + proptools.PropertyNameForField(field.Name) 146 147 if !fieldValue.CanSet() { 148 panic(fmt.Errorf("field %s is not settable", propertyName)) 149 } 150 151 // Get the property value if it was specified. 152 packedProperty, propertyIsSet := propertyMap[propertyName] 153 154 origFieldValue := fieldValue 155 156 // To make testing easier we validate the struct field's type regardless 157 // of whether or not the property was specified in the parsed string. 158 // TODO(ccross): we don't validate types inside nil struct pointers 159 // Move type validation to a function that runs on each factory once 160 switch kind := fieldValue.Kind(); kind { 161 case reflect.Bool, reflect.String, reflect.Struct: 162 // Do nothing 163 case reflect.Slice: 164 elemType := field.Type.Elem() 165 if elemType.Kind() != reflect.String { 166 if !proptools.HasTag(field, "blueprint", "mutated") { 167 panic(fmt.Errorf("field %s is a non-string slice", propertyName)) 168 } 169 } 170 case reflect.Interface: 171 if fieldValue.IsNil() { 172 panic(fmt.Errorf("field %s contains a nil interface", propertyName)) 173 } 174 fieldValue = fieldValue.Elem() 175 elemType := fieldValue.Type() 176 if elemType.Kind() != reflect.Ptr { 177 panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) 178 } 179 fallthrough 180 case reflect.Ptr: 181 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 182 case reflect.Struct: 183 if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) { 184 // Instantiate nil struct pointers 185 // Set into origFieldValue in case it was an interface, in which case 186 // fieldValue points to the unsettable pointer inside the interface 187 fieldValue = reflect.New(fieldValue.Type().Elem()) 188 origFieldValue.Set(fieldValue) 189 } 190 fieldValue = fieldValue.Elem() 191 case reflect.Bool, reflect.Int64, reflect.String: 192 // Nothing 193 default: 194 panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) 195 } 196 197 case reflect.Int, reflect.Uint: 198 if !proptools.HasTag(field, "blueprint", "mutated") { 199 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) 200 } 201 202 default: 203 panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) 204 } 205 206 if field.Anonymous && fieldValue.Kind() == reflect.Struct { 207 newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue) 208 errs = append(errs, newErrs...) 209 continue 210 } 211 212 if !propertyIsSet { 213 // This property wasn't specified. 214 continue 215 } 216 217 packedProperty.unpacked = true 218 219 if proptools.HasTag(field, "blueprint", "mutated") { 220 errs = append(errs, 221 &BlueprintError{ 222 Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 223 Pos: packedProperty.property.ColonPos, 224 }) 225 if len(errs) >= maxErrors { 226 return errs 227 } 228 continue 229 } 230 231 if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) { 232 errs = append(errs, 233 &BlueprintError{ 234 Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), 235 Pos: packedProperty.property.ColonPos, 236 }) 237 if len(errs) >= maxErrors { 238 return errs 239 } 240 continue 241 } 242 243 var newErrs []error 244 245 if fieldValue.Kind() == reflect.Struct { 246 localFilterKey, localFilterValue := filterKey, filterValue 247 if k, v, err := HasFilter(field.Tag); err != nil { 248 errs = append(errs, err) 249 if len(errs) >= maxErrors { 250 return errs 251 } 252 } else if k != "" { 253 if filterKey != "" { 254 errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", 255 field.Name)) 256 if len(errs) >= maxErrors { 257 return errs 258 } 259 } else { 260 localFilterKey, localFilterValue = k, v 261 } 262 } 263 newErrs = unpackStruct(propertyName+".", fieldValue, 264 packedProperty.property, propertyMap, localFilterKey, localFilterValue) 265 266 errs = append(errs, newErrs...) 267 if len(errs) >= maxErrors { 268 return errs 269 } 270 271 continue 272 } 273 274 // Handle basic types and pointers to basic types 275 276 propertyValue, err := propertyToValue(fieldValue.Type(), packedProperty.property) 277 if err != nil { 278 errs = append(errs, err) 279 if len(errs) >= maxErrors { 280 return errs 281 } 282 } 283 284 proptools.ExtendBasicType(fieldValue, propertyValue, proptools.Append) 285 } 286 287 return errs 288} 289 290func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) { 291 var value reflect.Value 292 293 var ptr bool 294 if typ.Kind() == reflect.Ptr { 295 typ = typ.Elem() 296 ptr = true 297 } 298 299 switch kind := typ.Kind(); kind { 300 case reflect.Bool: 301 b, ok := property.Value.Eval().(*parser.Bool) 302 if !ok { 303 return value, fmt.Errorf("%s: can't assign %s value to bool property %q", 304 property.Value.Pos(), property.Value.Type(), property.Name) 305 } 306 value = reflect.ValueOf(b.Value) 307 308 case reflect.Int64: 309 b, ok := property.Value.Eval().(*parser.Int64) 310 if !ok { 311 return value, fmt.Errorf("%s: can't assign %s value to int64 property %q", 312 property.Value.Pos(), property.Value.Type(), property.Name) 313 } 314 value = reflect.ValueOf(b.Value) 315 316 case reflect.String: 317 s, ok := property.Value.Eval().(*parser.String) 318 if !ok { 319 return value, fmt.Errorf("%s: can't assign %s value to string property %q", 320 property.Value.Pos(), property.Value.Type(), property.Name) 321 } 322 value = reflect.ValueOf(s.Value) 323 324 case reflect.Slice: 325 l, ok := property.Value.Eval().(*parser.List) 326 if !ok { 327 return value, fmt.Errorf("%s: can't assign %s value to list property %q", 328 property.Value.Pos(), property.Value.Type(), property.Name) 329 } 330 331 list := make([]string, len(l.Values)) 332 for i, value := range l.Values { 333 s, ok := value.Eval().(*parser.String) 334 if !ok { 335 // The parser should not produce this. 336 panic(fmt.Errorf("non-string value %q found in list", value)) 337 } 338 list[i] = s.Value 339 } 340 341 value = reflect.ValueOf(list) 342 343 default: 344 panic(fmt.Errorf("unexpected kind %s", kind)) 345 } 346 347 if ptr { 348 ptrValue := reflect.New(value.Type()) 349 ptrValue.Elem().Set(value) 350 value = ptrValue 351 } 352 353 return value, nil 354} 355 356func unpackStruct(namePrefix string, structValue reflect.Value, 357 property *parser.Property, propertyMap map[string]*packedProperty, 358 filterKey, filterValue string) []error { 359 360 m, ok := property.Value.Eval().(*parser.Map) 361 if !ok { 362 return []error{ 363 fmt.Errorf("%s: can't assign %s value to map property %q", 364 property.Value.Pos(), property.Value.Type(), property.Name), 365 } 366 } 367 368 errs := buildPropertyMap(namePrefix, m.Properties, propertyMap) 369 if len(errs) > 0 { 370 return errs 371 } 372 373 return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue) 374} 375 376func HasFilter(field reflect.StructTag) (k, v string, err error) { 377 tag := field.Get("blueprint") 378 for _, entry := range strings.Split(tag, ",") { 379 if strings.HasPrefix(entry, "filter") { 380 if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") { 381 return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry) 382 } 383 entry = strings.TrimPrefix(entry, "filter(") 384 entry = strings.TrimSuffix(entry, ")") 385 386 s := strings.Split(entry, ":") 387 if len(s) != 2 { 388 return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry) 389 } 390 k = s[0] 391 v, err = strconv.Unquote(s[1]) 392 if err != nil { 393 return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error()) 394 } 395 return k, v, nil 396 } 397 } 398 399 return "", "", nil 400} 401