Merge "Ensure test/runtime order of singletons/pre-singletons is consistent"
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index c62e76d..34b180d 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -295,18 +295,25 @@
 	recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
 		var fields []reflect.StructField
 
+		// Iterate while the list is non-empty so it can be modified in the loop.
 		for len(affectableProperties) > 0 {
 			p := affectableProperties[0]
 			if !strings.HasPrefix(affectableProperties[0], prefix) {
+				// The properties are sorted and recurse is always called with a prefix that matches
+				// the first property in the list, so if we've reached one that doesn't match the
+				// prefix we are done with this prefix.
 				break
 			}
-			affectableProperties = affectableProperties[1:]
 
 			nestedProperty := strings.TrimPrefix(p, prefix)
 			if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
 				var nestedType reflect.Type
 				nestedPrefix := nestedProperty[:i+1]
 
+				// Recurse to handle the properties with the found prefix.  This will return
+				// an updated affectableProperties with the handled entries removed from the front
+				// of the list, and the type that contains the handled entries.  The type may be
+				// nil if none of the entries matched factoryProps.
 				affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
 
 				if nestedType != nil {
@@ -325,6 +332,8 @@
 						Type: typ,
 					})
 				}
+				// The first element in the list has been handled, remove it from the list.
+				affectableProperties = affectableProperties[1:]
 			}
 		}
 
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
index b824c78..48cdfe7 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -235,6 +235,44 @@
 			}{},
 			want: "",
 		},
+		{
+			name:                 "nested",
+			affectableProperties: []string{"multilib.lib32.cflags"},
+			factoryProps: struct {
+				Multilib struct {
+					Lib32 struct {
+						Cflags string
+					}
+				}
+			}{},
+			want: "*struct { Multilib struct { Lib32 struct { Cflags string } } }",
+		},
+		{
+			name: "complex",
+			affectableProperties: []string{
+				"cflags",
+				"multilib.lib32.cflags",
+				"multilib.lib32.ldflags",
+				"multilib.lib64.cflags",
+				"multilib.lib64.ldflags",
+				"zflags",
+			},
+			factoryProps: struct {
+				Cflags   string
+				Multilib struct {
+					Lib32 struct {
+						Cflags  string
+						Ldflags string
+					}
+					Lib64 struct {
+						Cflags  string
+						Ldflags string
+					}
+				}
+				Zflags string
+			}{},
+			want: "*struct { Cflags string; Multilib struct { Lib32 struct { Cflags string; Ldflags string }; Lib64 struct { Cflags string; Ldflags string } }; Zflags string }",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {