Support nil pointers to structs in properties

Allow primary builders to reduce allocations of empty structures by
allowing nil pointers to concrete struct types.  Property readers will
not recurse into nil pointers, property writers will replace the nil
pointer with a pointer to the zero value of the pointer element type.

Allows a >50% primary builder time improvement with a trivial change in
Soong.

Change-Id: If6ad674bf7bf2a694c335378a074643a97d3c50b
This commit is contained in:
Colin Cross 2016-08-05 17:19:36 -07:00
parent aec881da86
commit c3d731258a
8 changed files with 353 additions and 181 deletions

View file

@ -152,15 +152,18 @@ func setDefaults(properties []Property, defaults reflect.Value) {
continue continue
} }
if f.Type().Kind() == reflect.Interface { if f.Kind() == reflect.Interface {
f = f.Elem() f = f.Elem()
} }
if f.Type().Kind() == reflect.Ptr { if f.Kind() == reflect.Ptr {
if f.IsNil() {
continue
}
f = f.Elem() f = f.Elem()
} }
if f.Type().Kind() == reflect.Struct { if f.Kind() == reflect.Struct {
setDefaults(prop.Properties, f) setDefaults(prop.Properties, f)
} else { } else {
prop.Default = fmt.Sprintf("%v", f.Interface()) prop.Default = fmt.Sprintf("%v", f.Interface())

View file

@ -43,6 +43,7 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
srcFieldValue := srcValue.Field(i) srcFieldValue := srcValue.Field(i)
dstFieldValue := dstValue.Field(i) dstFieldValue := dstValue.Field(i)
dstFieldInterfaceValue := reflect.Value{} dstFieldInterfaceValue := reflect.Value{}
origDstFieldValue := dstFieldValue
switch srcFieldValue.Kind() { switch srcFieldValue.Kind() {
case reflect.Bool, reflect.String, reflect.Int, reflect.Uint: case reflect.Bool, reflect.String, reflect.Int, reflect.Uint:
@ -93,7 +94,7 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
fallthrough fallthrough
case reflect.Ptr: case reflect.Ptr:
if srcFieldValue.IsNil() { if srcFieldValue.IsNil() {
dstFieldValue.Set(srcFieldValue) origDstFieldValue.Set(srcFieldValue)
break break
} }
@ -110,13 +111,13 @@ func CopyProperties(dstValue, srcValue reflect.Value) {
if dstFieldInterfaceValue.IsValid() { if dstFieldInterfaceValue.IsValid() {
dstFieldInterfaceValue.Set(newValue) dstFieldInterfaceValue.Set(newValue)
} else { } else {
dstFieldValue.Set(newValue) origDstFieldValue.Set(newValue)
} }
} }
case reflect.Bool, reflect.String: case reflect.Bool, reflect.String:
newValue := reflect.New(srcFieldValue.Type()) newValue := reflect.New(srcFieldValue.Type())
newValue.Elem().Set(srcFieldValue) newValue.Elem().Set(srcFieldValue)
dstFieldValue.Set(newValue) origDstFieldValue.Set(newValue)
default: default:
panic(fmt.Errorf("can't clone field %q: points to a %s", panic(fmt.Errorf("can't clone field %q: points to a %s",
field.Name, srcFieldValue.Kind())) field.Name, srcFieldValue.Kind()))

View file

@ -191,11 +191,15 @@ func extendPropertyErrorf(property string, format string, a ...interface{}) *Ext
func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc, func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFilterFunc,
order ExtendPropertyOrderFunc) error { order ExtendPropertyOrderFunc) error {
dstValue, err := getStruct(dst) srcValue, err := getStruct(src)
if err != nil { if err != nil {
if _, ok := err.(getStructEmptyError); ok {
return nil
}
return err return err
} }
srcValue, err := getStruct(src)
dstValue, err := getOrCreateStruct(dst)
if err != nil { if err != nil {
return err return err
} }
@ -212,20 +216,23 @@ func extendProperties(dst interface{}, src interface{}, filter ExtendPropertyFil
func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc, func extendMatchingProperties(dst []interface{}, src interface{}, filter ExtendPropertyFilterFunc,
order ExtendPropertyOrderFunc) error { order ExtendPropertyOrderFunc) error {
srcValue, err := getStruct(src)
if err != nil {
if _, ok := err.(getStructEmptyError); ok {
return nil
}
return err
}
dstValues := make([]reflect.Value, len(dst)) dstValues := make([]reflect.Value, len(dst))
for i := range dst { for i := range dst {
var err error var err error
dstValues[i], err = getStruct(dst[i]) dstValues[i], err = getOrCreateStruct(dst[i])
if err != nil { if err != nil {
return err return err
} }
} }
srcValue, err := getStruct(src)
if err != nil {
return err
}
return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order) return extendPropertiesRecursive(dstValues, srcValue, "", filter, false, order)
} }
@ -270,6 +277,7 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
found = true found = true
dstFieldValue := dstValue.FieldByIndex(dstField.Index) dstFieldValue := dstValue.FieldByIndex(dstField.Index)
origDstFieldValue := dstFieldValue
if srcFieldValue.Kind() != dstFieldValue.Kind() { if srcFieldValue.Kind() != dstFieldValue.Kind() {
return extendPropertyErrorf(propertyName, "mismatched types %s and %s", return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
@ -306,11 +314,12 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
} }
// Pointer to a struct // Pointer to a struct
if dstFieldValue.IsNil() != srcFieldValue.IsNil() { if srcFieldValue.IsNil() {
return extendPropertyErrorf(propertyName, "nilitude mismatch") continue
} }
if dstFieldValue.IsNil() { if dstFieldValue.IsNil() {
continue dstFieldValue = reflect.New(srcFieldValue.Type().Elem())
origDstFieldValue.Set(dstFieldValue)
} }
dstFieldValue = dstFieldValue.Elem() dstFieldValue = dstFieldValue.Elem()
@ -435,14 +444,32 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
return nil return nil
} }
type getStructEmptyError struct{}
func (getStructEmptyError) Error() string { return "interface containing nil pointer" }
func getOrCreateStruct(in interface{}) (reflect.Value, error) {
value, err := getStruct(in)
if _, ok := err.(getStructEmptyError); ok {
value := reflect.ValueOf(in)
newValue := reflect.New(value.Type().Elem())
value.Set(newValue)
}
return value, err
}
func getStruct(in interface{}) (reflect.Value, error) { func getStruct(in interface{}) (reflect.Value, error) {
value := reflect.ValueOf(in) value := reflect.ValueOf(in)
if value.Kind() != reflect.Ptr { if value.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in) return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
} }
value = value.Elem() if value.Type().Elem().Kind() != reflect.Struct {
if value.Kind() != reflect.Struct {
return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in) return reflect.Value{}, fmt.Errorf("expected pointer to struct, got %T", in)
} }
if value.IsNil() {
return reflect.Value{}, getStructEmptyError{}
}
value = value.Elem()
return value, nil return value, nil
} }

View file

@ -496,12 +496,69 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
}, },
}, },
}, },
{
// Nil pointer to a struct
in1: &struct {
Nested *struct {
S string
}
}{},
in2: &struct {
Nested *struct {
S string
}
}{
Nested: &struct {
S string
}{
S: "string",
},
},
out: &struct {
Nested *struct {
S string
}
}{
Nested: &struct {
S string
}{
S: "string",
},
},
},
{
// Nil pointer to a struct in an interface
in1: &struct {
Nested interface{}
}{
Nested: (*struct{ S string })(nil),
},
in2: &struct {
Nested interface{}
}{
Nested: &struct {
S string
}{
S: "string",
},
},
out: &struct {
Nested interface{}
}{
Nested: &struct {
S string
}{
S: "string",
},
},
},
// Errors // Errors
{ {
// Non-pointer in1 // Non-pointer in1
in1: struct{}{}, in1: struct{}{},
in2: &struct{}{},
err: errors.New("expected pointer to struct, got struct {}"), err: errors.New("expected pointer to struct, got struct {}"),
out: struct{}{}, out: struct{}{},
}, },
@ -515,6 +572,7 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
{ {
// Non-struct in1 // Non-struct in1
in1: &[]string{"bad"}, in1: &[]string{"bad"},
in2: &struct{}{},
err: errors.New("expected pointer to struct, got *[]string"), err: errors.New("expected pointer to struct, got *[]string"),
out: &[]string{"bad"}, out: &[]string{"bad"},
}, },
@ -606,23 +664,6 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
}, },
err: extendPropertyErrorf("s", "interface not a pointer"), err: extendPropertyErrorf("s", "interface not a pointer"),
}, },
{
// Pointer nilitude mismatch
in1: &struct{ S *struct{ S string } }{
S: &struct{ S string }{
S: "string1",
},
},
in2: &struct{ S *struct{ S string } }{
S: nil,
},
out: &struct{ S *struct{ S string } }{
S: &struct{ S string }{
S: "string1",
},
},
err: extendPropertyErrorf("s", "nilitude mismatch"),
},
{ {
// Pointer not a struct // Pointer not a struct
in1: &struct{ S *[]string }{ in1: &struct{ S *[]string }{
@ -912,6 +953,7 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
{ {
// Non-pointer in1 // Non-pointer in1
in1: []interface{}{struct{}{}}, in1: []interface{}{struct{}{}},
in2: &struct{}{},
err: errors.New("expected pointer to struct, got struct {}"), err: errors.New("expected pointer to struct, got struct {}"),
out: []interface{}{struct{}{}}, out: []interface{}{struct{}{}},
}, },
@ -925,6 +967,7 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
{ {
// Non-struct in1 // Non-struct in1
in1: []interface{}{&[]string{"bad"}}, in1: []interface{}{&[]string{"bad"}},
in2: &struct{}{},
err: errors.New("expected pointer to struct, got *[]string"), err: errors.New("expected pointer to struct, got *[]string"),
out: []interface{}{&[]string{"bad"}}, out: []interface{}{&[]string{"bad"}},
}, },

View file

@ -46,12 +46,14 @@ func typeEqual(v1, v2 reflect.Value) bool {
if v1.Type().Elem().Kind() != reflect.Struct { if v1.Type().Elem().Kind() != reflect.Struct {
return true return true
} }
if v1.IsNil() != v2.IsNil() { if v1.IsNil() && !v2.IsNil() {
return false return concreteType(v2)
} } else if v2.IsNil() && !v1.IsNil() {
if v1.IsNil() { return concreteType(v1)
} else if v1.IsNil() && v2.IsNil() {
return true return true
} }
v1 = v1.Elem() v1 = v1.Elem()
v2 = v2.Elem() v2 = v2.Elem()
} }
@ -74,3 +76,34 @@ func typeEqual(v1, v2 reflect.Value) bool {
return true return true
} }
// Returns true if v recursively contains no interfaces
func concreteType(v reflect.Value) bool {
if v.Kind() == reflect.Interface {
return false
}
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return true
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return true
}
for i := 0; i < v.NumField(); i++ {
v := v.Field(i)
switch kind := v.Kind(); kind {
case reflect.Interface, reflect.Ptr, reflect.Struct:
if !concreteType(v) {
return false
}
}
}
return true
}

View file

@ -88,7 +88,7 @@ var typeEqualTestCases = []struct {
// Mismatching nilitude embedded pointer to struct // Mismatching nilitude embedded pointer to struct
in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}}, in1: &struct{ S *struct{ S1 string } }{S: &struct{ S1 string }{}},
in2: &struct{ S *struct{ S1 string } }{}, in2: &struct{ S *struct{ S1 string } }{},
out: false, out: true,
}, },
{ {
// Matching embedded interface to pointer to struct // Matching embedded interface to pointer to struct
@ -116,13 +116,13 @@ var typeEqualTestCases = []struct {
}, },
{ {
// Matching pointer to non-struct // Matching pointer to non-struct
in1: struct{ S1 *string }{ S1: StringPtr("test1") }, in1: struct{ S1 *string }{S1: StringPtr("test1")},
in2: struct{ S1 *string }{ S1: StringPtr("test2") }, in2: struct{ S1 *string }{S1: StringPtr("test2")},
out: true, out: true,
}, },
{ {
// Matching nilitude pointer to non-struct // Matching nilitude pointer to non-struct
in1: struct{ S1 *string }{ S1: StringPtr("test1") }, in1: struct{ S1 *string }{S1: StringPtr("test1")},
in2: struct{ S1 *string }{}, in2: struct{ S1 *string }{},
out: true, out: true,
}, },

View file

@ -139,6 +139,8 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
panic(fmt.Errorf("field %s is not settable", propertyName)) panic(fmt.Errorf("field %s is not settable", propertyName))
} }
origFieldValue := fieldValue
// To make testing easier we validate the struct field's type regardless // To make testing easier we validate the struct field's type regardless
// of whether or not the property was specified in the parsed string. // of whether or not the property was specified in the parsed string.
switch kind := fieldValue.Kind(); kind { switch kind := fieldValue.Kind(); kind {
@ -163,7 +165,8 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind { switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
case reflect.Struct: case reflect.Struct:
if fieldValue.IsNil() { if fieldValue.IsNil() {
panic(fmt.Errorf("field %s contains a nil pointer", propertyName)) fieldValue = reflect.New(fieldValue.Type().Elem())
origFieldValue.Set(fieldValue)
} }
fieldValue = fieldValue.Elem() fieldValue = fieldValue.Elem()
case reflect.Bool, reflect.String: case reflect.Bool, reflect.String:

View file

@ -28,15 +28,17 @@ import (
var validUnpackTestCases = []struct { var validUnpackTestCases = []struct {
input string input string
output []interface{} output []interface{}
empty []interface{}
errs []error errs []error
}{ }{
{` {
m { input: `
name: "abc", m {
blank: "", name: "abc",
} blank: "",
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Name *string Name *string
Blank *string Blank *string
@ -47,46 +49,46 @@ var validUnpackTestCases = []struct {
Unset: nil, Unset: nil,
}, },
}, },
nil,
}, },
{` {
m { input: `
name: "abc", m {
} name: "abc",
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Name string Name string
}{ }{
Name: "abc", Name: "abc",
}, },
}, },
nil,
}, },
{` {
m { input: `
isGood: true, m {
} isGood: true,
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
IsGood bool IsGood bool
}{ }{
IsGood: true, IsGood: true,
}, },
}, },
nil,
}, },
{` {
m { input: `
isGood: true, m {
isBad: false, isGood: true,
} isBad: false,
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
IsGood *bool IsGood *bool
IsBad *bool IsBad *bool
@ -97,17 +99,17 @@ var validUnpackTestCases = []struct {
IsUgly: nil, IsUgly: nil,
}, },
}, },
nil,
}, },
{` {
m { input: `
stuff: ["asdf", "jkl;", "qwert", m {
"uiop", "bnm,"], stuff: ["asdf", "jkl;", "qwert",
empty: [] "uiop", "bnm,"],
} empty: []
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Stuff []string Stuff []string
Empty []string Empty []string
@ -118,17 +120,17 @@ var validUnpackTestCases = []struct {
Nil: nil, Nil: nil,
}, },
}, },
nil,
}, },
{` {
m { input: `
nested: { m {
name: "abc", nested: {
name: "abc",
}
} }
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested struct { Nested struct {
Name string Name string
@ -139,17 +141,17 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
{` {
m { input: `
nested: { m {
name: "def", nested: {
name: "def",
}
} }
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested interface{} Nested interface{}
}{ }{
@ -158,19 +160,19 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
{` {
m { input: `
nested: { m {
foo: "abc", nested: {
}, foo: "abc",
bar: false, },
baz: ["def", "ghi"], bar: false,
} baz: ["def", "ghi"],
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string Foo string
@ -185,19 +187,19 @@ var validUnpackTestCases = []struct {
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
}, },
nil,
}, },
{` {
m { input: `
nested: { m {
foo: "abc", nested: {
}, foo: "abc",
bar: false, },
baz: ["def", "ghi"], bar: false,
} baz: ["def", "ghi"],
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string `allowNested:"true"` Foo string `allowNested:"true"`
@ -214,19 +216,19 @@ var validUnpackTestCases = []struct {
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
}, },
nil,
}, },
{` {
m { input: `
nested: { m {
foo: "abc", nested: {
}, foo: "abc",
bar: false, },
baz: ["def", "ghi"], bar: false,
} baz: ["def", "ghi"],
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string Foo string
@ -241,24 +243,25 @@ var validUnpackTestCases = []struct {
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
}, },
[]error{ errs: []error{
&Error{ &Error{
Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"), Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
Pos: mkpos(27, 4, 8), Pos: mkpos(30, 4, 9),
}, },
}, },
}, },
// Anonymous struct // Anonymous struct
{` {
m { input: `
name: "abc", m {
nested: { name: "abc",
name: "def", nested: {
}, name: "def",
} },
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
EmbeddedStruct EmbeddedStruct
Nested struct { Nested struct {
@ -277,19 +280,19 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
// Anonymous interface // Anonymous interface
{` {
m { input: `
name: "abc", m {
nested: { name: "abc",
name: "def", nested: {
}, name: "def",
} },
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
EmbeddedInterface EmbeddedInterface
Nested struct { Nested struct {
@ -308,19 +311,19 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
// Anonymous struct with name collision // Anonymous struct with name collision
{` {
m { input: `
name: "abc", m {
nested: { name: "abc",
name: "def", nested: {
}, name: "def",
} },
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Name string Name string
EmbeddedStruct EmbeddedStruct
@ -344,19 +347,19 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
// Anonymous interface with name collision // Anonymous interface with name collision
{` {
m { input: `
name: "abc", m {
nested: { name: "abc",
name: "def", nested: {
}, name: "def",
} },
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Name string Name string
EmbeddedInterface EmbeddedInterface
@ -380,21 +383,21 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
nil,
}, },
// Variables // Variables
{` {
list = ["abc"] input: `
string = "def" list = ["abc"]
list_with_variable = [string] string = "def"
m { list_with_variable = [string]
name: string, m {
list: list, name: string,
list2: list_with_variable, list: list,
} list2: list_with_variable,
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Name string Name string
List []string List []string
@ -405,18 +408,18 @@ var validUnpackTestCases = []struct {
List2: []string{"def"}, List2: []string{"def"},
}, },
}, },
nil,
}, },
// Multiple property structs // Multiple property structs
{` {
m { input: `
nested: { m {
name: "abc", nested: {
name: "abc",
}
} }
}
`, `,
[]interface{}{ output: []interface{}{
struct { struct {
Nested struct { Nested struct {
Name string Name string
@ -438,7 +441,62 @@ var validUnpackTestCases = []struct {
struct { struct {
}{}, }{},
}, },
nil, },
// Nil pointer to struct
{
input: `
m {
nested: {
name: "abc",
}
}
`,
output: []interface{}{
struct {
Nested *struct {
Name string
}
}{
Nested: &struct{ Name string }{
Name: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested *struct {
Name string
}
}{},
},
},
// Interface containing nil pointer to struct
{
input: `
m {
nested: {
name: "abc",
}
}
`,
output: []interface{}{
struct {
Nested interface{}
}{
Nested: &EmbeddedStruct{
Name: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested interface{}
}{
Nested: (*EmbeddedStruct)(nil),
},
},
}, },
} }
@ -464,9 +522,13 @@ func TestUnpackProperties(t *testing.T) {
continue continue
} }
output := []interface{}{} var output []interface{}
for _, p := range testCase.output { if len(testCase.empty) > 0 {
output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface()) output = testCase.empty
} else {
for _, p := range testCase.output {
output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface())
}
} }
_, errs = unpackProperties(module.Properties, output...) _, errs = unpackProperties(module.Properties, output...)
if len(errs) != 0 && len(testCase.errs) == 0 { if len(errs) != 0 && len(testCase.errs) == 0 {