// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package blueprint import ( "fmt" "reflect" "strconv" "strings" "github.com/google/blueprint/parser" "github.com/google/blueprint/proptools" ) type packedProperty struct { property *parser.Property unpacked bool } func unpackProperties(propertyDefs []*parser.Property, propertiesStructs ...interface{}) (map[string]*parser.Property, []error) { propertyMap := make(map[string]*packedProperty) errs := buildPropertyMap("", propertyDefs, propertyMap) if len(errs) > 0 { return nil, errs } for _, properties := range propertiesStructs { propertiesValue := reflect.ValueOf(properties) if propertiesValue.Kind() != reflect.Ptr { panic("properties must be a pointer to a struct") } propertiesValue = propertiesValue.Elem() if propertiesValue.Kind() != reflect.Struct { panic("properties must be a pointer to a struct") } newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "") errs = append(errs, newErrs...) if len(errs) >= maxErrors { return nil, errs } } // Report any properties that didn't have corresponding struct fields as // errors. result := make(map[string]*parser.Property) for name, packedProperty := range propertyMap { result[name] = packedProperty.property if !packedProperty.unpacked { err := &Error{ Err: fmt.Errorf("unrecognized property %q", name), Pos: packedProperty.property.Pos, } errs = append(errs, err) } } if len(errs) > 0 { return nil, errs } return result, nil } func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property, propertyMap map[string]*packedProperty) (errs []error) { for _, propertyDef := range propertyDefs { name := namePrefix + propertyDef.Name.Name if first, present := propertyMap[name]; present { if first.property == propertyDef { // We've already added this property. continue } errs = append(errs, &Error{ Err: fmt.Errorf("property %q already defined", name), Pos: propertyDef.Pos, }) errs = append(errs, &Error{ Err: fmt.Errorf("<-- previous definition here"), Pos: first.property.Pos, }) if len(errs) >= maxErrors { return errs } continue } propertyMap[name] = &packedProperty{ property: propertyDef, unpacked: false, } // We intentionally do not rescursively add MapValue properties to the // property map here. Instead we add them when we encounter a struct // into which they can be unpacked. We do this so that if we never // encounter such a struct then the "unrecognized property" error will // be reported only once for the map property and not for each of its // sub-properties. } return } func unpackStructValue(namePrefix string, structValue reflect.Value, propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { structType := structValue.Type() var errs []error for i := 0; i < structValue.NumField(); i++ { fieldValue := structValue.Field(i) field := structType.Field(i) if field.PkgPath != "" { // This is an unexported field, so just skip it. continue } propertyName := namePrefix + proptools.PropertyNameForField(field.Name) if !fieldValue.CanSet() { panic(fmt.Errorf("field %s is not settable", propertyName)) } // To make testing easier we validate the struct field's type regardless // of whether or not the property was specified in the parsed string. switch kind := fieldValue.Kind(); kind { case reflect.Bool, reflect.String, reflect.Struct: // Do nothing case reflect.Slice: elemType := field.Type.Elem() if elemType.Kind() != reflect.String { panic(fmt.Errorf("field %s is a non-string slice", propertyName)) } case reflect.Interface: if fieldValue.IsNil() { panic(fmt.Errorf("field %s contains a nil interface", propertyName)) } fieldValue = fieldValue.Elem() elemType := fieldValue.Type() if elemType.Kind() != reflect.Ptr { panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName)) } fallthrough case reflect.Ptr: switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { case reflect.Struct: if fieldValue.IsNil() { panic(fmt.Errorf("field %s contains a nil pointer", propertyName)) } fieldValue = fieldValue.Elem() case reflect.Bool, reflect.String: // Nothing default: panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind)) } case reflect.Int, reflect.Uint: if !proptools.HasTag(field, "blueprint", "mutated") { panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName)) } default: panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind)) } if field.Anonymous && fieldValue.Kind() == reflect.Struct { newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue) errs = append(errs, newErrs...) continue } // Get the property value if it was specified. packedProperty, ok := propertyMap[propertyName] if !ok { // This property wasn't specified. continue } packedProperty.unpacked = true if proptools.HasTag(field, "blueprint", "mutated") { errs = append(errs, &Error{ Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), Pos: packedProperty.property.Pos, }) if len(errs) >= maxErrors { return errs } continue } if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) { errs = append(errs, &Error{ Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), Pos: packedProperty.property.Pos, }) if len(errs) >= maxErrors { return errs } continue } var newErrs []error switch kind := fieldValue.Kind(); kind { case reflect.Bool: newErrs = unpackBool(fieldValue, packedProperty.property) case reflect.String: newErrs = unpackString(fieldValue, packedProperty.property) case reflect.Slice: newErrs = unpackSlice(fieldValue, packedProperty.property) case reflect.Ptr: switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { case reflect.Bool: newValue := reflect.New(fieldValue.Type().Elem()) newErrs = unpackBool(newValue.Elem(), packedProperty.property) fieldValue.Set(newValue) case reflect.String: newValue := reflect.New(fieldValue.Type().Elem()) newErrs = unpackString(newValue.Elem(), packedProperty.property) fieldValue.Set(newValue) default: panic(fmt.Errorf("unexpected pointer kind %s", ptrKind)) } case reflect.Struct: localFilterKey, localFilterValue := filterKey, filterValue if k, v, err := HasFilter(field.Tag); err != nil { errs = append(errs, err) if len(errs) >= maxErrors { return errs } } else if k != "" { if filterKey != "" { errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", field.Name)) if len(errs) >= maxErrors { return errs } } else { localFilterKey, localFilterValue = k, v } } newErrs = unpackStruct(propertyName+".", fieldValue, packedProperty.property, propertyMap, localFilterKey, localFilterValue) default: panic(fmt.Errorf("unexpected kind %s", kind)) } errs = append(errs, newErrs...) if len(errs) >= maxErrors { return errs } } return errs } func unpackBool(boolValue reflect.Value, property *parser.Property) []error { if property.Value.Type != parser.Bool { return []error{ fmt.Errorf("%s: can't assign %s value to %s property %q", property.Value.Pos, property.Value.Type, parser.Bool, property.Name), } } boolValue.SetBool(property.Value.BoolValue) return nil } func unpackString(stringValue reflect.Value, property *parser.Property) []error { if property.Value.Type != parser.String { return []error{ fmt.Errorf("%s: can't assign %s value to %s property %q", property.Value.Pos, property.Value.Type, parser.String, property.Name), } } stringValue.SetString(property.Value.StringValue) return nil } func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { if property.Value.Type != parser.List { return []error{ fmt.Errorf("%s: can't assign %s value to %s property %q", property.Value.Pos, property.Value.Type, parser.List, property.Name), } } list := []string{} for _, value := range property.Value.ListValue { if value.Type != parser.String { // The parser should not produce this. panic("non-string value found in list") } list = append(list, value.StringValue) } sliceValue.Set(reflect.ValueOf(list)) return nil } func unpackStruct(namePrefix string, structValue reflect.Value, property *parser.Property, propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { if property.Value.Type != parser.Map { return []error{ fmt.Errorf("%s: can't assign %s value to %s property %q", property.Value.Pos, property.Value.Type, parser.Map, property.Name), } } errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap) if len(errs) > 0 { return errs } return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue) } func HasFilter(field reflect.StructTag) (k, v string, err error) { tag := field.Get("blueprint") for _, entry := range strings.Split(tag, ",") { if strings.HasPrefix(entry, "filter") { if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") { return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry) } entry = strings.TrimPrefix(entry, "filter(") entry = strings.TrimSuffix(entry, ")") s := strings.Split(entry, ":") if len(s) != 2 { return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry) } k = s[0] v, err = strconv.Unquote(s[1]) if err != nil { return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error()) } return k, v, nil } } return "", "", nil }