Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt +1 −2 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import com.android.hoststubgen.filters.SanitizationFilter 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.JdkPatchVisitor import com.android.hoststubgen.visitors.PackageRedirectRemapper Loading Loading @@ -70,7 +69,7 @@ class HostStubGenClassProcessor( // Remapping should happen at the end. outVisitor = ClassRemapper(outVisitor, remapper) val visitorOptions = BaseAdapter.Options( val visitorOptions = ImplGeneratingAdapter.Options( errors = errors, deleteClassFinals = options.deleteFinals.get, deleteMethodFinals = options.deleteFinals.get, Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt +5 −1 Original line number Diff line number Diff line Loading @@ -349,8 +349,12 @@ enum class Visibility { } } fun isEnum(access: Int): Boolean { return (access and Opcodes.ACC_ENUM) != 0 } fun ClassNode.isEnum(): Boolean { return (this.access and Opcodes.ACC_ENUM) != 0 return isEnum(this.access) } fun ClassNode.isAnnotation(): Boolean { Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.ktdeleted 100644 → 0 +0 −248 Original line number 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? } ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +191 −7 Original line number Diff line number Diff line Loading @@ -15,12 +15,16 @@ */ 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_NAME import com.android.hoststubgen.asm.CTOR_NAME import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.UnifiedVisitor import com.android.hoststubgen.asm.adjustStackForConstructorRedirection 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.toJvmClassName import com.android.hoststubgen.asm.writeByteCodeToPushArguments Loading @@ -29,11 +33,14 @@ import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.FilterPolicyWithReason import com.android.hoststubgen.filters.OutputFilter 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.HostStubGenProcessedAsThrow import com.android.hoststubgen.hosthelper.HostTestUtils 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 import org.objectweb.asm.Opcodes.INVOKEINTERFACE Loading @@ -42,18 +49,56 @@ import org.objectweb.asm.Opcodes.INVOKESTATIC import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.Type const val OPCODE_VERSION = Opcodes.ASM9 /** * An adapter that generates the "impl" class file from an input class file. */ class ImplGeneratingAdapter( classes: ClassNodes, val classes: ClassNodes, nextVisitor: ClassVisitor, filter: OutputFilter, options: Options, ) : BaseAdapter(classes, nextVisitor, filter, options) { val filter: OutputFilter, val options: 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 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( version: Int, origAccess: Int, Loading @@ -62,9 +107,21 @@ class ImplGeneratingAdapter( superName: String?, interfaces: Array<String> ) { val access = modifyClassAccess(origAccess) val access = maybeRemoveFinalFromClass(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)", 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) // classLoadHookMethod is non-null, then we need to inject code to call it Loading @@ -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() { writeRawMembers { // Create a class initializer to call onClassLoaded(). Loading Loading @@ -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, name: String, descriptor: String, Loading @@ -138,7 +244,85 @@ class ImplGeneratingAdapter( 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, name: String, descriptor: String, Loading Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt +1 −2 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import com.android.hoststubgen.filters.SanitizationFilter 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.JdkPatchVisitor import com.android.hoststubgen.visitors.PackageRedirectRemapper Loading Loading @@ -70,7 +69,7 @@ class HostStubGenClassProcessor( // Remapping should happen at the end. outVisitor = ClassRemapper(outVisitor, remapper) val visitorOptions = BaseAdapter.Options( val visitorOptions = ImplGeneratingAdapter.Options( errors = errors, deleteClassFinals = options.deleteFinals.get, deleteMethodFinals = options.deleteFinals.get, Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt +5 −1 Original line number Diff line number Diff line Loading @@ -349,8 +349,12 @@ enum class Visibility { } } fun isEnum(access: Int): Boolean { return (access and Opcodes.ACC_ENUM) != 0 } fun ClassNode.isEnum(): Boolean { return (this.access and Opcodes.ACC_ENUM) != 0 return isEnum(this.access) } fun ClassNode.isAnnotation(): Boolean { Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.ktdeleted 100644 → 0 +0 −248 Original line number 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? }
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +191 −7 Original line number Diff line number Diff line Loading @@ -15,12 +15,16 @@ */ 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_NAME import com.android.hoststubgen.asm.CTOR_NAME import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.UnifiedVisitor import com.android.hoststubgen.asm.adjustStackForConstructorRedirection 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.toJvmClassName import com.android.hoststubgen.asm.writeByteCodeToPushArguments Loading @@ -29,11 +33,14 @@ import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.FilterPolicyWithReason import com.android.hoststubgen.filters.OutputFilter 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.HostStubGenProcessedAsThrow import com.android.hoststubgen.hosthelper.HostTestUtils 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 import org.objectweb.asm.Opcodes.INVOKEINTERFACE Loading @@ -42,18 +49,56 @@ import org.objectweb.asm.Opcodes.INVOKESTATIC import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.Type const val OPCODE_VERSION = Opcodes.ASM9 /** * An adapter that generates the "impl" class file from an input class file. */ class ImplGeneratingAdapter( classes: ClassNodes, val classes: ClassNodes, nextVisitor: ClassVisitor, filter: OutputFilter, options: Options, ) : BaseAdapter(classes, nextVisitor, filter, options) { val filter: OutputFilter, val options: 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 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( version: Int, origAccess: Int, Loading @@ -62,9 +107,21 @@ class ImplGeneratingAdapter( superName: String?, interfaces: Array<String> ) { val access = modifyClassAccess(origAccess) val access = maybeRemoveFinalFromClass(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)", 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) // classLoadHookMethod is non-null, then we need to inject code to call it Loading @@ -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() { writeRawMembers { // Create a class initializer to call onClassLoaded(). Loading Loading @@ -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, name: String, descriptor: String, Loading @@ -138,7 +244,85 @@ class ImplGeneratingAdapter( 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, name: String, descriptor: String, Loading