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 "sort" 21 "strconv" 22 "strings" 23 "text/scanner" 24 25 "github.com/google/blueprint/parser" 26) 27 28const maxUnpackErrors = 10 29 30var ( 31 // Hard-coded list of allowlisted property names of type map. This is to limit use of maps to 32 // where absolutely necessary. 33 validMapProperties = []string{} 34) 35 36type UnpackError struct { 37 Err error 38 Pos scanner.Position 39} 40 41func (e *UnpackError) Error() string { 42 return fmt.Sprintf("%s: %s", e.Pos, e.Err) 43} 44 45// packedProperty helps to track properties usage (`used` will be true) 46type packedProperty struct { 47 property *parser.Property 48 used bool 49} 50 51// unpackContext keeps compound names and their values in a map. It is initialized from 52// parsed properties. 53type unpackContext struct { 54 propertyMap map[string]*packedProperty 55 validMapProperties map[string]bool 56 errs []error 57} 58 59// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties. 60// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized 61// from it. See PropertyNameForField for field and property name matching. 62// For instance, if the input contains 63// { foo: "abc", bar: {x: 1},} 64// and a runtime value being has been declared as 65// var v struct { Foo string; Bar int } 66// then v.Foo will be set to "abc" and v.Bar will be set to 1 67// (cf. unpack_test.go for further examples) 68// 69// The type of a receiving field has to match the property type, i.e., a bool/int/string field 70// can be set from a property with bool/int/string value, a struct can be set from a map (only the 71// matching fields are set), and an slice can be set from a list. 72// If a field of a runtime value has been already set prior to the UnpackProperties, the new value 73// is appended to it (see somewhat inappropriately named ExtendBasicType). 74// The same property can initialize fields in multiple runtime values. It is an error if any property 75// value was not used to initialize at least one field. 76func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) { 77 return unpackProperties(properties, validMapProperties, objects...) 78} 79 80func unpackProperties(properties []*parser.Property, validMapProps []string, objects ...interface{}) (map[string]*parser.Property, []error) { 81 var unpackContext unpackContext 82 unpackContext.propertyMap = make(map[string]*packedProperty) 83 if !unpackContext.buildPropertyMap("", properties) { 84 return nil, unpackContext.errs 85 } 86 unpackContext.validMapProperties = make(map[string]bool, len(validMapProps)) 87 for _, p := range validMapProps { 88 unpackContext.validMapProperties[p] = true 89 } 90 91 for _, obj := range objects { 92 valueObject := reflect.ValueOf(obj) 93 if !isStructPtr(valueObject.Type()) { 94 panic(fmt.Errorf("properties must be *struct, got %s", 95 valueObject.Type())) 96 } 97 unpackContext.unpackToStruct("", valueObject.Elem()) 98 if len(unpackContext.errs) >= maxUnpackErrors { 99 return nil, unpackContext.errs 100 } 101 } 102 103 // Gather property map, and collect any unused properties. 104 // Avoid reporting subproperties of unused properties. 105 result := make(map[string]*parser.Property) 106 var unusedNames []string 107 for name, v := range unpackContext.propertyMap { 108 if v.used { 109 result[name] = v.property 110 } else { 111 unusedNames = append(unusedNames, name) 112 } 113 } 114 if len(unusedNames) == 0 && len(unpackContext.errs) == 0 { 115 return result, nil 116 } 117 return nil, unpackContext.reportUnusedNames(unusedNames) 118} 119 120func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error { 121 sort.Strings(unusedNames) 122 var lastReported string 123 for _, name := range unusedNames { 124 // if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*' 125 if lastReported != "" { 126 trimmed := strings.TrimPrefix(name, lastReported) 127 if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') { 128 continue 129 } 130 } 131 ctx.errs = append(ctx.errs, &UnpackError{ 132 fmt.Errorf("unrecognized property %q", name), 133 ctx.propertyMap[name].property.ColonPos}) 134 lastReported = name 135 } 136 return ctx.errs 137} 138 139func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool { 140 nOldErrors := len(ctx.errs) 141 for _, property := range properties { 142 name := fieldPath(prefix, property.Name) 143 if first, present := ctx.propertyMap[name]; present { 144 ctx.addError( 145 &UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos}) 146 if ctx.addError( 147 &UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) { 148 return false 149 } 150 continue 151 } 152 153 ctx.propertyMap[name] = &packedProperty{property, false} 154 switch propValue := property.Value.Eval().(type) { 155 case *parser.Map: 156 // If this is a map and the values are not primitive types, we need to unroll it for further 157 // mapping. Keys are limited to string types. 158 ctx.buildPropertyMap(name, propValue.Properties) 159 if len(propValue.MapItems) == 0 { 160 continue 161 } 162 items := propValue.MapItems 163 keysType := items[0].Key.Type() 164 valsAreBasic := primitiveType(items[0].Value.Type()) 165 if keysType != parser.StringType { 166 ctx.addError(&UnpackError{Err: fmt.Errorf("complex key types are unsupported: %s", keysType)}) 167 return false 168 } else if valsAreBasic { 169 continue 170 } 171 itemProperties := make([]*parser.Property, len(items), len(items)) 172 for i, item := range items { 173 itemProperties[i] = &parser.Property{ 174 Name: fmt.Sprintf("%s{value:%d}", property.Name, i), 175 NamePos: property.NamePos, 176 ColonPos: property.ColonPos, 177 Value: item.Value, 178 } 179 } 180 if !ctx.buildPropertyMap(prefix, itemProperties) { 181 return false 182 } 183 case *parser.List: 184 // If it is a list, unroll it unless its elements are of primitive type 185 // (no further mapping will be needed in that case, so we avoid cluttering 186 // the map). 187 if len(propValue.Values) == 0 { 188 continue 189 } 190 if primitiveType(propValue.Values[0].Type()) { 191 continue 192 } 193 194 itemProperties := make([]*parser.Property, len(propValue.Values), len(propValue.Values)) 195 for i, expr := range propValue.Values { 196 itemProperties[i] = &parser.Property{ 197 Name: property.Name + "[" + strconv.Itoa(i) + "]", 198 NamePos: property.NamePos, 199 ColonPos: property.ColonPos, 200 Value: expr, 201 } 202 } 203 if !ctx.buildPropertyMap(prefix, itemProperties) { 204 return false 205 } 206 } 207 } 208 209 return len(ctx.errs) == nOldErrors 210} 211 212// primitiveType returns whether typ is a primitive type 213func primitiveType(typ parser.Type) bool { 214 return typ == parser.StringType || typ == parser.Int64Type || typ == parser.BoolType 215} 216 217func fieldPath(prefix, fieldName string) string { 218 if prefix == "" { 219 return fieldName 220 } 221 return prefix + "." + fieldName 222} 223 224func (ctx *unpackContext) addError(e error) bool { 225 ctx.errs = append(ctx.errs, e) 226 return len(ctx.errs) < maxUnpackErrors 227} 228 229func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) { 230 structType := structValue.Type() 231 232 for i := 0; i < structValue.NumField(); i++ { 233 fieldValue := structValue.Field(i) 234 field := structType.Field(i) 235 236 // In Go 1.7, runtime-created structs are unexported, so it's not 237 // possible to create an exported anonymous field with a generated 238 // type. So workaround this by special-casing "BlueprintEmbed" to 239 // behave like an anonymous field for structure unpacking. 240 if field.Name == "BlueprintEmbed" { 241 field.Name = "" 242 field.Anonymous = true 243 } 244 245 if field.PkgPath != "" { 246 // This is an unexported field, so just skip it. 247 continue 248 } 249 250 propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name)) 251 252 if !fieldValue.CanSet() { 253 panic(fmt.Errorf("field %s is not settable", propertyName)) 254 } 255 256 // Get the property value if it was specified. 257 packedProperty, propertyIsSet := ctx.propertyMap[propertyName] 258 259 origFieldValue := fieldValue 260 261 // To make testing easier we validate the struct field's type regardless 262 // of whether or not the property was specified in the parsed string. 263 // TODO(ccross): we don't validate types inside nil struct pointers 264 // Move type validation to a function that runs on each factory once 265 switch kind := fieldValue.Kind(); kind { 266 case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice: 267 // Do nothing 268 case reflect.Map: 269 // Restrict names of map properties that _can_ be set in bp files 270 if _, ok := ctx.validMapProperties[propertyName]; !ok { 271 if !HasTag(field, "blueprint", "mutated") { 272 ctx.addError(&UnpackError{ 273 Err: fmt.Errorf("Uses of maps for properties must be allowlisted. %q is an unsupported use case", propertyName), 274 }) 275 } 276 } 277 case reflect.Interface: 278 if fieldValue.IsNil() { 279 panic(fmt.Errorf("field %s contains a nil interface", propertyName)) 280 } 281 fieldValue = fieldValue.Elem() 282 elemType := fieldValue.Type() 283 if elemType.Kind() != reflect.Ptr { 284 panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) 285 } 286 fallthrough 287 case reflect.Ptr: 288 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 289 case reflect.Struct: 290 if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) { 291 // Instantiate nil struct pointers 292 // Set into origFieldValue in case it was an interface, in which case 293 // fieldValue points to the unsettable pointer inside the interface 294 fieldValue = reflect.New(fieldValue.Type().Elem()) 295 origFieldValue.Set(fieldValue) 296 } 297 fieldValue = fieldValue.Elem() 298 case reflect.Bool, reflect.Int64, reflect.String: 299 // Nothing 300 default: 301 panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) 302 } 303 304 case reflect.Int, reflect.Uint: 305 if !HasTag(field, "blueprint", "mutated") { 306 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) 307 } 308 309 default: 310 panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) 311 } 312 313 if field.Anonymous && isStruct(fieldValue.Type()) { 314 ctx.unpackToStruct(namePrefix, fieldValue) 315 continue 316 } 317 318 if !propertyIsSet { 319 // This property wasn't specified. 320 continue 321 } 322 323 packedProperty.used = true 324 property := packedProperty.property 325 326 if HasTag(field, "blueprint", "mutated") { 327 if !ctx.addError( 328 &UnpackError{ 329 fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 330 property.ColonPos, 331 }) { 332 return 333 } 334 continue 335 } 336 337 if isStruct(fieldValue.Type()) { 338 if property.Value.Eval().Type() != parser.MapType { 339 ctx.addError(&UnpackError{ 340 fmt.Errorf("can't assign %s value to map property %q", 341 property.Value.Type(), property.Name), 342 property.Value.Pos(), 343 }) 344 continue 345 } 346 ctx.unpackToStruct(propertyName, fieldValue) 347 if len(ctx.errs) >= maxUnpackErrors { 348 return 349 } 350 } else if isSlice(fieldValue.Type()) { 351 if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok { 352 ExtendBasicType(fieldValue, unpackedValue, Append) 353 } 354 if len(ctx.errs) >= maxUnpackErrors { 355 return 356 } 357 } else if fieldValue.Type().Kind() == reflect.Map { 358 if unpackedValue, ok := ctx.unpackToMap(propertyName, property, fieldValue.Type()); ok { 359 ExtendBasicType(fieldValue, unpackedValue, Append) 360 } 361 if len(ctx.errs) >= maxUnpackErrors { 362 return 363 } 364 365 } else { 366 unpackedValue, err := propertyToValue(fieldValue.Type(), property) 367 if err != nil && !ctx.addError(err) { 368 return 369 } 370 ExtendBasicType(fieldValue, unpackedValue, Append) 371 } 372 } 373} 374 375// unpackToMap unpacks given parser.property into a go map of type mapType 376func (ctx *unpackContext) unpackToMap(mapName string, property *parser.Property, mapType reflect.Type) (reflect.Value, bool) { 377 propValueAsMap, ok := property.Value.Eval().(*parser.Map) 378 // Verify this property is a map 379 if !ok { 380 ctx.addError(&UnpackError{ 381 fmt.Errorf("can't assign %q value to map property %q", property.Value.Type(), property.Name), 382 property.Value.Pos(), 383 }) 384 return reflect.MakeMap(mapType), false 385 } 386 // And is not a struct 387 if len(propValueAsMap.Properties) > 0 { 388 ctx.addError(&UnpackError{ 389 fmt.Errorf("can't assign property to a map (%s) property %q", property.Value.Type(), property.Name), 390 property.Value.Pos(), 391 }) 392 return reflect.MakeMap(mapType), false 393 } 394 395 items := propValueAsMap.MapItems 396 m := reflect.MakeMap(mapType) 397 if len(items) == 0 { 398 return m, true 399 } 400 keyConstructor := ctx.itemConstructor(items[0].Key.Type()) 401 keyType := mapType.Key() 402 valueConstructor := ctx.itemConstructor(items[0].Value.Type()) 403 valueType := mapType.Elem() 404 405 itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos} 406 for i, item := range items { 407 itemProperty.Name = fmt.Sprintf("%s{key:%d}", mapName, i) 408 itemProperty.Value = item.Key 409 if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok { 410 packedProperty.used = true 411 } 412 keyValue, ok := itemValue(keyConstructor, itemProperty, keyType) 413 if !ok { 414 continue 415 } 416 itemProperty.Name = fmt.Sprintf("%s{value:%d}", mapName, i) 417 itemProperty.Value = item.Value 418 if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok { 419 packedProperty.used = true 420 } 421 value, ok := itemValue(valueConstructor, itemProperty, valueType) 422 if ok { 423 m.SetMapIndex(keyValue, value) 424 } 425 } 426 427 return m, true 428} 429 430// unpackSlice creates a value of a given slice type from the property which should be a list 431func (ctx *unpackContext) unpackToSlice( 432 sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) { 433 propValueAsList, ok := property.Value.Eval().(*parser.List) 434 if !ok { 435 ctx.addError(&UnpackError{ 436 fmt.Errorf("can't assign %s value to list property %q", 437 property.Value.Type(), property.Name), 438 property.Value.Pos(), 439 }) 440 return reflect.MakeSlice(sliceType, 0, 0), false 441 } 442 exprs := propValueAsList.Values 443 value := reflect.MakeSlice(sliceType, 0, len(exprs)) 444 if len(exprs) == 0 { 445 return value, true 446 } 447 448 itemConstructor := ctx.itemConstructor(exprs[0].Type()) 449 itemType := sliceType.Elem() 450 451 itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos} 452 for i, expr := range exprs { 453 itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]" 454 itemProperty.Value = expr 455 if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok { 456 packedProperty.used = true 457 } 458 if itemValue, ok := itemValue(itemConstructor, itemProperty, itemType); ok { 459 value = reflect.Append(value, itemValue) 460 } 461 } 462 return value, true 463} 464 465// constructItem is a function to construct a reflect.Value from given parser.Property of reflect.Type 466type constructItem func(*parser.Property, reflect.Type) (reflect.Value, bool) 467 468// itemValue creates a new item of type t with value determined by f 469func itemValue(f constructItem, property *parser.Property, t reflect.Type) (reflect.Value, bool) { 470 isPtr := t.Kind() == reflect.Ptr 471 if isPtr { 472 t = t.Elem() 473 } 474 val, ok := f(property, t) 475 if !ok { 476 return val, ok 477 } 478 if isPtr { 479 ptrValue := reflect.New(val.Type()) 480 ptrValue.Elem().Set(val) 481 return ptrValue, true 482 } 483 return val, true 484} 485 486// itemConstructor returns a function to construct an item of typ 487func (ctx *unpackContext) itemConstructor(typ parser.Type) constructItem { 488 // The function to construct an item value depends on the type of list elements. 489 switch typ { 490 case parser.BoolType, parser.StringType, parser.Int64Type: 491 return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 492 value, err := propertyToValue(t, property) 493 if err != nil { 494 ctx.addError(err) 495 return value, false 496 } 497 return value, true 498 } 499 case parser.ListType: 500 return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 501 return ctx.unpackToSlice(property.Name, property, t) 502 } 503 case parser.MapType: 504 return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 505 if t.Kind() == reflect.Map { 506 return ctx.unpackToMap(property.Name, property, t) 507 } else { 508 itemValue := reflect.New(t).Elem() 509 ctx.unpackToStruct(property.Name, itemValue) 510 return itemValue, true 511 } 512 } 513 case parser.NotEvaluatedType: 514 return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 515 return reflect.New(t), false 516 } 517 default: 518 panic(fmt.Errorf("bizarre property expression type: %v", typ)) 519 } 520} 521 522// propertyToValue creates a value of a given value type from the property. 523func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) { 524 var value reflect.Value 525 var baseType reflect.Type 526 isPtr := typ.Kind() == reflect.Ptr 527 if isPtr { 528 baseType = typ.Elem() 529 } else { 530 baseType = typ 531 } 532 533 switch kind := baseType.Kind(); kind { 534 case reflect.Bool: 535 b, ok := property.Value.Eval().(*parser.Bool) 536 if !ok { 537 return value, &UnpackError{ 538 fmt.Errorf("can't assign %s value to bool property %q", 539 property.Value.Type(), property.Name), 540 property.Value.Pos(), 541 } 542 } 543 value = reflect.ValueOf(b.Value) 544 545 case reflect.Int64: 546 b, ok := property.Value.Eval().(*parser.Int64) 547 if !ok { 548 return value, &UnpackError{ 549 fmt.Errorf("can't assign %s value to int64 property %q", 550 property.Value.Type(), property.Name), 551 property.Value.Pos(), 552 } 553 } 554 value = reflect.ValueOf(b.Value) 555 556 case reflect.String: 557 s, ok := property.Value.Eval().(*parser.String) 558 if !ok { 559 return value, &UnpackError{ 560 fmt.Errorf("can't assign %s value to string property %q", 561 property.Value.Type(), property.Name), 562 property.Value.Pos(), 563 } 564 } 565 value = reflect.ValueOf(s.Value) 566 567 default: 568 return value, &UnpackError{ 569 fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ), 570 property.NamePos} 571 } 572 573 if isPtr { 574 ptrValue := reflect.New(value.Type()) 575 ptrValue.Elem().Set(value) 576 return ptrValue, nil 577 } 578 return value, nil 579} 580