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

Commit 18b74782 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

HostStubGen cleanup

Remove BaseAdapter

Bug: 292141694
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s
Flag: EXEMPT host test change only
Change-Id: I7e577489613aaaf4ae39f3e6a8ffb416d1379f4e
parent 2fc51dd2
Loading
Loading
Loading
Loading
+1 −2
Original line number Original line Diff line number Diff line
@@ -29,7 +29,6 @@ import com.android.hoststubgen.filters.SanitizationFilter
import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.utils.ClassPredicate
import com.android.hoststubgen.utils.ClassPredicate
import com.android.hoststubgen.visitors.BaseAdapter
import com.android.hoststubgen.visitors.ImplGeneratingAdapter
import com.android.hoststubgen.visitors.ImplGeneratingAdapter
import com.android.hoststubgen.visitors.JdkPatchVisitor
import com.android.hoststubgen.visitors.JdkPatchVisitor
import com.android.hoststubgen.visitors.PackageRedirectRemapper
import com.android.hoststubgen.visitors.PackageRedirectRemapper
@@ -70,7 +69,7 @@ class HostStubGenClassProcessor(
        // Remapping should happen at the end.
        // Remapping should happen at the end.
        outVisitor = ClassRemapper(outVisitor, remapper)
        outVisitor = ClassRemapper(outVisitor, remapper)


        val visitorOptions = BaseAdapter.Options(
        val visitorOptions = ImplGeneratingAdapter.Options(
            errors = errors,
            errors = errors,
            deleteClassFinals = options.deleteFinals.get,
            deleteClassFinals = options.deleteFinals.get,
            deleteMethodFinals = options.deleteFinals.get,
            deleteMethodFinals = options.deleteFinals.get,
+5 −1
Original line number Original line Diff line number Diff line
@@ -349,8 +349,12 @@ enum class Visibility {
    }
    }
}
}


fun isEnum(access: Int): Boolean {
    return (access and Opcodes.ACC_ENUM) != 0
}

fun ClassNode.isEnum(): Boolean {
fun ClassNode.isEnum(): Boolean {
    return (this.access and Opcodes.ACC_ENUM) != 0
    return isEnum(this.access)
}
}


fun ClassNode.isAnnotation(): Boolean {
fun ClassNode.isAnnotation(): Boolean {
+0 −248
Original line number Original line Diff line number Diff line
/*
 * 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.visitors

import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.log
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes

const val OPCODE_VERSION = Opcodes.ASM9

abstract class BaseAdapter(
    protected val classes: ClassNodes,
    nextVisitor: ClassVisitor,
    protected val filter: OutputFilter,
    protected val options: Options,
) : ClassVisitor(OPCODE_VERSION, nextVisitor) {

    /**
     * Options to control the behavior.
     */
    data class Options(
        val errors: HostStubGenErrors,
        val deleteClassFinals: Boolean,
        val deleteMethodFinals: Boolean,
        val throwExceptionType: String,
        // We don't remove finals from fields, because final fields have a stronger memory
        // guarantee than non-final fields, see:
        // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5
        // i.e. changing a final field to non-final _could_ result in different behavior.
    )

    protected lateinit var currentPackageName: String
    protected lateinit var currentClassName: String
    protected var redirectionClass: String? = null
    protected lateinit var classPolicy: FilterPolicyWithReason

    private fun isEnum(access: Int): Boolean {
        return (access and Opcodes.ACC_ENUM) != 0
    }

    protected fun modifyClassAccess(access: Int): Int {
        if (options.deleteClassFinals && !isEnum(access)) {
            return access and Opcodes.ACC_FINAL.inv()
        }
        return access
    }

    protected fun modifyMethodAccess(access: Int): Int {
        if (options.deleteMethodFinals) {
            return access and Opcodes.ACC_FINAL.inv()
        }
        return access
    }

    override fun visit(
        version: Int,
        origAccess: Int,
        name: String,
        signature: String?,
        superName: String?,
        interfaces: Array<String>,
    ) {
        val access = modifyClassAccess(origAccess)
        super.visit(version, access, name, signature, superName, interfaces)
        currentClassName = name
        currentPackageName = getPackageNameFromFullClassName(name)
        classPolicy = filter.getPolicyForClass(currentClassName)
        redirectionClass = filter.getRedirectionClass(currentClassName)

        log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
        log.indent()
        log.v("Emitting class: %s", name)
        log.indent()

        // Inject annotations to generated classes.
        UnifiedVisitor.on(this).visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
    }

    override fun visitEnd() {
        log.unindent()
        log.unindent()
        super.visitEnd()
    }

    var skipMemberModificationNestCount = 0

    /**
     * This method allows writing class members without any modifications.
     */
    protected inline fun writeRawMembers(callback: () -> Unit) {
        skipMemberModificationNestCount++
        try {
            callback()
        } finally {
            skipMemberModificationNestCount--
        }
    }

    override fun visitField(
        access: Int,
        name: String,
        descriptor: String,
        signature: String?,
        value: Any?,
    ): FieldVisitor? {
        if (skipMemberModificationNestCount > 0) {
            return super.visitField(access, name, descriptor, signature, value)
        }
        val policy = filter.getPolicyForField(currentClassName, name)
        log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy)

        log.withIndent {
            if (policy.policy == FilterPolicy.Remove) {
                log.d("Removing %s %s", name, policy)
                return null
            }

            log.v("Emitting field: %s %s %s", name, descriptor, policy)
            val ret = super.visitField(access, name, descriptor, signature, value)

            UnifiedVisitor.on(ret)
                .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)

            return ret
        }
    }

    final override fun visitMethod(
        origAccess: Int,
        name: String,
        descriptor: String,
        signature: String?,
        exceptions: Array<String>?,
    ): MethodVisitor? {
        val access = modifyMethodAccess(origAccess)
        if (skipMemberModificationNestCount > 0) {
            return super.visitMethod(access, name, descriptor, signature, exceptions)
        }
        val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
        log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)

        log.withIndent {
            // If it's a substitute-from method, then skip (== remove).
            // Instead of this method, we rename the substitute-to method with the original
            // name, in the "Maybe rename the method" part below.
            val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
            if (policy.policy == FilterPolicy.Substitute) {
                log.d("Skipping %s%s %s", name, descriptor, policy)
                return null
            }
            if (p.policy == FilterPolicy.Remove) {
                log.d("Removing %s%s %s", name, descriptor, policy)
                return null
            }

            var newAccess = access

            // Maybe rename the method.
            val newName: String
            val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
            if (renameTo != null) {
                newName = renameTo

                // It's confusing, but here, `newName` is the original method name
                // (the one with the @substitute/replace annotation).
                // `name` is the name of the method we're currently visiting, so it's usually a
                // "...$ravewnwood" name.
                newAccess = checkSubstitutionMethodCompatibility(
                    classes, currentClassName, newName, name, descriptor, options.errors
                )
                if (newAccess == NOT_COMPATIBLE) {
                    return null
                }
                newAccess = modifyMethodAccess(newAccess)

                log.v(
                    "Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
                    newName, policy
                )
            } else {
                log.v("Emitting method: %s%s %s", name, descriptor, policy)
                newName = name
            }

            // Let subclass update the flag.
            // But note, we only use it when calling the super's method,
            // but not for visitMethodInner(), because when subclass wants to change access,
            // it can do so inside visitMethodInner().
            newAccess = updateAccessFlags(newAccess, name, descriptor, policy.policy)

            val ret = visitMethodInner(
                access, newName, descriptor, signature, exceptions, policy,
                renameTo != null,
                super.visitMethod(newAccess, newName, descriptor, signature, exceptions)
            )

            ret?.let {
                UnifiedVisitor.on(ret)
                    .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
            }

            return ret
        }
    }

    open fun updateAccessFlags(
        access: Int,
        name: String,
        descriptor: String,
        policy: FilterPolicy,
    ): Int {
        return access
    }

    abstract fun visitMethodInner(
        access: Int,
        name: String,
        descriptor: String,
        signature: String?,
        exceptions: Array<String>?,
        policy: FilterPolicyWithReason,
        substituted: Boolean,
        superVisitor: MethodVisitor?,
    ): MethodVisitor?
}
+191 −7
Original line number Original line Diff line number Diff line
@@ -15,12 +15,16 @@
 */
 */
package com.android.hoststubgen.visitors
package com.android.hoststubgen.visitors


import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
import com.android.hoststubgen.asm.CTOR_NAME
import com.android.hoststubgen.asm.CTOR_NAME
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.UnifiedVisitor
import com.android.hoststubgen.asm.adjustStackForConstructorRedirection
import com.android.hoststubgen.asm.adjustStackForConstructorRedirection
import com.android.hoststubgen.asm.changeMethodDescriptorReturnType
import com.android.hoststubgen.asm.changeMethodDescriptorReturnType
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.asm.isEnum
import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.asm.writeByteCodeToPushArguments
import com.android.hoststubgen.asm.writeByteCodeToPushArguments
@@ -29,11 +33,14 @@ import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
import com.android.hoststubgen.hosthelper.HostTestUtils
import com.android.hoststubgen.hosthelper.HostTestUtils
import com.android.hoststubgen.log
import com.android.hoststubgen.log
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.INVOKEINTERFACE
import org.objectweb.asm.Opcodes.INVOKEINTERFACE
@@ -42,18 +49,56 @@ import org.objectweb.asm.Opcodes.INVOKESTATIC
import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
import org.objectweb.asm.Type
import org.objectweb.asm.Type


const val OPCODE_VERSION = Opcodes.ASM9

/**
/**
 * An adapter that generates the "impl" class file from an input class file.
 * An adapter that generates the "impl" class file from an input class file.
 */
 */
class ImplGeneratingAdapter(
class ImplGeneratingAdapter(
    classes: ClassNodes,
    val classes: ClassNodes,
    nextVisitor: ClassVisitor,
    nextVisitor: ClassVisitor,
    filter: OutputFilter,
    val filter: OutputFilter,
    options: Options,
    val options: Options,
) : BaseAdapter(classes, nextVisitor, filter, options) {
) : ClassVisitor(OPCODE_VERSION, nextVisitor) {

    /**
     * Options to control the behavior.
     */
    data class Options(
        val errors: HostStubGenErrors,

        val deleteClassFinals: Boolean,
        val deleteMethodFinals: Boolean,
        // We don't remove finals from fields, because final fields have a stronger memory
        // guarantee than non-final fields, see:
        // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5
        // i.e. changing a final field to non-final _could_ result in different behavior.
        // val deleteFieldFinals: Boolean,

        val throwExceptionType: String,
    )

    private lateinit var currentPackageName: String
    private lateinit var currentClassName: String
    private var redirectionClass: String? = null
    private lateinit var classPolicy: FilterPolicyWithReason


    private var classLoadHooks: List<String> = emptyList()
    private var classLoadHooks: List<String> = emptyList()


    private fun maybeRemoveFinalFromClass(access: Int): Int {
        if (options.deleteClassFinals && !isEnum(access)) {
            return access and Opcodes.ACC_FINAL.inv()
        }
        return access
    }

    private fun maybeRemoveFinalFromMethod(access: Int): Int {
        if (options.deleteMethodFinals) {
            return access and Opcodes.ACC_FINAL.inv()
        }
        return access
    }

    override fun visit(
    override fun visit(
        version: Int,
        version: Int,
        origAccess: Int,
        origAccess: Int,
@@ -62,9 +107,21 @@ class ImplGeneratingAdapter(
        superName: String?,
        superName: String?,
        interfaces: Array<String>
        interfaces: Array<String>
    ) {
    ) {
        val access = modifyClassAccess(origAccess)
        val access = maybeRemoveFinalFromClass(origAccess)

        super.visit(version, access, name, signature, superName, interfaces)
        super.visit(version, access, name, signature, superName, interfaces)


        currentClassName = name
        currentPackageName = getPackageNameFromFullClassName(name)
        classPolicy = filter.getPolicyForClass(currentClassName)
        redirectionClass = filter.getRedirectionClass(currentClassName)
        log.d("[%s] visit: %s (package: %s)", javaClass.simpleName, name, currentPackageName)
        log.indent()
        log.v("Emitting class: %s", name)
        log.indent()
        // Inject annotations to generated classes.
        UnifiedVisitor.on(this).visitAnnotation(CLASS_DESCRIPTOR, true)

        classLoadHooks = filter.getClassLoadHooks(currentClassName)
        classLoadHooks = filter.getClassLoadHooks(currentClassName)


        // classLoadHookMethod is non-null, then we need to inject code to call it
        // classLoadHookMethod is non-null, then we need to inject code to call it
@@ -80,6 +137,26 @@ class ImplGeneratingAdapter(
        }
        }
    }
    }


    override fun visitEnd() {
        log.unindent()
        log.unindent()
        super.visitEnd()
    }

    var skipMemberModificationNestCount = 0

    /**
     * This method allows writing class members without any modifications.
     */
    private inline fun writeRawMembers(callback: () -> Unit) {
        skipMemberModificationNestCount++
        try {
            callback()
        } finally {
            skipMemberModificationNestCount--
        }
    }

    private fun injectClassLoadHook() {
    private fun injectClassLoadHook() {
        writeRawMembers {
        writeRawMembers {
            // Create a class initializer to call onClassLoaded().
            // Create a class initializer to call onClassLoaded().
@@ -124,7 +201,36 @@ class ImplGeneratingAdapter(
        }
        }
    }
    }


    override fun updateAccessFlags(
    override fun visitField(
        access: Int,
        name: String,
        descriptor: String,
        signature: String?,
        value: Any?,
    ): FieldVisitor? {
        if (skipMemberModificationNestCount > 0) {
            return super.visitField(access, name, descriptor, signature, value)
        }
        val policy = filter.getPolicyForField(currentClassName, name)
        log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy)

        log.withIndent {
            if (policy.policy == FilterPolicy.Remove) {
                log.d("Removing %s %s", name, policy)
                return null
            }

            log.v("Emitting field: %s %s %s", name, descriptor, policy)
            val ret = super.visitField(access, name, descriptor, signature, value)

            UnifiedVisitor.on(ret)
                .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)

            return ret
        }
    }

    private fun updateMethodAccessFlags(
        access: Int,
        access: Int,
        name: String,
        name: String,
        descriptor: String,
        descriptor: String,
@@ -138,7 +244,85 @@ class ImplGeneratingAdapter(
        return access
        return access
    }
    }


    override fun visitMethodInner(
    override fun visitMethod(
        origAccess: Int,
        name: String,
        descriptor: String,
        signature: String?,
        exceptions: Array<String>?,
    ): MethodVisitor? {
        val access = maybeRemoveFinalFromMethod(origAccess)
        if (skipMemberModificationNestCount > 0) {
            return super.visitMethod(access, name, descriptor, signature, exceptions)
        }
        val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
        log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)

        log.withIndent {
            // If it's a substitute-from method, then skip (== remove).
            // Instead of this method, we rename the substitute-to method with the original
            // name, in the "Maybe rename the method" part below.
            val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
            if (policy.policy == FilterPolicy.Substitute) {
                log.d("Skipping %s%s %s", name, descriptor, policy)
                return null
            }
            if (p.policy == FilterPolicy.Remove) {
                log.d("Removing %s%s %s", name, descriptor, policy)
                return null
            }

            var newAccess = access

            // Maybe rename the method.
            val newName: String
            val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
            if (renameTo != null) {
                newName = renameTo

                // It's confusing, but here, `newName` is the original method name
                // (the one with the @substitute/replace annotation).
                // `name` is the name of the method we're currently visiting, so it's usually a
                // "...$ravewnwood" name.
                newAccess = checkSubstitutionMethodCompatibility(
                    classes, currentClassName, newName, name, descriptor, options.errors
                )
                if (newAccess == NOT_COMPATIBLE) {
                    return null
                }
                newAccess = maybeRemoveFinalFromMethod(newAccess)

                log.v(
                    "Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
                    newName, policy
                )
            } else {
                log.v("Emitting method: %s%s %s", name, descriptor, policy)
                newName = name
            }

            // Let subclass update the flag.
            // But note, we only use it when calling the super's method,
            // but not for visitMethodInner(), because when subclass wants to change access,
            // it can do so inside visitMethodInner().
            newAccess = updateMethodAccessFlags(newAccess, name, descriptor, policy.policy)

            val ret = visitMethodInner(
                access, newName, descriptor, signature, exceptions, policy,
                renameTo != null,
                super.visitMethod(newAccess, newName, descriptor, signature, exceptions)
            )

            ret?.let {
                UnifiedVisitor.on(ret)
                    .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
            }

            return ret
        }
    }

    private fun visitMethodInner(
        access: Int,
        access: Int,
        name: String,
        name: String,
        descriptor: String,
        descriptor: String,