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 := &Error{ 67 Err: fmt.Errorf("unrecognized property %q", name), 68 Pos: packedProperty.property.Pos, 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.Name 86 if first, present := propertyMap[name]; present { 87 if first.property == propertyDef { 88 // We've already added this property. 89 continue 90 } 91 92 errs = append(errs, &Error{ 93 Err: fmt.Errorf("property %q already defined", name), 94 Pos: propertyDef.Pos, 95 }) 96 errs = append(errs, &Error{ 97 Err: fmt.Errorf("<-- previous definition here"), 98 Pos: first.property.Pos, 99 }) 100 if len(errs) >= maxErrors { 101 return errs 102 } 103 continue 104 } 105 106 propertyMap[name] = &packedProperty{ 107 property: propertyDef, 108 unpacked: false, 109 } 110 111 // We intentionally do not rescursively add MapValue properties to the 112 // property map here. Instead we add them when we encounter a struct 113 // into which they can be unpacked. We do this so that if we never 114 // encounter such a struct then the "unrecognized property" error will 115 // be reported only once for the map property and not for each of its 116 // sub-properties. 117 } 118 119 return 120} 121 122func unpackStructValue(namePrefix string, structValue reflect.Value, 123 propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { 124 125 structType := structValue.Type() 126 127 var errs []error 128 for i := 0; i < structValue.NumField(); i++ { 129 fieldValue := structValue.Field(i) 130 field := structType.Field(i) 131 132 if field.PkgPath != "" { 133 // This is an unexported field, so just skip it. 134 continue 135 } 136 137 propertyName := namePrefix + proptools.PropertyNameForField(field.Name) 138 139 if !fieldValue.CanSet() { 140 panic(fmt.Errorf("field %s is not settable", propertyName)) 141 } 142 143 // To make testing easier we validate the struct field's type regardless 144 // of whether or not the property was specified in the parsed string. 145 switch kind := fieldValue.Kind(); kind { 146 case reflect.Bool, reflect.String, reflect.Struct: 147 // Do nothing 148 case reflect.Slice: 149 elemType := field.Type.Elem() 150 if elemType.Kind() != reflect.String { 151 panic(fmt.Errorf("field %s is a non-string slice", propertyName)) 152 } 153 case reflect.Interface: 154 if fieldValue.IsNil() { 155 panic(fmt.Errorf("field %s contains a nil interface", propertyName)) 156 } 157 fieldValue = fieldValue.Elem() 158 elemType := fieldValue.Type() 159 if elemType.Kind() != reflect.Ptr { 160 panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) 161 } 162 fallthrough 163 case reflect.Ptr: 164 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 165 case reflect.Struct: 166 if fieldValue.IsNil() { 167 panic(fmt.Errorf("field %s contains a nil pointer", propertyName)) 168 } 169 fieldValue = fieldValue.Elem() 170 case reflect.Bool, reflect.String: 171 // Nothing 172 default: 173 panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) 174 } 175 176 case reflect.Int, reflect.Uint: 177 if !proptools.HasTag(field, "blueprint", "mutated") { 178 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) 179 } 180 181 default: 182 panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) 183 } 184 185 if field.Anonymous && fieldValue.Kind() == reflect.Struct { 186 newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue) 187 errs = append(errs, newErrs...) 188 continue 189 } 190 191 // Get the property value if it was specified. 192 packedProperty, ok := propertyMap[propertyName] 193 if !ok { 194 // This property wasn't specified. 195 continue 196 } 197 198 packedProperty.unpacked = true 199 200 if proptools.HasTag(field, "blueprint", "mutated") { 201 errs = append(errs, 202 &Error{ 203 Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 204 Pos: packedProperty.property.Pos, 205 }) 206 if len(errs) >= maxErrors { 207 return errs 208 } 209 continue 210 } 211 212 if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) { 213 errs = append(errs, 214 &Error{ 215 Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), 216 Pos: packedProperty.property.Pos, 217 }) 218 if len(errs) >= maxErrors { 219 return errs 220 } 221 continue 222 } 223 224 var newErrs []error 225 226 switch kind := fieldValue.Kind(); kind { 227 case reflect.Bool: 228 newErrs = unpackBool(fieldValue, packedProperty.property) 229 case reflect.String: 230 newErrs = unpackString(fieldValue, packedProperty.property) 231 case reflect.Slice: 232 newErrs = unpackSlice(fieldValue, packedProperty.property) 233 case reflect.Ptr: 234 switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { 235 case reflect.Bool: 236 newValue := reflect.New(fieldValue.Type().Elem()) 237 newErrs = unpackBool(newValue.Elem(), packedProperty.property) 238 fieldValue.Set(newValue) 239 case reflect.String: 240 newValue := reflect.New(fieldValue.Type().Elem()) 241 newErrs = unpackString(newValue.Elem(), packedProperty.property) 242 fieldValue.Set(newValue) 243 default: 244 panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) 245 } 246 case reflect.Struct: 247 localFilterKey, localFilterValue := filterKey, filterValue 248 if k, v, err := HasFilter(field.Tag); err != nil { 249 errs = append(errs, err) 250 if len(errs) >= maxErrors { 251 return errs 252 } 253 } else if k != "" { 254 if filterKey != "" { 255 errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", 256 field.Name)) 257 if len(errs) >= maxErrors { 258 return errs 259 } 260 } else { 261 localFilterKey, localFilterValue = k, v 262 } 263 } 264 newErrs = unpackStruct(propertyName+".", fieldValue, 265 packedProperty.property, propertyMap, localFilterKey, localFilterValue) 266 default: 267 panic(fmt.Errorf("unexpected kind %s", kind)) 268 } 269 errs = append(errs, newErrs...) 270 if len(errs) >= maxErrors { 271 return errs 272 } 273 } 274 275 return errs 276} 277 278func unpackBool(boolValue reflect.Value, property *parser.Property) []error { 279 if property.Value.Type != parser.Bool { 280 return []error{ 281 fmt.Errorf("%s: can't assign %s value to %s property %q", 282 property.Value.Pos, property.Value.Type, parser.Bool, 283 property.Name), 284 } 285 } 286 boolValue.SetBool(property.Value.BoolValue) 287 return nil 288} 289 290func unpackString(stringValue reflect.Value, 291 property *parser.Property) []error { 292 293 if property.Value.Type != parser.String { 294 return []error{ 295 fmt.Errorf("%s: can't assign %s value to %s property %q", 296 property.Value.Pos, property.Value.Type, parser.String, 297 property.Name), 298 } 299 } 300 stringValue.SetString(property.Value.StringValue) 301 return nil 302} 303 304func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { 305 if property.Value.Type != parser.List { 306 return []error{ 307 fmt.Errorf("%s: can't assign %s value to %s property %q", 308 property.Value.Pos, property.Value.Type, parser.List, 309 property.Name), 310 } 311 } 312 313 list := []string{} 314 for _, value := range property.Value.ListValue { 315 if value.Type != parser.String { 316 // The parser should not produce this. 317 panic("non-string value found in list") 318 } 319 list = append(list, value.StringValue) 320 } 321 322 sliceValue.Set(reflect.ValueOf(list)) 323 return nil 324} 325 326func unpackStruct(namePrefix string, structValue reflect.Value, 327 property *parser.Property, propertyMap map[string]*packedProperty, 328 filterKey, filterValue string) []error { 329 330 if property.Value.Type != parser.Map { 331 return []error{ 332 fmt.Errorf("%s: can't assign %s value to %s property %q", 333 property.Value.Pos, property.Value.Type, parser.Map, 334 property.Name), 335 } 336 } 337 338 errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap) 339 if len(errs) > 0 { 340 return errs 341 } 342 343 return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue) 344} 345 346func HasFilter(field reflect.StructTag) (k, v string, err error) { 347 tag := field.Get("blueprint") 348 for _, entry := range strings.Split(tag, ",") { 349 if strings.HasPrefix(entry, "filter") { 350 if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") { 351 return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry) 352 } 353 entry = strings.TrimPrefix(entry, "filter(") 354 entry = strings.TrimSuffix(entry, ")") 355 356 s := strings.Split(entry, ":") 357 if len(s) != 2 { 358 return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry) 359 } 360 k = s[0] 361 v, err = strconv.Unquote(s[1]) 362 if err != nil { 363 return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error()) 364 } 365 return k, v, nil 366 } 367 } 368 369 return "", "", nil 370} 371