• 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				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
295func 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
307func 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
321func 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
345func 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
365func 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