• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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