diff options
10 files changed, 138 insertions, 14 deletions
diff --git a/Ravenwood.bp b/Ravenwood.bp index d13c4d78190c..93febca49421 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -33,6 +33,7 @@ java_genrule { "@$(location ravenwood/ravenwood-standard-options.txt) " + "--debug-log $(location hoststubgen_framework-minus-apex.log) " + + "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " + "--out-impl-jar $(location ravenwood.jar) " + @@ -56,6 +57,7 @@ java_genrule { "hoststubgen_dump.txt", "hoststubgen_framework-minus-apex.log", + "hoststubgen_framework-minus-apex_stats.csv", ], visibility: ["//visibility:private"], } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 97e09b817ef6..06eeb47c94ed 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -49,6 +49,7 @@ import java.util.zip.ZipOutputStream class HostStubGen(val options: HostStubGenOptions) { fun run() { val errors = HostStubGenErrors() + val stats = HostStubGenStats() // Load all classes. val allClasses = loadClassStructures(options.inJar.get) @@ -80,7 +81,14 @@ class HostStubGen(val options: HostStubGenOptions) { options.enableClassChecker.get, allClasses, errors, + stats, ) + + // Dump statistics, if specified. + options.statsFile.ifSet { + PrintWriter(it).use { pw -> stats.dump(pw) } + log.i("Dump file created at $it") + } } /** @@ -237,6 +245,7 @@ class HostStubGen(val options: HostStubGenOptions) { enableChecker: Boolean, classes: ClassNodes, errors: HostStubGenErrors, + stats: HostStubGenStats, ) { log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar) log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled") @@ -254,7 +263,8 @@ class HostStubGen(val options: HostStubGenOptions) { while (inEntries.hasMoreElements()) { val entry = inEntries.nextElement() convertSingleEntry(inZip, entry, stubOutStream, implOutStream, - filter, packageRedirector, enableChecker, classes, errors) + filter, packageRedirector, enableChecker, classes, errors, + stats) } log.i("Converted all entries.") } @@ -287,6 +297,7 @@ class HostStubGen(val options: HostStubGenOptions) { enableChecker: Boolean, classes: ClassNodes, errors: HostStubGenErrors, + stats: HostStubGenStats, ) { log.d("Entry: %s", entry.name) log.withIndent { @@ -300,7 +311,7 @@ class HostStubGen(val options: HostStubGenOptions) { // If it's a class, convert it. if (name.endsWith(".class")) { processSingleClass(inZip, entry, stubOutStream, implOutStream, filter, - packageRedirector, enableChecker, classes, errors) + packageRedirector, enableChecker, classes, errors, stats) return } @@ -354,6 +365,7 @@ class HostStubGen(val options: HostStubGenOptions) { enableChecker: Boolean, classes: ClassNodes, errors: HostStubGenErrors, + stats: HostStubGenStats, ) { val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "") val classPolicy = filter.getPolicyForClass(classInternalName) @@ -370,7 +382,7 @@ class HostStubGen(val options: HostStubGenOptions) { stubOutStream.putNextEntry(newEntry) convertClass(classInternalName, /*forImpl=*/false, bis, stubOutStream, filter, packageRedirector, enableChecker, classes, - errors) + errors, stats) stubOutStream.closeEntry() } } @@ -383,7 +395,7 @@ class HostStubGen(val options: HostStubGenOptions) { implOutStream.putNextEntry(newEntry) convertClass(classInternalName, /*forImpl=*/true, bis, implOutStream, filter, packageRedirector, enableChecker, classes, - errors) + errors, stats) implOutStream.closeEntry() } } @@ -403,6 +415,7 @@ class HostStubGen(val options: HostStubGenOptions) { enableChecker: Boolean, classes: ClassNodes, errors: HostStubGenErrors, + stats: HostStubGenStats, ) { val cr = ClassReader(input) @@ -420,6 +433,7 @@ class HostStubGen(val options: HostStubGenOptions) { enablePostTrace = options.enablePostTrace.get, enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get, errors = errors, + stats = stats, ) outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter, packageRedirector, forImpl, visitorOptions) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index d2ead18b5492..9f5d524517d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -108,6 +108,8 @@ class HostStubGenOptions( var enablePostTrace: SetOnce<Boolean> = SetOnce(false), var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false), + + var statsFile: SetOnce<String?> = SetOnce(null), ) { companion object { @@ -252,6 +254,8 @@ class HostStubGenOptions( "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg()) "--debug-log" -> setLogFile(LogLevel.Debug, nextArg()) + "--stats-file" -> ret.statsFile.setNextStringArg() + else -> throw ArgumentsException("Unknown option: $arg") } } catch (e: SetOnce.SetMoreThanOnceException) { @@ -387,6 +391,7 @@ class HostStubGenOptions( enablePreTrace=$enablePreTrace, enablePostTrace=$enablePostTrace, enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection, + statsFile=$statsFile, } """.trimIndent() } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt new file mode 100644 index 000000000000..fe4072f6b3ee --- /dev/null +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 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 + +import com.android.hoststubgen.asm.toHumanReadableClassName +import com.android.hoststubgen.filters.FilterPolicyWithReason +import java.io.PrintWriter + +open class HostStubGenStats { + data class Stats( + var supported: Int = 0, + var total: Int = 0, + val children: MutableMap<String, Stats> = mutableMapOf<String, Stats>(), + ) + + private val stats = mutableMapOf<String, Stats>() + + fun onVisitPolicyForMethod(fullClassName: String, policy: FilterPolicyWithReason) { + if (policy.isIgnoredForStats) return + + val packageName = resolvePackageName(fullClassName) + val className = resolveClassName(fullClassName) + + val packageStats = stats.getOrPut(packageName) { Stats() } + val classStats = packageStats.children.getOrPut(className) { Stats() } + + if (policy.policy.isSupported) { + packageStats.supported += 1 + classStats.supported += 1 + } + packageStats.total += 1 + classStats.total += 1 + } + + fun dump(pw: PrintWriter) { + pw.printf("PackageName,ClassName,SupportedMethods,TotalMethods\n") + stats.forEach { (packageName, packageStats) -> + if (packageStats.supported > 0) { + packageStats.children.forEach { (className, classStats) -> + pw.printf("%s,%s,%d,%d\n", packageName, className, + classStats.supported, classStats.total) + } + } + } + } + + private fun resolvePackageName(fullClassName: String): String { + val start = fullClassName.lastIndexOf('/') + return fullClassName.substring(0, start).toHumanReadableClassName() + } + + private fun resolveClassName(fullClassName: String): String { + val start = fullClassName.lastIndexOf('/') + val end = fullClassName.indexOf('$') + if (end == -1) { + return fullClassName.substring(start + 1) + } else { + return fullClassName.substring(start + 1, end) + } + } +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt index 93179969e968..4d211065f1c8 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt @@ -111,6 +111,16 @@ enum class FilterPolicy { } } + /** Returns whether a policy is considered supported. */ + val isSupported: Boolean + get() { + return when (this) { + // TODO: handle native method with no substitution as being unsupported + Stub, StubClass, Keep, KeepClass, SubstituteAndStub, SubstituteAndKeep -> true + else -> false + } + } + fun getSubstitutionBasePolicy(): FilterPolicy { return when (this) { SubstituteAndKeep -> Keep @@ -136,4 +146,4 @@ enum class FilterPolicy { fun withReason(reason: String): FilterPolicyWithReason { return FilterPolicyWithReason(this, reason) } -}
\ No newline at end of file +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt index b64a2f5fd8a5..53eb5a8c2fdc 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt @@ -63,4 +63,15 @@ data class FilterPolicyWithReason ( override fun toString(): String { return "[$policy - reason: $reason]" } -}
\ No newline at end of file + + /** Returns whether this policy should be ignored for stats. */ + val isIgnoredForStats: Boolean + get() { + return reason.contains("anonymous-inner-class") + || reason.contains("is-annotation") + || reason.contains("is-enum") + || reason.contains("is-synthetic-method") + || reason.contains("special-class") + || reason.contains("substitute-from") + } +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index ea7d1d0ff391..78b13fd36f06 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt @@ -119,14 +119,14 @@ class ImplicitOutputFilter( if (cn.isEnum()) { mn?.let { mn -> if (isAutoGeneratedEnumMember(mn)) { - return memberPolicy.withReason(classPolicy.reason).wrapReason("enum") + return memberPolicy.withReason(classPolicy.reason).wrapReason("is-enum") } } } // Keep (or stub) all members of annotations. if (cn.isAnnotation()) { - return memberPolicy.withReason(classPolicy.reason).wrapReason("annotation") + return memberPolicy.withReason(classPolicy.reason).wrapReason("is-annotation") } mn?.let { @@ -134,7 +134,7 @@ class ImplicitOutputFilter( // For synthetic methods (such as lambdas), let's just inherit the class's // policy. return memberPolicy.withReason(classPolicy.reason).wrapReason( - "synthetic method") + "is-synthetic-method") } } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 7fdd944770c6..6ad83fbab083 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -132,23 +132,24 @@ fun createFilterFromTextPolicyFile( throw ParseException( "Policy for AIDL classes already defined") } - aidlPolicy = policy.withReason("$FILTER_REASON (AIDL)") + aidlPolicy = policy.withReason( + "$FILTER_REASON (special-class AIDL)") } SpecialClass.FeatureFlags -> { if (featureFlagsPolicy != null) { throw ParseException( "Policy for feature flags already defined") } - featureFlagsPolicy = - policy.withReason("$FILTER_REASON (feature flags)") + featureFlagsPolicy = policy.withReason( + "$FILTER_REASON (special-class feature flags)") } SpecialClass.Sysprops -> { if (syspropsPolicy != null) { throw ParseException( "Policy for sysprops already defined") } - syspropsPolicy = - policy.withReason("$FILTER_REASON (sysprops)") + syspropsPolicy = policy.withReason( + "$FILTER_REASON (special-class sysprops)") } } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 21cfd4bcd0d8..c20aa8bc70ca 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt @@ -16,6 +16,7 @@ package com.android.hoststubgen.visitors import com.android.hoststubgen.HostStubGenErrors +import com.android.hoststubgen.HostStubGenStats import com.android.hoststubgen.LogLevel import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.UnifiedVisitor @@ -50,6 +51,7 @@ abstract class BaseAdapter ( */ data class Options ( val errors: HostStubGenErrors, + val stats: HostStubGenStats, val enablePreTrace: Boolean, val enablePostTrace: Boolean, val enableNonStubMethodCallDetection: Boolean, diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 416b78242899..beca945a2819 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -141,6 +141,11 @@ class ImplGeneratingAdapter( substituted: Boolean, superVisitor: MethodVisitor?, ): MethodVisitor? { + // Record statistics about visiting this method when visible. + if ((access and Opcodes.ACC_PRIVATE) == 0) { + options.stats.onVisitPolicyForMethod(currentClassName, policy) + } + // Inject method log, if needed. var innerVisitor = superVisitor |