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