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

Commit 50418c15 authored by John Wu's avatar John Wu
Browse files

[HostStubGen] Code cleanup

- Reduce unnecessary output filter layering: a lot of these filters are
  orthogonal to each other, so there is no need to do "layering" as
  these layers do not interact with each other. Merge them all into
  InMemoryOutputFilter.
- Consolidate the construction of the ClassVisitor layers into
  HostStubGenClassProcessor: previously the logic to build these layers
  are scattered in multiple places. Put them all into the same method to
  make it easier to understand and update these ClassVisitors.

Bug: 292141694
Flag: EXEMPT host side change only
Test: f/b/r/scripts/run-ravenwood-tests.sh
Change-Id: Ibec23a1282c5013541cfe2beb977d25d81bf4bb5
parent 24b00ea7
Loading
Loading
Loading
Loading
+56 −20
Original line number Diff line number Diff line
@@ -30,12 +30,15 @@ import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.utils.ClassPredicate
import com.android.hoststubgen.visitors.BaseAdapter
import com.android.hoststubgen.visitors.ImplGeneratingAdapter
import com.android.hoststubgen.visitors.PackageRedirectRemapper
import java.io.PrintWriter
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.commons.ClassRemapper
import org.objectweb.asm.util.CheckClassAdapter
import org.objectweb.asm.util.TraceClassVisitor

/**
 * This class implements bytecode transformation of HostStubGen.
@@ -133,21 +136,10 @@ class HostStubGenClassProcessor(
        return filter
    }

    fun processClassBytecode(bytecode: ByteArray): ByteArray {
        val cr = ClassReader(bytecode)

        // If the class was already processed previously, skip
        val clz = allClasses.getClass(cr.className)
        if (clz.findAnyAnnotation(processedAnnotation) != null) {
            return bytecode
        }
    private fun buildVisitor(base: ClassVisitor, className: String): ClassVisitor {
        // Connect to the base visitor
        var outVisitor: ClassVisitor = base

        // COMPUTE_FRAMES wouldn't be happy if code uses
        val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES
        val cw = ClassWriter(flags)

        // Connect to the class writer
        var outVisitor: ClassVisitor = cw
        if (options.enableClassChecker.get) {
            outVisitor = CheckClassAdapter(outVisitor)
        }
@@ -158,15 +150,59 @@ class HostStubGenClassProcessor(
        val visitorOptions = BaseAdapter.Options(
            errors = errors,
            stats = stats,
            enablePreTrace = options.enablePreTrace.get,
            enablePostTrace = options.enablePostTrace.get,
            deleteClassFinals = options.deleteFinals.get,
            deleteMethodFinals = options.deleteFinals.get,
        )
        outVisitor = BaseAdapter.getVisitor(
            cr.className, allClasses, outVisitor, filter,
            packageRedirector, visitorOptions

        val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))

        // Inject TraceClassVisitor for debugging.
        if (options.enablePostTrace.get) {
            outVisitor = TraceClassVisitor(outVisitor, verbosePrinter)
        }

        // Handle --package-redirect
        if (!packageRedirector.isEmpty) {
            // Don't apply the remapper on redirect-from classes.
            // Otherwise, if the target jar actually contains the "from" classes (which
            // may or may not be the case) they'd be renamed.
            // But we update all references in other places, so, a method call to a "from" class
            // would be replaced with the "to" class. All type references (e.g. variable types)
            // will be updated too.
            if (!packageRedirector.isTarget(className)) {
                outVisitor = ClassRemapper(outVisitor, packageRedirector)
            } else {
                log.v(
                    "Class $className is a redirect-from class, not applying" +
                            " --package-redirect"
                )
            }
        }

        outVisitor = ImplGeneratingAdapter(allClasses, outVisitor, filter, visitorOptions)

        // Inject TraceClassVisitor for debugging.
        if (options.enablePreTrace.get) {
            outVisitor = TraceClassVisitor(outVisitor, verbosePrinter)
        }

        return outVisitor
    }

    fun processClassBytecode(bytecode: ByteArray): ByteArray {
        val cr = ClassReader(bytecode)

        // If the class was already processed previously, skip
        val clz = allClasses.getClass(cr.className)
        if (clz.findAnyAnnotation(processedAnnotation) != null) {
            return bytecode
        }

        // COMPUTE_FRAMES wouldn't be happy if code uses
        val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES
        val cw = ClassWriter(flags)

        val outVisitor = buildVisitor(cw, cr.className)

        cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
+13 −14
Original line number Diff line number Diff line
@@ -97,13 +97,12 @@ abstract class DelegatingFilter(
    }

    override fun getMethodCallReplaceTo(
        callerClassName: String,
        callerMethodName: String,
        className: String,
        methodName: String,
        descriptor: String,
    ): MethodReplaceTarget? {
        return fallback.getMethodCallReplaceTo(
            callerClassName, callerMethodName, className, methodName, descriptor)
            className, methodName, descriptor
        )
    }
}
+64 −21
Original line number Diff line number Diff line
@@ -28,10 +28,12 @@ class InMemoryOutputFilter(
    private val classes: ClassNodes,
    fallback: OutputFilter,
) : DelegatingFilter(fallback) {
    private val mPolicies: MutableMap<String, FilterPolicyWithReason> = mutableMapOf()
    private val mRenames: MutableMap<String, String> = mutableMapOf()
    private val mRedirectionClasses: MutableMap<String, String> = mutableMapOf()
    private val mClassLoadHooks: MutableMap<String, String> = mutableMapOf()
    private val mPolicies = mutableMapOf<String, FilterPolicyWithReason>()
    private val mRenames = mutableMapOf<String, String>()
    private val mRedirectionClasses = mutableMapOf<String, String>()
    private val mClassLoadHooks = mutableMapOf<String, String>()
    private val mMethodCallReplaceSpecs = mutableListOf<MethodCallReplaceSpec>()
    private val mTypeRenameSpecs = mutableListOf<TypeRenameSpec>()

    private fun getClassKey(className: String): String {
        return className.toHumanReadableClassName()
@@ -45,10 +47,6 @@ class InMemoryOutputFilter(
        return getClassKey(className) + "." + methodName + ";" + signature
    }

    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
        return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
    }

    private fun checkClass(className: String) {
        if (classes.findClass(className) == null) {
            log.w("Unknown class $className")
@@ -74,6 +72,10 @@ class InMemoryOutputFilter(
        }
    }

    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
        return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
    }

    fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
        checkClass(className)
        mPolicies[getClassKey(className)] = policy
@@ -135,11 +137,52 @@ class InMemoryOutputFilter(
    }

    override fun getClassLoadHooks(className: String): List<String> {
        return addNonNullElement(super.getClassLoadHooks(className),
            mClassLoadHooks[getClassKey(className)])
        return addNonNullElement(
            super.getClassLoadHooks(className),
            mClassLoadHooks[getClassKey(className)]
        )
    }

    fun setClassLoadHook(className: String, methodName: String) {
        mClassLoadHooks[getClassKey(className)] = methodName.toHumanReadableMethodName()
    }

    override fun hasAnyMethodCallReplace(): Boolean {
        return mMethodCallReplaceSpecs.isNotEmpty() || super.hasAnyMethodCallReplace()
    }

    override fun getMethodCallReplaceTo(
        className: String,
        methodName: String,
        descriptor: String,
    ): MethodReplaceTarget? {
        // Maybe use 'Tri' if we end up having too many replacements.
        mMethodCallReplaceSpecs.forEach {
            if (className == it.fromClass &&
                methodName == it.fromMethod
            ) {
                if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
                    return MethodReplaceTarget(it.toClass, it.toMethod)
                }
            }
        }
        return super.getMethodCallReplaceTo(className, methodName, descriptor)
    }

    fun setMethodCallReplaceSpec(spec: MethodCallReplaceSpec) {
        mMethodCallReplaceSpecs.add(spec)
    }

    override fun remapType(className: String): String? {
        mTypeRenameSpecs.forEach {
            if (it.typeInternalNamePattern.matcher(className).matches()) {
                return it.typeInternalNamePrefix + className
            }
        }
        return super.remapType(className)
    }

    fun setRemapTypeSpec(spec: TypeRenameSpec) {
        mTypeRenameSpecs.add(spec)
    }
}
+4 −6
Original line number Diff line number Diff line
@@ -108,8 +108,6 @@ abstract class OutputFilter {
     * If a method call should be forwarded to another method, return the target's class / method.
     */
    open fun getMethodCallReplaceTo(
        callerClassName: String,
        callerMethodName: String,
        className: String,
        methodName: String,
        descriptor: String,
+33 −34
Original line number Diff line number Diff line
@@ -58,6 +58,23 @@ enum class SpecialClass {
    RFile,
}

data class MethodCallReplaceSpec(
    val fromClass: String,
    val fromMethod: String,
    val fromDescriptor: String,
    val toClass: String,
    val toMethod: String,
)

/**
 * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
 * to it.
 */
data class TypeRenameSpec(
    val typeInternalNamePattern: Pattern,
    val typeInternalNamePrefix: String,
)

/**
 * This receives [TextFileFilterPolicyBuilder] parsing result.
 */
@@ -99,7 +116,7 @@ interface PolicyFileProcessor {
        className: String,
        methodName: String,
        methodDesc: String,
        replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
        replaceSpec: MethodCallReplaceSpec,
    )
}

@@ -116,9 +133,6 @@ class TextFileFilterPolicyBuilder(
    private var featureFlagsPolicy: FilterPolicyWithReason? = null
    private var syspropsPolicy: FilterPolicyWithReason? = null
    private var rFilePolicy: FilterPolicyWithReason? = null
    private val typeRenameSpec = mutableListOf<TextFilePolicyRemapperFilter.TypeRenameSpec>()
    private val methodReplaceSpec =
        mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()

    /**
     * Fields for a filter chain used for "partial allowlisting", which are used by
@@ -126,17 +140,14 @@ class TextFileFilterPolicyBuilder(
     */
    private val annotationAllowedInMemoryFilter: InMemoryOutputFilter
    val annotationAllowedMembersFilter: OutputFilter
        get() = annotationAllowedInMemoryFilter

    private val annotationAllowedPolicy = FilterPolicy.AnnotationAllowed.withReason(FILTER_REASON)

    init {
        // Create a filter that checks "partial allowlisting".
        var aaf: OutputFilter = ConstantFilter(FilterPolicy.Remove, "default disallowed")

        aaf = InMemoryOutputFilter(classes, aaf)
        annotationAllowedInMemoryFilter = aaf

        annotationAllowedMembersFilter = annotationAllowedInMemoryFilter
        val filter = ConstantFilter(FilterPolicy.Remove, "default disallowed")
        annotationAllowedInMemoryFilter = InMemoryOutputFilter(classes, filter)
    }

    /**
@@ -153,20 +164,10 @@ class TextFileFilterPolicyBuilder(
     * Generate the resulting [OutputFilter].
     */
    fun createOutputFilter(): OutputFilter {
        var ret: OutputFilter = imf
        if (typeRenameSpec.isNotEmpty()) {
            ret = TextFilePolicyRemapperFilter(typeRenameSpec, ret)
        }
        if (methodReplaceSpec.isNotEmpty()) {
            ret = TextFilePolicyMethodReplaceFilter(methodReplaceSpec, classes, ret)
        }

        // Wrap the in-memory-filter with AHF.
        ret = AndroidHeuristicsFilter(
            classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, ret
        return AndroidHeuristicsFilter(
            classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf
        )

        return ret
    }

    private inner class Processor : PolicyFileProcessor {
@@ -180,9 +181,7 @@ class TextFileFilterPolicyBuilder(
        }

        override fun onRename(pattern: Pattern, prefix: String) {
            typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
                pattern, prefix
            )
            imf.setRemapTypeSpec(TypeRenameSpec(pattern, prefix))
        }

        override fun onClassStart(className: String) {
@@ -284,12 +283,12 @@ class TextFileFilterPolicyBuilder(
            className: String,
            methodName: String,
            methodDesc: String,
            replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
            replaceSpec: MethodCallReplaceSpec,
        ) {
            // Keep the source method, because the target method may call it.
            imf.setPolicyForMethod(className, methodName, methodDesc,
                FilterPolicy.Keep.withReason(FILTER_REASON))
            methodReplaceSpec.add(replaceSpec)
            imf.setMethodCallReplaceSpec(replaceSpec)
        }
    }
}
@@ -630,8 +629,8 @@ class TextFileFilterPolicyParser {
            if (classAndMethod != null) {
                // If the substitution target contains a ".", then
                // it's a method call redirect.
                val spec = TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
                        currentClassName!!.toJvmClassName(),
                val spec = MethodCallReplaceSpec(
                    className.toJvmClassName(),
                    methodName,
                    signature,
                    classAndMethod.first.toJvmClassName(),
Loading