Merge "HostStubGen: Fix direct outer class detection" into main
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 4e0cd09..4db583f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -158,7 +158,7 @@
         // This is used when a member (methods, fields, nested classes) don't get any polices
         // from upper filters. e.g. when a method has no annotations, then this filter will apply
         // the class-wide policy, if any. (if not, we'll fall back to the above filter.)
-        filter = ClassWidePolicyPropagatingFilter(filter)
+        filter = ClassWidePolicyPropagatingFilter(allClasses, filter)
 
         // Inject default hooks from options.
         filter = DefaultHookInjectingFilter(
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index d7aa0af..d581c27 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -108,7 +108,7 @@
  * Otherwise, return null.
  */
 fun getDirectOuterClassName(className: String): String? {
-    val pos = className.indexOf('$')
+    val pos = className.lastIndexOf('$')
     if (pos < 0) {
         return null
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
index 6aac3d8..47790b1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
@@ -15,6 +15,7 @@
  */
 package com.android.hoststubgen.filters
 
+import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.getDirectOuterClassName
 
 /**
@@ -22,22 +23,38 @@
  * (obtained from [outermostFilter]) to the fields and methods.
  */
 class ClassWidePolicyPropagatingFilter(
-        fallback: OutputFilter,
+    private val classes: ClassNodes,
+    fallback: OutputFilter,
     ) : DelegatingFilter(fallback) {
 
     private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
         var currentClass = className
 
-        while (true) {
-            outermostFilter.getPolicyForClass(className).let { policy ->
-                if (policy.policy.isClassWidePolicy) {
-                    val p = if (resolve) policy.policy.resolveClassWidePolicy() else policy.policy
 
-                    return p.withReason(policy.reason).wrapReason("class-wide in $currentClass")
-                }
-                // If the class's policy is remove, then remove it.
-                if (policy.policy == FilterPolicy.Remove) {
-                    return FilterPolicy.Remove.withReason("class-wide in $currentClass")
+        // If the class name is `a.b.c.A$B$C`, then we try to get the class wide policy
+        // from a.b.c.A$B$C, then a.b.c.A$B, and then a.b.c.A.
+        while (true) {
+            // Sometimes a class name has a `$` in it but not as a nest class name separator --
+            // e.g. class name like "MyClass$$". In this case, `MyClass$` may not actually be
+            // a class name.
+            // So before getting the class policy on a nonexistent class, which may cause an
+            // incorrect result, we make sure if className actually exists.
+            if (classes.hasClass(className)) {
+                outermostFilter.getPolicyForClass(className).let { policy ->
+                    if (policy.policy.isClassWidePolicy) {
+                        val p = if (resolve) {
+                            policy.policy.resolveClassWidePolicy()
+                        } else {
+                            policy.policy
+                        }
+
+                        return p.withReason(policy.reason)
+                            .wrapReason("class-wide in $currentClass")
+                    }
+                    // If the class's policy is remove, then remove it.
+                    if (policy.policy == FilterPolicy.Remove) {
+                        return FilterPolicy.Remove.withReason("class-wide in $currentClass")
+                    }
                 }
             }
 
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt
new file mode 100644
index 0000000..66624d1
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.utils
+
+import com.android.hoststubgen.asm.getDirectOuterClassName
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class AsmUtilsTest {
+    private fun checkGetDirectOuterClassName(input: String, expected: String?) {
+        assertThat(getDirectOuterClassName(input)).isEqualTo(expected)
+    }
+
+    @Test
+    fun testGetDirectOuterClassName() {
+        checkGetDirectOuterClassName("a", null)
+        checkGetDirectOuterClassName("a\$x", "a")
+        checkGetDirectOuterClassName("a.b.c\$x", "a.b.c")
+        checkGetDirectOuterClassName("a.b.c\$x\$y", "a.b.c\$x")
+    }
+}
\ No newline at end of file