Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d9b7a74e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[Ravenwood] Improve "supported API" CSV" into main

parents 71c42508 7c7f4903
Loading
Loading
Loading
Loading
+11 −4
Original line number Diff line number Diff line
@@ -39,13 +39,17 @@ dump() {
    local jar=$1
    local file=$2

    # Use sed to remove the header + prepend the jar filename.
    sed -e '1d' -e "s/^/$jar,/" $file
}

collect_stats() {
    local out="$1"
    {
        echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
        # Copy the header, with the first column appended.
        echo -n "Jar,"
        head -n 1 hoststubgen_framework-minus-apex_stats.csv

        dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
        dump "service.core"  hoststubgen_services.core_stats.csv
    } > "$out"
@@ -56,7 +60,10 @@ collect_stats() {
collect_apis() {
    local out="$1"
    {
        echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
        # Copy the header, with the first column appended.
        echo -n "Jar,"
        head -n 1 hoststubgen_framework-minus-apex_apis.csv

        dump "framework-minus-apex"  hoststubgen_framework-minus-apex_apis.csv
        dump "service.core"  hoststubgen_services.core_apis.csv
    } > "$out"
+6 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.hoststubgen

import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.dumper.ApiDumper
import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@ class HostStubGen(val options: HostStubGenOptions) {
            log.i("Dump file created at $it")
        }
        options.apiListFile.ifSet {
            PrintWriter(it).use { pw -> stats.dumpApis(pw) }
            PrintWriter(it).use { pw ->
                // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
                // framework-minus-apex.jar so that we can dump inherited methods from it.
                ApiDumper(pw, allClasses, null, filter).dump()
            }
            log.i("API list file created at $it")
        }
    }
+4 −41
Original line number Diff line number Diff line
@@ -15,7 +15,8 @@
 */
package com.android.hoststubgen

import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
@@ -55,8 +56,8 @@ open class HostStubGenStats {
        // Ignore methods where policy isn't relevant
        if (policy.isIgnoredForStats) return

        val packageName = resolvePackageName(fullClassName)
        val className = resolveOuterClassName(fullClassName)
        val packageName = getPackageNameFromFullClassName(fullClassName)
        val className = getOuterClassNameFromFullClassName(fullClassName)

        // Ignore methods for certain generated code
        if (className.endsWith("Proto")
@@ -88,42 +89,4 @@ open class HostStubGenStats {
            }
        }
    }

    fun dumpApis(pw: PrintWriter) {
        pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
        apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
            .forEach { api ->
            pw.printf(
                "%s,%s,%s,%s\n",
                csvEscape(resolvePackageName(api.fullClassName)),
                csvEscape(resolveClassName(api.fullClassName)),
                csvEscape(api.methodName),
                csvEscape(api.methodDesc),
                )
        }
    }

    private fun resolvePackageName(fullClassName: String): String {
        val start = fullClassName.lastIndexOf('/')
        return fullClassName.substring(0, start).toHumanReadableClassName()
    }

    private fun resolveOuterClassName(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)
        }
    }

    private fun resolveClassName(fullClassName: String): String {
        val pos = fullClassName.lastIndexOf('/')
        if (pos == -1) {
            return fullClassName
        } else {
            return fullClassName.substring(pos + 1)
        }
    }
}
+33 −5
Original line number Diff line number Diff line
@@ -76,17 +76,45 @@ fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): Strin
    return null
}

private val removeLastElement = """[./][^./]*$""".toRegex()
val periodOrSlash = charArrayOf('.', '/')

fun getPackageNameFromClassName(className: String): String {
    return className.replace(removeLastElement, "")
fun getPackageNameFromFullClassName(fullClassName: String): String {
    val pos = fullClassName.lastIndexOfAny(periodOrSlash)
    if (pos == -1) {
        return ""
    } else {
        return fullClassName.substring(0, pos)
    }
}

fun resolveClassName(className: String, packageName: String): String {
fun getClassNameFromFullClassName(fullClassName: String): String {
    val pos = fullClassName.lastIndexOfAny(periodOrSlash)
    if (pos == -1) {
        return fullClassName
    } else {
        return fullClassName.substring(pos + 1)
    }
}

fun getOuterClassNameFromFullClassName(fullClassName: String): String {
    val start = fullClassName.lastIndexOfAny(periodOrSlash)
    val end = fullClassName.indexOf('$')
    if (end == -1) {
        return fullClassName.substring(start + 1)
    } else {
        return fullClassName.substring(start + 1, end)
    }
}

/**
 * If [className] is a fully qualified name, just return it.
 * Otherwise, prepend [defaultPackageName].
 */
fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
    if (className.contains('.') || className.contains('/')) {
        return className
    }
    return "$packageName.$className"
    return "$defaultPackageName.$className"
}

fun String.toJvmClassName(): String {
+202 −0
Original line number Diff line number Diff line
/*
 * 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.dumper

import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
import com.android.hoststubgen.asm.CTOR_NAME
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.getClassNameFromFullClassName
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.csvEscape
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.log
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
import java.io.PrintWriter

/**
 * Dump all the API methods in [classes], with inherited methods, with their policies.
 */
class ApiDumper(
    val pw: PrintWriter,
    val classes: ClassNodes,
    val frameworkClasses: ClassNodes?,
    val filter: OutputFilter,
) {
    private data class MethodKey(
        val name: String,
        val descriptor: String,
    )

    val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")

    private val shownMethods = mutableSetOf<MethodKey>()

    /**
     * Do the dump.
     */
    fun dump() {
        pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
                ",Supported,Policy,Reason\n")

        classes.forEach { classNode ->
            shownMethods.clear()
            dump(classNode, classNode)
        }
    }

    private fun dumpMethod(
        classPackage: String,
        className: String,
        isSuperClass: Boolean,
        methodClassName: String,
        methodName: String,
        methodDesc: String,
        policy: FilterPolicyWithReason,
    ) {
        pw.printf(
            "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
            csvEscape(classPackage),
            csvEscape(className),
            if (isSuperClass) { 1 } else { 0 },
            csvEscape(methodClassName),
            csvEscape(methodName),
            csvEscape(methodDesc),
            if (policy.policy.isSupported) { 1 } else { 0 },
            policy.policy,
            csvEscape(policy.reason),
        )
    }

    private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
        val methodKey = MethodKey(methodName, methodDesc)

        if (shownMethods.contains(methodKey)) {
            return true
        }
        shownMethods.add(methodKey)
        return false
    }

    private fun dump(
        dumpClass: ClassNode,
        methodClass: ClassNode,
        ) {
        val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
        val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()

        val isSuperClass = dumpClass != methodClass

        methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->

            // Don't print ctor's from super classes.
            if (isSuperClass) {
                if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
                    return@forEach
                }
            }
            // If we already printed the method from a subclass, don't print it.
            if (isDuplicate(method.name, method.desc)) {
                return@forEach
            }

            val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)

            // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
            // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
            // and for now we don't have an easy way to detect it.
            if (policy.policy == FilterPolicy.Remove) {
                return@forEach
            }

            val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)

            dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
                renameTo ?: method.name, method.desc, policy)
       }

        // Dump super class methods.
        dumpSuper(dumpClass, methodClass.superName)

        // Dump interface methods (which may have default methods).
        methodClass.interfaces?.sorted()?.forEach { interfaceName ->
            dumpSuper(dumpClass, interfaceName)
        }
    }

    /**
     * Dump a given super class / interface.
     */
    private fun dumpSuper(
        dumpClass: ClassNode,
        methodClassName: String,
    ) {
        classes.findClass(methodClassName)?.let { methodClass ->
            dump(dumpClass, methodClass)
            return
        }
        frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
            dump(dumpClass, methodClass)
            return
        }
        if (methodClassName.startsWith("java/") ||
            methodClassName.startsWith("javax/")
            ) {
            dumpStandardClass(dumpClass, methodClassName)
            return
        }
        log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
    }

    /**
     * Dump methods from Java standard classes.
     */
    private fun dumpStandardClass(
        dumpClass: ClassNode,
        methodClassName: String,
    ) {
        val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
        val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()

        val methodClassName = methodClassName.toHumanReadableClassName()

        try {
            val clazz = Class.forName(methodClassName)

            // Method.getMethods() returns only public methods, but with inherited ones.
            // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
            //
            // Since we're only interested in public ones, just use getMethods().
            clazz.methods.forEach { method ->
                val methodName = method.name
                val methodDesc = Type.getMethodDescriptor(method)

                // If we already printed the method from a subclass, don't print it.
                if (isDuplicate(methodName, methodDesc)) {
                    return@forEach
                }

                dumpMethod(pkg, cls, true, methodClassName,
                    methodName, methodDesc, javaStandardApiPolicy)
            }
        } catch (e: ClassNotFoundException) {
            log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
        }
    }
}
Loading