HostStubGen: Fix direct outer class detection
If there's a nested class like A$B$C and C doesn't have a class-wide
policy, HSG was supposed to look at A$B's ("direct outer class") policy,
but it was using A's policy. Fixed it.
Bug: 311174191
Test: ./scripts/run-all-tests.sh
Test: atest --host CtsGraphicsTestCasesRavenwood \
CtsTextTestCasesRavenwood \
CtsOsTestCasesRavenwood \
CtsAccountManagerTestCasesRavenwood \
CtsContentTestCasesRavenwood \
CtsProtoTestCasesRavenwood \
CtsUtilTestCasesRavenwood \
CtsDatabaseTestCasesRavenwood
Change-Id: I4881caac5e91366cf60e1aa232ecaf6b7c88aecf
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