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 panic(fmt.Errorf("field %s is a non-string slice", propertyName)) 167 } 168 case reflect.Interface: 169 if fieldValue.IsNil() { 170 panic(fmt.Errorf("field %s contains a nil interface", propertyName)) 171 } 172 fieldValue = fieldValue.Elem() 173 elemType := fieldValue.Type() 174 if elemType.Kind() != reflect.Ptr { 175 panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) 176 } 177 fallthrough 178 case reflect.Ptr: 179 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 180 case reflect.Struct: 181 if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) { 182 // Instantiate nil struct pointers 183 // Set into origFieldValue in case it was an interface, in which case 184 // fieldValue points to the unsettable pointer inside the interface 185 fieldValue = reflect.New(fieldValue.Type().Elem()) 186 origFieldValue.Set(fieldValue) 187 } 188 fieldValue = fieldValue.Elem() 189 case reflect.Bool, reflect.String: 190 // Nothing 191 default: 192 panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) 193 } 194 195 case reflect.Int, reflect.Uint: 196 if !proptools.HasTag(field, "blueprint", "mutated") { 197 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) 198 } 199 200 default: 201 panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) 202 } 203 204 if field.Anonymous && fieldValue.Kind() == reflect.Struct { 205 newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue) 206 errs = append(errs, newErrs...) 207 continue 208 } 209 210 if !propertyIsSet { 211 // This property wasn't specified. 212 continue 213 } 214 215 packedProperty.unpacked = true 216 217 if proptools.HasTag(field, "blueprint", "mutated") { 218 errs = append(errs, 219 &BlueprintError{ 220 Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 221 Pos: packedProperty.property.ColonPos, 222 }) 223 if len(errs) >= maxErrors { 224 return errs 225 } 226 continue 227 } 228 229 if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) { 230 errs = append(errs, 231 &BlueprintError{ 232 Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), 233 Pos: packedProperty.property.ColonPos, 234 }) 235 if len(errs) >= maxErrors { 236 return errs 237 } 238 continue 239 } 240 241 var newErrs []error 242 243 switch kind := fieldValue.Kind(); kind { 244 case reflect.Bool: 245 newErrs = unpackBool(fieldValue, packedProperty.property) 246 case reflect.String: 247 newErrs = unpackString(fieldValue, packedProperty.property) 248 case reflect.Slice: 249 newErrs = unpackSlice(fieldValue, packedProperty.property) 250 case reflect.Ptr: 251 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 252 case reflect.Bool: 253 newValue := reflect.New(fieldValue.Type().Elem()) 254 newErrs = unpackBool(newValue.Elem(), packedProperty.property) 255 fieldValue.Set(newValue) 256 case reflect.String: 257 newValue := reflect.New(fieldValue.Type().Elem()) 258 newErrs = unpackString(newValue.Elem(), packedProperty.property) 259 fieldValue.Set(newValue) 260 default: 261 panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) 262 } 263 case reflect.Struct: 264 localFilterKey, localFilterValue := filterKey, filterValue 265 if k, v, err := HasFilter(field.Tag); err != nil { 266 errs = append(errs, err) 267 if len(errs) >= maxErrors { 268 return errs 269 } 270 } else if k != "" { 271 if filterKey != "" { 272 errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", 273 field.Name)) 274 if len(errs) >= maxErrors { 275 return errs 276 } 277 } else { 278 localFilterKey, localFilterValue = k, v 279 } 280 } 281 newErrs = unpackStruct(propertyName+".", fieldValue, 282 packedProperty.property, propertyMap, localFilterKey, localFilterValue) 283 default: 284 panic(fmt.Errorf("unexpected kind %s", kind)) 285 } 286 errs = append(errs, newErrs...) 287 if len(errs) >= maxErrors { 288 return errs 289 } 290 } 291 292 return errs 293} 294 295func unpackBool(boolValue reflect.Value, property *parser.Property) []error { 296 b, ok := property.Value.Eval().(*parser.Bool) 297 if !ok { 298 return []error{ 299 fmt.Errorf("%s: can't assign %s value to bool property %q", 300 property.Value.Pos(), property.Value.Type(), property.Name), 301 } 302 } 303 boolValue.SetBool(b.Value) 304 return nil 305} 306 307func unpackString(stringValue reflect.Value, 308 property *parser.Property) []error { 309 310 s, ok := property.Value.Eval().(*parser.String) 311 if !ok { 312 return []error{ 313 fmt.Errorf("%s: can't assign %s value to string property %q", 314 property.Value.Pos(), property.Value.Type(), property.Name), 315 } 316 } 317 stringValue.SetString(s.Value) 318 return nil 319} 320 321func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { 322 323 l, ok := property.Value.Eval().(*parser.List) 324 if !ok { 325 return []error{ 326 fmt.Errorf("%s: can't assign %s value to list property %q", 327 property.Value.Pos(), property.Value.Type(), property.Name), 328 } 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 sliceValue.Set(reflect.ValueOf(list)) 342 return nil 343} 344 345func unpackStruct(namePrefix string, structValue reflect.Value, 346 property *parser.Property, propertyMap map[string]*packedProperty, 347 filterKey, filterValue string) []error { 348 349 m, ok := property.Value.Eval().(*parser.Map) 350 if !ok { 351 return []error{ 352 fmt.Errorf("%s: can't assign %s value to map property %q", 353 property.Value.Pos(), property.Value.Type(), property.Name), 354 } 355 } 356 357 errs := buildPropertyMap(namePrefix, m.Properties, propertyMap) 358 if len(errs) > 0 { 359 return errs 360 } 361 362 return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue) 363} 364 365func HasFilter(field reflect.StructTag) (k, v string, err error) { 366 tag := field.Get("blueprint") 367 for _, entry := range strings.Split(tag, ",") { 368 if strings.HasPrefix(entry, "filter") { 369 if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") { 370 return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry) 371 } 372 entry = strings.TrimPrefix(entry, "filter(") 373 entry = strings.TrimSuffix(entry, ")") 374 375 s := strings.Split(entry, ":") 376 if len(s) != 2 { 377 return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry) 378 } 379 k = s[0] 380 v, err = strconv.Unquote(s[1]) 381 if err != nil { 382 return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error()) 383 } 384 return k, v, nil 385 } 386 } 387 388 return "", "", nil 389} 390