Home | History | Annotate | Download | only in blueprint
      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 
     15 package blueprint
     16 
     17 import (
     18 	"fmt"
     19 	"reflect"
     20 	"strconv"
     21 	"strings"
     22 
     23 	"github.com/google/blueprint/parser"
     24 	"github.com/google/blueprint/proptools"
     25 )
     26 
     27 type packedProperty struct {
     28 	property *parser.Property
     29 	unpacked bool
     30 }
     31 
     32 func 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 
     81 func 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 
    121 func 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 
    295 func 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 
    307 func 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 
    321 func 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 
    345 func 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 
    365 func 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