• 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 proptools
16
17import (
18	"fmt"
19	"reflect"
20	"sort"
21	"strconv"
22	"strings"
23	"text/scanner"
24
25	"github.com/google/blueprint/parser"
26)
27
28const maxUnpackErrors = 10
29
30var (
31	// Hard-coded list of allowlisted property names of type map. This is to limit use of maps to
32	// where absolutely necessary.
33	validMapProperties = []string{}
34)
35
36type UnpackError struct {
37	Err error
38	Pos scanner.Position
39}
40
41func (e *UnpackError) Error() string {
42	return fmt.Sprintf("%s: %s", e.Pos, e.Err)
43}
44
45// packedProperty helps to track properties usage (`used` will be true)
46type packedProperty struct {
47	property *parser.Property
48	used     bool
49}
50
51// unpackContext keeps compound names and their values in a map. It is initialized from
52// parsed properties.
53type unpackContext struct {
54	propertyMap        map[string]*packedProperty
55	validMapProperties map[string]bool
56	errs               []error
57}
58
59// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties.
60// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized
61// from it. See PropertyNameForField for field and property name matching.
62// For instance, if the input contains
63//   { foo: "abc", bar: {x: 1},}
64// and a runtime value being has been declared as
65//   var v struct { Foo string; Bar int }
66// then v.Foo will be set to "abc" and v.Bar will be set to 1
67// (cf. unpack_test.go for further examples)
68//
69// The type of a receiving field has to match the property type, i.e., a bool/int/string field
70// can be set from a property with bool/int/string value, a struct can be set from a map (only the
71// matching fields are set), and an slice can be set from a list.
72// If a field of a runtime value has been already set prior to the UnpackProperties, the new value
73// is appended to it (see somewhat inappropriately named ExtendBasicType).
74// The same property can initialize fields in multiple runtime values. It is an error if any property
75// value was not used to initialize at least one field.
76func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) {
77	return unpackProperties(properties, validMapProperties, objects...)
78}
79
80func unpackProperties(properties []*parser.Property, validMapProps []string, objects ...interface{}) (map[string]*parser.Property, []error) {
81	var unpackContext unpackContext
82	unpackContext.propertyMap = make(map[string]*packedProperty)
83	if !unpackContext.buildPropertyMap("", properties) {
84		return nil, unpackContext.errs
85	}
86	unpackContext.validMapProperties = make(map[string]bool, len(validMapProps))
87	for _, p := range validMapProps {
88		unpackContext.validMapProperties[p] = true
89	}
90
91	for _, obj := range objects {
92		valueObject := reflect.ValueOf(obj)
93		if !isStructPtr(valueObject.Type()) {
94			panic(fmt.Errorf("properties must be *struct, got %s",
95				valueObject.Type()))
96		}
97		unpackContext.unpackToStruct("", valueObject.Elem())
98		if len(unpackContext.errs) >= maxUnpackErrors {
99			return nil, unpackContext.errs
100		}
101	}
102
103	// Gather property map, and collect any unused properties.
104	// Avoid reporting subproperties of unused properties.
105	result := make(map[string]*parser.Property)
106	var unusedNames []string
107	for name, v := range unpackContext.propertyMap {
108		if v.used {
109			result[name] = v.property
110		} else {
111			unusedNames = append(unusedNames, name)
112		}
113	}
114	if len(unusedNames) == 0 && len(unpackContext.errs) == 0 {
115		return result, nil
116	}
117	return nil, unpackContext.reportUnusedNames(unusedNames)
118}
119
120func (ctx *unpackContext) reportUnusedNames(unusedNames []string) []error {
121	sort.Strings(unusedNames)
122	var lastReported string
123	for _, name := range unusedNames {
124		// if 'foo' has been reported, ignore 'foo\..*' and 'foo\[.*'
125		if lastReported != "" {
126			trimmed := strings.TrimPrefix(name, lastReported)
127			if trimmed != name && (trimmed[0] == '.' || trimmed[0] == '[') {
128				continue
129			}
130		}
131		ctx.errs = append(ctx.errs, &UnpackError{
132			fmt.Errorf("unrecognized property %q", name),
133			ctx.propertyMap[name].property.ColonPos})
134		lastReported = name
135	}
136	return ctx.errs
137}
138
139func (ctx *unpackContext) buildPropertyMap(prefix string, properties []*parser.Property) bool {
140	nOldErrors := len(ctx.errs)
141	for _, property := range properties {
142		name := fieldPath(prefix, property.Name)
143		if first, present := ctx.propertyMap[name]; present {
144			ctx.addError(
145				&UnpackError{fmt.Errorf("property %q already defined", name), property.ColonPos})
146			if ctx.addError(
147				&UnpackError{fmt.Errorf("<-- previous definition here"), first.property.ColonPos}) {
148				return false
149			}
150			continue
151		}
152
153		ctx.propertyMap[name] = &packedProperty{property, false}
154		switch propValue := property.Value.Eval().(type) {
155		case *parser.Map:
156			// If this is a map and the values are not primitive types, we need to unroll it for further
157			// mapping. Keys are limited to string types.
158			ctx.buildPropertyMap(name, propValue.Properties)
159			if len(propValue.MapItems) == 0 {
160				continue
161			}
162			items := propValue.MapItems
163			keysType := items[0].Key.Type()
164			valsAreBasic := primitiveType(items[0].Value.Type())
165			if keysType != parser.StringType {
166				ctx.addError(&UnpackError{Err: fmt.Errorf("complex key types are unsupported: %s", keysType)})
167				return false
168			} else if valsAreBasic {
169				continue
170			}
171			itemProperties := make([]*parser.Property, len(items), len(items))
172			for i, item := range items {
173				itemProperties[i] = &parser.Property{
174					Name:     fmt.Sprintf("%s{value:%d}", property.Name, i),
175					NamePos:  property.NamePos,
176					ColonPos: property.ColonPos,
177					Value:    item.Value,
178				}
179			}
180			if !ctx.buildPropertyMap(prefix, itemProperties) {
181				return false
182			}
183		case *parser.List:
184			// If it is a list, unroll it unless its elements are of primitive type
185			// (no further mapping will be needed in that case, so we avoid cluttering
186			// the map).
187			if len(propValue.Values) == 0 {
188				continue
189			}
190			if primitiveType(propValue.Values[0].Type()) {
191				continue
192			}
193
194			itemProperties := make([]*parser.Property, len(propValue.Values), len(propValue.Values))
195			for i, expr := range propValue.Values {
196				itemProperties[i] = &parser.Property{
197					Name:     property.Name + "[" + strconv.Itoa(i) + "]",
198					NamePos:  property.NamePos,
199					ColonPos: property.ColonPos,
200					Value:    expr,
201				}
202			}
203			if !ctx.buildPropertyMap(prefix, itemProperties) {
204				return false
205			}
206		}
207	}
208
209	return len(ctx.errs) == nOldErrors
210}
211
212// primitiveType returns whether typ is a primitive type
213func primitiveType(typ parser.Type) bool {
214	return typ == parser.StringType || typ == parser.Int64Type || typ == parser.BoolType
215}
216
217func fieldPath(prefix, fieldName string) string {
218	if prefix == "" {
219		return fieldName
220	}
221	return prefix + "." + fieldName
222}
223
224func (ctx *unpackContext) addError(e error) bool {
225	ctx.errs = append(ctx.errs, e)
226	return len(ctx.errs) < maxUnpackErrors
227}
228
229func (ctx *unpackContext) unpackToStruct(namePrefix string, structValue reflect.Value) {
230	structType := structValue.Type()
231
232	for i := 0; i < structValue.NumField(); i++ {
233		fieldValue := structValue.Field(i)
234		field := structType.Field(i)
235
236		// In Go 1.7, runtime-created structs are unexported, so it's not
237		// possible to create an exported anonymous field with a generated
238		// type. So workaround this by special-casing "BlueprintEmbed" to
239		// behave like an anonymous field for structure unpacking.
240		if field.Name == "BlueprintEmbed" {
241			field.Name = ""
242			field.Anonymous = true
243		}
244
245		if field.PkgPath != "" {
246			// This is an unexported field, so just skip it.
247			continue
248		}
249
250		propertyName := fieldPath(namePrefix, PropertyNameForField(field.Name))
251
252		if !fieldValue.CanSet() {
253			panic(fmt.Errorf("field %s is not settable", propertyName))
254		}
255
256		// Get the property value if it was specified.
257		packedProperty, propertyIsSet := ctx.propertyMap[propertyName]
258
259		origFieldValue := fieldValue
260
261		// To make testing easier we validate the struct field's type regardless
262		// of whether or not the property was specified in the parsed string.
263		// TODO(ccross): we don't validate types inside nil struct pointers
264		// Move type validation to a function that runs on each factory once
265		switch kind := fieldValue.Kind(); kind {
266		case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice:
267			// Do nothing
268		case reflect.Map:
269			// Restrict names of map properties that _can_ be set in bp files
270			if _, ok := ctx.validMapProperties[propertyName]; !ok {
271				if !HasTag(field, "blueprint", "mutated") {
272					ctx.addError(&UnpackError{
273						Err: fmt.Errorf("Uses of maps for properties must be allowlisted. %q is an unsupported use case", propertyName),
274					})
275				}
276			}
277		case reflect.Interface:
278			if fieldValue.IsNil() {
279				panic(fmt.Errorf("field %s contains a nil interface", propertyName))
280			}
281			fieldValue = fieldValue.Elem()
282			elemType := fieldValue.Type()
283			if elemType.Kind() != reflect.Ptr {
284				panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
285			}
286			fallthrough
287		case reflect.Ptr:
288			switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
289			case reflect.Struct:
290				if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
291					// Instantiate nil struct pointers
292					// Set into origFieldValue in case it was an interface, in which case
293					// fieldValue points to the unsettable pointer inside the interface
294					fieldValue = reflect.New(fieldValue.Type().Elem())
295					origFieldValue.Set(fieldValue)
296				}
297				fieldValue = fieldValue.Elem()
298			case reflect.Bool, reflect.Int64, reflect.String:
299				// Nothing
300			default:
301				panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
302			}
303
304		case reflect.Int, reflect.Uint:
305			if !HasTag(field, "blueprint", "mutated") {
306				panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
307			}
308
309		default:
310			panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
311		}
312
313		if field.Anonymous && isStruct(fieldValue.Type()) {
314			ctx.unpackToStruct(namePrefix, fieldValue)
315			continue
316		}
317
318		if !propertyIsSet {
319			// This property wasn't specified.
320			continue
321		}
322
323		packedProperty.used = true
324		property := packedProperty.property
325
326		if HasTag(field, "blueprint", "mutated") {
327			if !ctx.addError(
328				&UnpackError{
329					fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
330					property.ColonPos,
331				}) {
332				return
333			}
334			continue
335		}
336
337		if isStruct(fieldValue.Type()) {
338			if property.Value.Eval().Type() != parser.MapType {
339				ctx.addError(&UnpackError{
340					fmt.Errorf("can't assign %s value to map property %q",
341						property.Value.Type(), property.Name),
342					property.Value.Pos(),
343				})
344				continue
345			}
346			ctx.unpackToStruct(propertyName, fieldValue)
347			if len(ctx.errs) >= maxUnpackErrors {
348				return
349			}
350		} else if isSlice(fieldValue.Type()) {
351			if unpackedValue, ok := ctx.unpackToSlice(propertyName, property, fieldValue.Type()); ok {
352				ExtendBasicType(fieldValue, unpackedValue, Append)
353			}
354			if len(ctx.errs) >= maxUnpackErrors {
355				return
356			}
357		} else if fieldValue.Type().Kind() == reflect.Map {
358			if unpackedValue, ok := ctx.unpackToMap(propertyName, property, fieldValue.Type()); ok {
359				ExtendBasicType(fieldValue, unpackedValue, Append)
360			}
361			if len(ctx.errs) >= maxUnpackErrors {
362				return
363			}
364
365		} else {
366			unpackedValue, err := propertyToValue(fieldValue.Type(), property)
367			if err != nil && !ctx.addError(err) {
368				return
369			}
370			ExtendBasicType(fieldValue, unpackedValue, Append)
371		}
372	}
373}
374
375// unpackToMap unpacks given parser.property into a go map of type mapType
376func (ctx *unpackContext) unpackToMap(mapName string, property *parser.Property, mapType reflect.Type) (reflect.Value, bool) {
377	propValueAsMap, ok := property.Value.Eval().(*parser.Map)
378	// Verify this property is a map
379	if !ok {
380		ctx.addError(&UnpackError{
381			fmt.Errorf("can't assign %q value to map property %q", property.Value.Type(), property.Name),
382			property.Value.Pos(),
383		})
384		return reflect.MakeMap(mapType), false
385	}
386	// And is not a struct
387	if len(propValueAsMap.Properties) > 0 {
388		ctx.addError(&UnpackError{
389			fmt.Errorf("can't assign property to a map (%s) property %q", property.Value.Type(), property.Name),
390			property.Value.Pos(),
391		})
392		return reflect.MakeMap(mapType), false
393	}
394
395	items := propValueAsMap.MapItems
396	m := reflect.MakeMap(mapType)
397	if len(items) == 0 {
398		return m, true
399	}
400	keyConstructor := ctx.itemConstructor(items[0].Key.Type())
401	keyType := mapType.Key()
402	valueConstructor := ctx.itemConstructor(items[0].Value.Type())
403	valueType := mapType.Elem()
404
405	itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
406	for i, item := range items {
407		itemProperty.Name = fmt.Sprintf("%s{key:%d}", mapName, i)
408		itemProperty.Value = item.Key
409		if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
410			packedProperty.used = true
411		}
412		keyValue, ok := itemValue(keyConstructor, itemProperty, keyType)
413		if !ok {
414			continue
415		}
416		itemProperty.Name = fmt.Sprintf("%s{value:%d}", mapName, i)
417		itemProperty.Value = item.Value
418		if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
419			packedProperty.used = true
420		}
421		value, ok := itemValue(valueConstructor, itemProperty, valueType)
422		if ok {
423			m.SetMapIndex(keyValue, value)
424		}
425	}
426
427	return m, true
428}
429
430// unpackSlice creates a value of a given slice type from the property which should be a list
431func (ctx *unpackContext) unpackToSlice(
432	sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
433	propValueAsList, ok := property.Value.Eval().(*parser.List)
434	if !ok {
435		ctx.addError(&UnpackError{
436			fmt.Errorf("can't assign %s value to list property %q",
437				property.Value.Type(), property.Name),
438			property.Value.Pos(),
439		})
440		return reflect.MakeSlice(sliceType, 0, 0), false
441	}
442	exprs := propValueAsList.Values
443	value := reflect.MakeSlice(sliceType, 0, len(exprs))
444	if len(exprs) == 0 {
445		return value, true
446	}
447
448	itemConstructor := ctx.itemConstructor(exprs[0].Type())
449	itemType := sliceType.Elem()
450
451	itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
452	for i, expr := range exprs {
453		itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
454		itemProperty.Value = expr
455		if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
456			packedProperty.used = true
457		}
458		if itemValue, ok := itemValue(itemConstructor, itemProperty, itemType); ok {
459			value = reflect.Append(value, itemValue)
460		}
461	}
462	return value, true
463}
464
465// constructItem is a function to construct a reflect.Value from given parser.Property of reflect.Type
466type constructItem func(*parser.Property, reflect.Type) (reflect.Value, bool)
467
468// itemValue creates a new item of type t with value determined by f
469func itemValue(f constructItem, property *parser.Property, t reflect.Type) (reflect.Value, bool) {
470	isPtr := t.Kind() == reflect.Ptr
471	if isPtr {
472		t = t.Elem()
473	}
474	val, ok := f(property, t)
475	if !ok {
476		return val, ok
477	}
478	if isPtr {
479		ptrValue := reflect.New(val.Type())
480		ptrValue.Elem().Set(val)
481		return ptrValue, true
482	}
483	return val, true
484}
485
486// itemConstructor returns a function  to construct an item of typ
487func (ctx *unpackContext) itemConstructor(typ parser.Type) constructItem {
488	// The function to construct an item value depends on the type of list elements.
489	switch typ {
490	case parser.BoolType, parser.StringType, parser.Int64Type:
491		return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
492			value, err := propertyToValue(t, property)
493			if err != nil {
494				ctx.addError(err)
495				return value, false
496			}
497			return value, true
498		}
499	case parser.ListType:
500		return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
501			return ctx.unpackToSlice(property.Name, property, t)
502		}
503	case parser.MapType:
504		return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
505			if t.Kind() == reflect.Map {
506				return ctx.unpackToMap(property.Name, property, t)
507			} else {
508				itemValue := reflect.New(t).Elem()
509				ctx.unpackToStruct(property.Name, itemValue)
510				return itemValue, true
511			}
512		}
513	case parser.NotEvaluatedType:
514		return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
515			return reflect.New(t), false
516		}
517	default:
518		panic(fmt.Errorf("bizarre property expression type: %v", typ))
519	}
520}
521
522// propertyToValue creates a value of a given value type from the property.
523func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
524	var value reflect.Value
525	var baseType reflect.Type
526	isPtr := typ.Kind() == reflect.Ptr
527	if isPtr {
528		baseType = typ.Elem()
529	} else {
530		baseType = typ
531	}
532
533	switch kind := baseType.Kind(); kind {
534	case reflect.Bool:
535		b, ok := property.Value.Eval().(*parser.Bool)
536		if !ok {
537			return value, &UnpackError{
538				fmt.Errorf("can't assign %s value to bool property %q",
539					property.Value.Type(), property.Name),
540				property.Value.Pos(),
541			}
542		}
543		value = reflect.ValueOf(b.Value)
544
545	case reflect.Int64:
546		b, ok := property.Value.Eval().(*parser.Int64)
547		if !ok {
548			return value, &UnpackError{
549				fmt.Errorf("can't assign %s value to int64 property %q",
550					property.Value.Type(), property.Name),
551				property.Value.Pos(),
552			}
553		}
554		value = reflect.ValueOf(b.Value)
555
556	case reflect.String:
557		s, ok := property.Value.Eval().(*parser.String)
558		if !ok {
559			return value, &UnpackError{
560				fmt.Errorf("can't assign %s value to string property %q",
561					property.Value.Type(), property.Name),
562				property.Value.Pos(),
563			}
564		}
565		value = reflect.ValueOf(s.Value)
566
567	default:
568		return value, &UnpackError{
569			fmt.Errorf("cannot assign %s value %s to %s property %s", property.Value.Type(), property.Value, kind, typ),
570			property.NamePos}
571	}
572
573	if isPtr {
574		ptrValue := reflect.New(value.Type())
575		ptrValue.Elem().Set(value)
576		return ptrValue, nil
577	}
578	return value, nil
579}
580