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 30type UnpackError struct { 31 Err error 32 Pos scanner.Position 33} 34 35func (e *UnpackError) Error() string { 36 return fmt.Sprintf("%s: %s", e.Pos, e.Err) 37} 38 39// packedProperty helps to track properties usage (`used` will be true) 40type packedProperty struct { 41 property *parser.Property 42 used bool 43} 44 45// unpackContext keeps compound names and their values in a map. It is initialized from 46// parsed properties. 47type unpackContext struct { 48 propertyMap map[string]*packedProperty 49 errs []error 50} 51 52// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties. 53// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized 54// from it. See PropertyNameForField for field and property name matching. 55// For instance, if the input contains 56// 57// { foo: "abc", bar: {x: 1},} 58// 59// and a runtime value being has been declared as 60// 61// var v struct { Foo string; Bar int } 62// 63// then v.Foo will be set to "abc" and v.Bar will be set to 1 64// (cf. unpack_test.go for further examples) 65// 66// The type of a receiving field has to match the property type, i.e., a bool/int/string field 67// can be set from a property with bool/int/string value, a struct can be set from a map (only the 68// matching fields are set), and an slice can be set from a list. 69// If a field of a runtime value has been already set prior to the UnpackProperties, the new value 70// is appended to it (see somewhat inappropriately named ExtendBasicType). 71// The same property can initialize fields in multiple runtime values. It is an error if any property 72// value was not used to initialize at least one field. 73func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) { 74 var unpackContext unpackContext 75 unpackContext.propertyMap = make(map[string]*packedProperty) 76 if !unpackContext.buildPropertyMap("", properties) { 77 return nil, unpackContext.errs 78 } 79 80 for _, obj := range objects { 81 valueObject := reflect.ValueOf(obj) 82 if !isStructPtr(valueObject.Type()) { 83 panic(fmt.Errorf("properties must be *struct, got %s", 84 valueObject.Type())) 85 } 86 unpackContext.unpackToStruct("", valueObject.Elem()) 87 if len(unpackContext.errs) >= maxUnpackErrors { 88 return nil, unpackContext.errs 89 } 90 } 91 92 // Gather property map, and collect any unused properties. 93 // Avoid reporting subproperties of unused properties. 94 result := make(map[string]*parser.Property) 95 var unusedNames []string 96 for name, v := range unpackContext.propertyMap { 97 if v.used { 98 result[name] = v.property 99 } else { 100 unusedNames = append(unusedNames, name) 101 } 102 } 103 if len(unusedNames) == 0 && len(unpackContext.errs) == 0 { 104 return result, nil 105 } 106 return nil, unpackContext.reportUnusedNames(unusedNames) 107} 108 109func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error { 110 sort.Strings(unusedNames) 111 var lastReported string 112 for _, name := range unusedNames { 113 // if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*' 114 if lastReported != "" { 115 trimmed := strings.TrimPrefix(name, lastReported) 116 if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') { 117 continue 118 } 119 } 120 ctx.errs = append(ctx.errs, &UnpackError{ 121 fmt.Errorf("unrecognized property %q", name), 122 ctx.propertyMap[name].property.ColonPos}) 123 lastReported = name 124 } 125 return ctx.errs 126} 127 128func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool { 129 nOldErrors := len(ctx.errs) 130 for _, property := range properties { 131 name := fieldPath(prefix, property.Name) 132 if first, present := ctx.propertyMap[name]; present { 133 ctx.addError( 134 &UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos}) 135 if ctx.addError( 136 &UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) { 137 return false 138 } 139 continue 140 } 141 142 ctx.propertyMap[name] = &packedProperty{property, false} 143 switch propValue := property.Value.Eval().(type) { 144 case *parser.Map: 145 ctx.buildPropertyMap(name, propValue.Properties) 146 case *parser.List: 147 // If it is a list, unroll it unless its elements are of primitive type 148 // (no further mapping will be needed in that case, so we avoid cluttering 149 // the map). 150 if len(propValue.Values) == 0 { 151 continue 152 } 153 if t := propValue.Values[0].Type(); t == parser.StringType || t == parser.Int64Type || t == parser.BoolType { 154 continue 155 } 156 157 itemProperties := make([]*parser.Property, len(propValue.Values), len(propValue.Values)) 158 for i, expr := range propValue.Values { 159 itemProperties[i] = &parser.Property{ 160 Name: property.Name + "[" + strconv.Itoa(i) + "]", 161 NamePos: property.NamePos, 162 ColonPos: property.ColonPos, 163 Value: expr, 164 } 165 } 166 if !ctx.buildPropertyMap(prefix, itemProperties) { 167 return false 168 } 169 } 170 } 171 172 return len(ctx.errs) == nOldErrors 173} 174 175func fieldPath(prefix, fieldName string) string { 176 if prefix == "" { 177 return fieldName 178 } 179 return prefix + "." + fieldName 180} 181 182func (ctx *unpackContext) addError(e error) bool { 183 ctx.errs = append(ctx.errs, e) 184 return len(ctx.errs) < maxUnpackErrors 185} 186 187func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) { 188 structType := structValue.Type() 189 190 for i := 0; i < structValue.NumField(); i++ { 191 fieldValue := structValue.Field(i) 192 field := structType.Field(i) 193 194 // In Go 1.7, runtime-created structs are unexported, so it's not 195 // possible to create an exported anonymous field with a generated 196 // type. So workaround this by special-casing "BlueprintEmbed" to 197 // behave like an anonymous field for structure unpacking. 198 if field.Name == "BlueprintEmbed" { 199 field.Name = "" 200 field.Anonymous = true 201 } 202 203 if field.PkgPath != "" { 204 // This is an unexported field, so just skip it. 205 continue 206 } 207 208 propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name)) 209 210 if !fieldValue.CanSet() { 211 panic(fmt.Errorf("field %s is not settable", propertyName)) 212 } 213 214 // Get the property value if it was specified. 215 packedProperty, propertyIsSet := ctx.propertyMap[propertyName] 216 217 origFieldValue := fieldValue 218 219 // To make testing easier we validate the struct field's type regardless 220 // of whether or not the property was specified in the parsed string. 221 // TODO(ccross): we don't validate types inside nil struct pointers 222 // Move type validation to a function that runs on each factory once 223 switch kind := fieldValue.Kind(); kind { 224 case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice: 225 // Do nothing 226 case reflect.Interface: 227 if fieldValue.IsNil() { 228 panic(fmt.Errorf("field %s contains a nil interface", propertyName)) 229 } 230 fieldValue = fieldValue.Elem() 231 elemType := fieldValue.Type() 232 if elemType.Kind() != reflect.Ptr { 233 panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) 234 } 235 fallthrough 236 case reflect.Ptr: 237 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 238 case reflect.Struct: 239 if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) { 240 // Instantiate nil struct pointers 241 // Set into origFieldValue in case it was an interface, in which case 242 // fieldValue points to the unsettable pointer inside the interface 243 fieldValue = reflect.New(fieldValue.Type().Elem()) 244 origFieldValue.Set(fieldValue) 245 } 246 fieldValue = fieldValue.Elem() 247 case reflect.Bool, reflect.Int64, reflect.String: 248 // Nothing 249 default: 250 panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) 251 } 252 253 case reflect.Int, reflect.Uint: 254 if !HasTag(field, "blueprint", "mutated") { 255 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) 256 } 257 258 default: 259 panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) 260 } 261 262 if field.Anonymous && isStruct(fieldValue.Type()) { 263 ctx.unpackToStruct(namePrefix, fieldValue) 264 continue 265 } 266 267 if !propertyIsSet { 268 // This property wasn't specified. 269 continue 270 } 271 272 packedProperty.used = true 273 property := packedProperty.property 274 275 if HasTag(field, "blueprint", "mutated") { 276 if !ctx.addError( 277 &UnpackError{ 278 fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 279 property.ColonPos, 280 }) { 281 return 282 } 283 continue 284 } 285 286 if isStruct(fieldValue.Type()) { 287 if property.Value.Eval().Type() != parser.MapType { 288 ctx.addError(&UnpackError{ 289 fmt.Errorf("can't assign %s value to map property %q", 290 property.Value.Type(), property.Name), 291 property.Value.Pos(), 292 }) 293 continue 294 } 295 ctx.unpackToStruct(propertyName, fieldValue) 296 if len(ctx.errs) >= maxUnpackErrors { 297 return 298 } 299 } else if isSlice(fieldValue.Type()) { 300 if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok { 301 ExtendBasicType(fieldValue, unpackedValue, Append) 302 } 303 if len(ctx.errs) >= maxUnpackErrors { 304 return 305 } 306 307 } else { 308 unpackedValue, err := propertyToValue(fieldValue.Type(), property) 309 if err != nil && !ctx.addError(err) { 310 return 311 } 312 ExtendBasicType(fieldValue, unpackedValue, Append) 313 } 314 } 315} 316 317// unpackSlice creates a value of a given slice type from the property which should be a list 318func (ctx *unpackContext) unpackToSlice( 319 sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) { 320 propValueAsList, ok := property.Value.Eval().(*parser.List) 321 if !ok { 322 ctx.addError(&UnpackError{ 323 fmt.Errorf("can't assign %s value to list property %q", 324 property.Value.Type(), property.Name), 325 property.Value.Pos(), 326 }) 327 return reflect.MakeSlice(sliceType, 0, 0), false 328 } 329 exprs := propValueAsList.Values 330 value := reflect.MakeSlice(sliceType, 0, len(exprs)) 331 if len(exprs) == 0 { 332 return value, true 333 } 334 335 // The function to construct an item value depends on the type of list elements. 336 var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool) 337 switch exprs[0].Type() { 338 case parser.BoolType, parser.StringType, parser.Int64Type: 339 getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 340 value, err := propertyToValue(t, property) 341 if err != nil { 342 ctx.addError(err) 343 return value, false 344 } 345 return value, true 346 } 347 case parser.ListType: 348 getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 349 return ctx.unpackToSlice(property.Name, property, t) 350 } 351 case parser.MapType: 352 getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 353 itemValue := reflect.New(t).Elem() 354 ctx.unpackToStruct(property.Name, itemValue) 355 return itemValue, true 356 } 357 case parser.NotEvaluatedType: 358 getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) { 359 return reflect.New(t), false 360 } 361 default: 362 panic(fmt.Errorf("bizarre property expression type: %v", exprs[0].Type())) 363 } 364 365 itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos} 366 elemType := sliceType.Elem() 367 isPtr := elemType.Kind() == reflect.Ptr 368 369 for i, expr := range exprs { 370 itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]" 371 itemProperty.Value = expr 372 if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok { 373 packedProperty.used = true 374 } 375 if isPtr { 376 if itemValue, ok := getItemFunc(itemProperty, elemType.Elem()); ok { 377 ptrValue := reflect.New(itemValue.Type()) 378 ptrValue.Elem().Set(itemValue) 379 value = reflect.Append(value, ptrValue) 380 } 381 } else { 382 if itemValue, ok := getItemFunc(itemProperty, elemType); ok { 383 value = reflect.Append(value, itemValue) 384 } 385 } 386 } 387 return value, true 388} 389 390// propertyToValue creates a value of a given value type from the property. 391func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) { 392 var value reflect.Value 393 var baseType reflect.Type 394 isPtr := typ.Kind() == reflect.Ptr 395 if isPtr { 396 baseType = typ.Elem() 397 } else { 398 baseType = typ 399 } 400 401 switch kind := baseType.Kind(); kind { 402 case reflect.Bool: 403 b, ok := property.Value.Eval().(*parser.Bool) 404 if !ok { 405 return value, &UnpackError{ 406 fmt.Errorf("can't assign %s value to bool property %q", 407 property.Value.Type(), property.Name), 408 property.Value.Pos(), 409 } 410 } 411 value = reflect.ValueOf(b.Value) 412 413 case reflect.Int64: 414 b, ok := property.Value.Eval().(*parser.Int64) 415 if !ok { 416 return value, &UnpackError{ 417 fmt.Errorf("can't assign %s value to int64 property %q", 418 property.Value.Type(), property.Name), 419 property.Value.Pos(), 420 } 421 } 422 value = reflect.ValueOf(b.Value) 423 424 case reflect.String: 425 s, ok := property.Value.Eval().(*parser.String) 426 if !ok { 427 return value, &UnpackError{ 428 fmt.Errorf("can't assign %s value to string property %q", 429 property.Value.Type(), property.Name), 430 property.Value.Pos(), 431 } 432 } 433 value = reflect.ValueOf(s.Value) 434 435 default: 436 return value, &UnpackError{ 437 fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ), 438 property.NamePos} 439 } 440 441 if isPtr { 442 ptrValue := reflect.New(value.Type()) 443 ptrValue.Elem().Set(value) 444 return ptrValue, nil 445 } 446 return value, nil 447} 448