Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt +1 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,7 @@ class HostStubGenClassProcessor( deleteClassFinals = options.deleteFinals.get, deleteMethodFinals = options.deleteFinals.get, throwExceptionType = options.throwExceptionType.get, annotationsToMakeVisible = options.allAnnotationSet ) val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose)) Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt +7 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.hoststubgen import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.utils.ArgIterator import com.android.hoststubgen.utils.BaseOptions import com.android.hoststubgen.utils.ClassDescriptorSet import com.android.hoststubgen.utils.FileOrResource import com.android.hoststubgen.utils.SetOnce Loading Loading @@ -68,6 +69,8 @@ open class HostStubGenClassProcessorOptions( val enableClassChecker: SetOnce<Boolean> = SetOnce(false), val enablePreTrace: SetOnce<Boolean> = SetOnce(false), val enablePostTrace: SetOnce<Boolean> = SetOnce(false), val allAnnotationSet: ClassDescriptorSet = ClassDescriptorSet() ) : BaseOptions() { private val allAnnotations = mutableSetOf<String>() Loading @@ -83,7 +86,10 @@ open class HostStubGenClassProcessorOptions( // Define some shorthands... fun nextArg(): String = args.nextArgRequired(option) fun MutableSet<String>.addUniqueAnnotationArg(): String = nextArg().also { this += ensureUniqueAnnotation(it) } nextArg().also { this += ensureUniqueAnnotation(it) allAnnotationSet.addType(it) } when (option) { "--policy-override-file" -> policyOverrideFiles.add(FileOrResource(nextArg())) Loading ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassDescriptorSet.kt 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.utils import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toJvmClassName /** * Represents a set of "class descriptors" for faster lookup, which supports all of the * "com/android/ravenwood/Xxx", "Lcom/android/ravenwood/Xxx;" and * "com.android.ravenwood.Xxx" formats. */ class ClassDescriptorSet { private val matches = mutableSetOf<String>() fun addType(typeName: String) { val internalName = typeName.toJvmClassName() matches.add(internalName) matches.add("L$internalName;") matches.add(typeName.toHumanReadableClassName()) } fun contains(descriptor: String?): Boolean { if (descriptor == null) { return false } return matches.contains(descriptor) } } No newline at end of file ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +78 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrowButSupported import com.android.hoststubgen.hosthelper.HostTestUtils import com.android.hoststubgen.log import com.android.hoststubgen.utils.ClassDescriptorSet import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassVisitor import org.objectweb.asm.FieldVisitor import org.objectweb.asm.MethodVisitor Loading @@ -49,6 +51,7 @@ import org.objectweb.asm.Opcodes.INVOKESPECIAL import org.objectweb.asm.Opcodes.INVOKESTATIC import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.Type import java.lang.annotation.RetentionPolicy const val OPCODE_VERSION = Opcodes.ASM9 Loading Loading @@ -77,6 +80,9 @@ class ImplGeneratingAdapter( // val deleteFieldFinals: Boolean, val throwExceptionType: String, // We make all annotations in this set "runtime-visible". val annotationsToMakeVisible: ClassDescriptorSet, ) private lateinit var currentPackageName: String Loading Loading @@ -153,6 +159,32 @@ class ImplGeneratingAdapter( super.visitEnd() } /** * Tweak annotation "visibility" aka "retention policy". */ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { // If it's a "known" annotation -- i.e. any Ravenwood annotations -- we do the following: // 1. For the annotation type itself, change the retention policy to "RUNTIME". // 2. Make the annotation "runtime-visible" across the whole jar. // For 1. if (options.annotationsToMakeVisible.contains(currentClassName)) { // This current type is a known annotation. We change the retention policy. if ("Ljava/lang/annotation/Retention;" == descriptor) { // If it is, we return our custom AnnotationVisitor to modify its value. // We pass the original visitor from the superclass to maintain the chain. return RetentionPolicyAnnotationVisitor( super.visitAnnotation(descriptor, visible)) } } // For 2. If the annotation we're processing now is "known" (i.e. RavenwoodKeep, etc), // force it to be "runtime-visible" aka RUNTIME. return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } var skipMemberModificationNestCount = 0 /** Loading Loading @@ -236,7 +268,7 @@ class ImplGeneratingAdapter( UnifiedVisitor.on(ret) .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true) return ret return ForceFieldAnnotationVisibilityVisitor(ret) } } Loading Loading @@ -328,7 +360,7 @@ class ImplGeneratingAdapter( .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true) } return ret return ForceMethodAnnotationVisibilityVisitor(ret) } } Loading Loading @@ -655,4 +687,48 @@ class ImplGeneratingAdapter( } } } /** * An AnnotationVisitor that specifically targets the `value` of a @Retention * annotation and forces it to be `RUNTIME`. */ class RetentionPolicyAnnotationVisitor( next: AnnotationVisitor?, ) : AnnotationVisitor(OPCODE_VERSION, next) { override fun visitEnum(name: String?, descriptor: String?, value: String?) { // We only care about the "value" property of the @Retention annotation. if ("value" == name && "Ljava/lang/annotation/RetentionPolicy;" == descriptor) { super.visitEnum(name, descriptor, RetentionPolicy.RUNTIME.name) } else { // For any other enum property, delegate to the default behavior. super.visitEnum(name, descriptor, value) } } } /** * Force a field's annotation to be runtime-visible if it's "known". */ inner class ForceFieldAnnotationVisibilityVisitor( next: FieldVisitor?, ) : FieldVisitor(OPCODE_VERSION, next) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } } /** * Force a method's annotation to be runtime-visible if it's "known". */ inner class ForceMethodAnnotationVisibilityVisitor( next: MethodVisitor?, ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } } } ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt +32 −92 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt +1 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,7 @@ class HostStubGenClassProcessor( deleteClassFinals = options.deleteFinals.get, deleteMethodFinals = options.deleteFinals.get, throwExceptionType = options.throwExceptionType.get, annotationsToMakeVisible = options.allAnnotationSet ) val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose)) Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessorOptions.kt +7 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.hoststubgen import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.utils.ArgIterator import com.android.hoststubgen.utils.BaseOptions import com.android.hoststubgen.utils.ClassDescriptorSet import com.android.hoststubgen.utils.FileOrResource import com.android.hoststubgen.utils.SetOnce Loading Loading @@ -68,6 +69,8 @@ open class HostStubGenClassProcessorOptions( val enableClassChecker: SetOnce<Boolean> = SetOnce(false), val enablePreTrace: SetOnce<Boolean> = SetOnce(false), val enablePostTrace: SetOnce<Boolean> = SetOnce(false), val allAnnotationSet: ClassDescriptorSet = ClassDescriptorSet() ) : BaseOptions() { private val allAnnotations = mutableSetOf<String>() Loading @@ -83,7 +86,10 @@ open class HostStubGenClassProcessorOptions( // Define some shorthands... fun nextArg(): String = args.nextArgRequired(option) fun MutableSet<String>.addUniqueAnnotationArg(): String = nextArg().also { this += ensureUniqueAnnotation(it) } nextArg().also { this += ensureUniqueAnnotation(it) allAnnotationSet.addType(it) } when (option) { "--policy-override-file" -> policyOverrideFiles.add(FileOrResource(nextArg())) Loading
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/ClassDescriptorSet.kt 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.utils import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toJvmClassName /** * Represents a set of "class descriptors" for faster lookup, which supports all of the * "com/android/ravenwood/Xxx", "Lcom/android/ravenwood/Xxx;" and * "com.android.ravenwood.Xxx" formats. */ class ClassDescriptorSet { private val matches = mutableSetOf<String>() fun addType(typeName: String) { val internalName = typeName.toJvmClassName() matches.add(internalName) matches.add("L$internalName;") matches.add(typeName.toHumanReadableClassName()) } fun contains(descriptor: String?): Boolean { if (descriptor == null) { return false } return matches.contains(descriptor) } } No newline at end of file
ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +78 −2 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrowButSupported import com.android.hoststubgen.hosthelper.HostTestUtils import com.android.hoststubgen.log import com.android.hoststubgen.utils.ClassDescriptorSet import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassVisitor import org.objectweb.asm.FieldVisitor import org.objectweb.asm.MethodVisitor Loading @@ -49,6 +51,7 @@ import org.objectweb.asm.Opcodes.INVOKESPECIAL import org.objectweb.asm.Opcodes.INVOKESTATIC import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.Type import java.lang.annotation.RetentionPolicy const val OPCODE_VERSION = Opcodes.ASM9 Loading Loading @@ -77,6 +80,9 @@ class ImplGeneratingAdapter( // val deleteFieldFinals: Boolean, val throwExceptionType: String, // We make all annotations in this set "runtime-visible". val annotationsToMakeVisible: ClassDescriptorSet, ) private lateinit var currentPackageName: String Loading Loading @@ -153,6 +159,32 @@ class ImplGeneratingAdapter( super.visitEnd() } /** * Tweak annotation "visibility" aka "retention policy". */ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { // If it's a "known" annotation -- i.e. any Ravenwood annotations -- we do the following: // 1. For the annotation type itself, change the retention policy to "RUNTIME". // 2. Make the annotation "runtime-visible" across the whole jar. // For 1. if (options.annotationsToMakeVisible.contains(currentClassName)) { // This current type is a known annotation. We change the retention policy. if ("Ljava/lang/annotation/Retention;" == descriptor) { // If it is, we return our custom AnnotationVisitor to modify its value. // We pass the original visitor from the superclass to maintain the chain. return RetentionPolicyAnnotationVisitor( super.visitAnnotation(descriptor, visible)) } } // For 2. If the annotation we're processing now is "known" (i.e. RavenwoodKeep, etc), // force it to be "runtime-visible" aka RUNTIME. return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } var skipMemberModificationNestCount = 0 /** Loading Loading @@ -236,7 +268,7 @@ class ImplGeneratingAdapter( UnifiedVisitor.on(ret) .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true) return ret return ForceFieldAnnotationVisibilityVisitor(ret) } } Loading Loading @@ -328,7 +360,7 @@ class ImplGeneratingAdapter( .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true) } return ret return ForceMethodAnnotationVisibilityVisitor(ret) } } Loading Loading @@ -655,4 +687,48 @@ class ImplGeneratingAdapter( } } } /** * An AnnotationVisitor that specifically targets the `value` of a @Retention * annotation and forces it to be `RUNTIME`. */ class RetentionPolicyAnnotationVisitor( next: AnnotationVisitor?, ) : AnnotationVisitor(OPCODE_VERSION, next) { override fun visitEnum(name: String?, descriptor: String?, value: String?) { // We only care about the "value" property of the @Retention annotation. if ("value" == name && "Ljava/lang/annotation/RetentionPolicy;" == descriptor) { super.visitEnum(name, descriptor, RetentionPolicy.RUNTIME.name) } else { // For any other enum property, delegate to the default behavior. super.visitEnum(name, descriptor, value) } } } /** * Force a field's annotation to be runtime-visible if it's "known". */ inner class ForceFieldAnnotationVisibilityVisitor( next: FieldVisitor?, ) : FieldVisitor(OPCODE_VERSION, next) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } } /** * Force a method's annotation to be runtime-visible if it's "known". */ inner class ForceMethodAnnotationVisibilityVisitor( next: MethodVisitor?, ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { return super.visitAnnotation(descriptor, visible || options.annotationsToMakeVisible.contains(descriptor)) } } }
ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt +32 −92 File changed.Preview size limit exceeded, changes collapsed. Show changes