diff options
16 files changed, 327 insertions, 281 deletions
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index ee3a3c27ca77..319efe04da8c 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -60,7 +60,9 @@ public final class RavenwoodEnvironment { public static void ensureRavenwoodInitialized() { } - private static native void ensureRavenwoodInitialized$ravenwood(); + private static void ensureRavenwoodInitialized$ravenwood() { + nativeEnsureRavenwoodInitialized(); + } /** * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment. @@ -92,7 +94,9 @@ public final class RavenwoodEnvironment { throw notSupportedOnDevice(); } - private native <T> T fromAddress$ravenwood(long address); + private <T> T fromAddress$ravenwood(long address) { + return nativeFromAddress(address); + } /** * See {@link Workaround}. It's only usable on Ravenwood. @@ -114,7 +118,14 @@ public final class RavenwoodEnvironment { throw notSupportedOnDevice(); } - private native String getRavenwoodRuntimePath$ravenwood(); + private String getRavenwoodRuntimePath$ravenwood() { + return nativeGetRavenwoodRuntimePath(); + } + + // Private native methods that are actually substituted on Ravenwood + private native <T> T nativeFromAddress(long address); + private native String nativeGetRavenwoodRuntimePath(); + private static native void nativeEnsureRavenwoodInitialized(); /** * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java index f894b0e69a90..58f6bbb5baf5 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/RavenwoodEnvironment_host.java @@ -36,7 +36,7 @@ public class RavenwoodEnvironment_host { /** * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}. */ - public static void ensureRavenwoodInitialized() { + public static void nativeEnsureRavenwoodInitialized() { // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook. @@ -63,14 +63,14 @@ public class RavenwoodEnvironment_host { /** * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}. */ - public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) { + public static String nativeGetRavenwoodRuntimePath(RavenwoodEnvironment env) { return RavenwoodCommonUtils.getRavenwoodRuntimePath(); } /** * Called from {@link RavenwoodEnvironment#fromAddress(long)}. */ - public static <T> T fromAddress(RavenwoodEnvironment env, long address) { + public static <T> T nativeFromAddress(RavenwoodEnvironment env, long address) { return JvmWorkaround.getInstance().fromAddress(address); } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index b5a0d0fde901..0f38fe7d5068 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -24,6 +24,7 @@ import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.FilterPolicy import com.android.hoststubgen.filters.FilterRemapper import com.android.hoststubgen.filters.ImplicitOutputFilter +import com.android.hoststubgen.filters.NativeFilter import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.createFilterFromTextPolicyFile import com.android.hoststubgen.filters.printAsTextPolicy @@ -132,6 +133,9 @@ class HostStubGen(val options: HostStubGenOptions) { // The first filter is for the default policy from the command line options. var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options") + // Next, we build a filter that preserves all native methods by default + filter = NativeFilter(allClasses, filter) + // Next, we need a filter that resolves "class-wide" policies. // This is used when a member (methods, fields, nested classes) don't get any polices // from upper filters. e.g. when a method has no annotations, then this filter will apply diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index 7dd4fdd078a2..7197e0e79861 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -327,6 +327,10 @@ fun MethodNode.isPublic(): Boolean { return (this.access and Opcodes.ACC_PUBLIC) != 0 } +fun MethodNode.isNative(): Boolean { + return (this.access and Opcodes.ACC_NATIVE) != 0 +} + fun MethodNode.isSpecial(): Boolean { return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt index 353b7bcfd8ac..8ee3a946a21c 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt @@ -16,6 +16,7 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.isNative /** * This is used as the second last fallback filter. This filter propagates the class-wide policy @@ -87,7 +88,16 @@ class ClassWidePolicyPropagatingFilter( methodName: String, descriptor: String ): FilterPolicyWithReason { - return getClassWidePolicy(className, resolve = true) - ?: super.getPolicyForMethod(className, methodName, descriptor) + return outermostFilter.getNativeSubstitutionClass(className)?.let { + // First check native substitution + classes.findMethod(className, methodName, descriptor)?.let { mn -> + if (mn.isNative()) { + FilterPolicy.NativeSubstitute.withReason("class-wide in $className") + } else { + null + } + } + } ?: getClassWidePolicy(className, resolve = true) + ?: super.getPolicyForMethod(className, methodName, descriptor) } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt index 27d98b81d52e..be3c59c80152 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt @@ -25,7 +25,10 @@ import com.android.hoststubgen.HostStubGenInternalException * * @param policy the policy. Cannot be a "substitute" policy. */ -class ConstantFilter(policy: FilterPolicy, private val reason: String) : OutputFilter() { +class ConstantFilter( + policy: FilterPolicy, + private val reason: String +) : OutputFilter() { private val classPolicy: FilterPolicy private val fieldPolicy: FilterPolicy diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt index 3e0bb57d2abd..ab03874cf43d 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt @@ -33,6 +33,11 @@ enum class FilterPolicy { Substitute, /** + * Only usable with methods. Replace a native method with a "substitution" method, + */ + NativeSubstitute, + + /** * Only usable with methods. The item will be kept in the impl jar file, but when called, * it'll throw. */ @@ -98,7 +103,15 @@ enum class FilterPolicy { get() { return when (this) { // TODO: handle native method with no substitution as being unsupported - Keep, KeepClass, Substitute -> true + Keep, KeepClass, Substitute, NativeSubstitute -> true + else -> false + } + } + + val isMethodRewriteBody: Boolean + get() { + return when (this) { + NativeSubstitute, Throw, Ignore -> true else -> false } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt new file mode 100644 index 000000000000..bd719310719b --- /dev/null +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt @@ -0,0 +1,40 @@ +/* + * 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.filters + +import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.isNative + +class NativeFilter( + private val classes: ClassNodes, + fallback: OutputFilter +) : DelegatingFilter(fallback) { + override fun getPolicyForMethod( + className: String, + methodName: String, + descriptor: String, + ): FilterPolicyWithReason { + return classes.findMethod(className, methodName, descriptor)?.let { mn -> + // For native methods that weren't handled by outer filters, + // we keep it so that native method registration will not crash. + if (mn.isNative()) { + FilterPolicy.Keep.withReason("native-preserve") + } else { + null + } + } ?: super.getPolicyForMethod(className, methodName, descriptor) + } +}
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 0313c7f9c13e..41ba9286ef1e 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt @@ -213,7 +213,7 @@ abstract class BaseAdapter( // 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) + newAccess = updateAccessFlags(newAccess, name, descriptor, policy.policy) val ret = visitMethodInner( access, newName, descriptor, signature, exceptions, policy, @@ -234,6 +234,7 @@ abstract class BaseAdapter( access: Int, name: String, descriptor: String, + policy: FilterPolicy, ): Int { return access } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt index 8250412b3717..55d0c0e555f1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt @@ -20,38 +20,23 @@ import org.objectweb.asm.Attribute import org.objectweb.asm.Handle import org.objectweb.asm.Label import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes import org.objectweb.asm.TypePath /** - * A method visitor that removes everything from method body. + * A method visitor that creates or replaces a method body. * - * To inject a method body, override [visitCode] and create the opcodes there. + * Override [emitNewCode] to build the method body. */ abstract class BodyReplacingMethodVisitor( - access: Int, - name: String, - descriptor: String, - signature: String?, - exceptions: Array<String>?, - next: MethodVisitor?, + private val createBody: Boolean, + next: MethodVisitor? ) : MethodVisitor(OPCODE_VERSION, next) { - val isVoid: Boolean - val isStatic: Boolean - - init { - isVoid = descriptor.endsWith(")V") - isStatic = access and Opcodes.ACC_STATIC != 0 - } // Following methods are for things that we need to keep. // Since they're all calling the super method, we can just remove them, but we keep them // just to clarify what we're keeping. - final override fun visitParameter( - name: String?, - access: Int - ) { + final override fun visitParameter(name: String?, access: Int) { super.visitParameter(name, access) } @@ -59,10 +44,7 @@ abstract class BodyReplacingMethodVisitor( return super.visitAnnotationDefault() } - final override fun visitAnnotation( - descriptor: String?, - visible: Boolean - ): AnnotationVisitor? { + final override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { return super.visitAnnotation(descriptor, visible) } @@ -75,17 +57,14 @@ abstract class BodyReplacingMethodVisitor( return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible) } - final override fun visitAnnotableParameterCount( - parameterCount: Int, - visible: Boolean - ) { + final override fun visitAnnotableParameterCount(parameterCount: Int, visible: Boolean) { super.visitAnnotableParameterCount(parameterCount, visible) } final override fun visitParameterAnnotation( - parameter: Int, - descriptor: String?, - visible: Boolean + parameter: Int, + descriptor: String?, + visible: Boolean ): AnnotationVisitor? { return super.visitParameterAnnotation(parameter, descriptor, visible) } @@ -94,10 +73,6 @@ abstract class BodyReplacingMethodVisitor( super.visitAttribute(attribute) } - override fun visitEnd() { - super.visitEnd() - } - /** * Control when to emit the code. We use this to ignore all visitXxx method calls caused by * the original method, so we'll remove all the original code. @@ -108,9 +83,18 @@ abstract class BodyReplacingMethodVisitor( * (See also https://asm.ow2.io/asm4-guide.pdf section 3.2.1 about the MethovVisitor * call order.) */ - var emitCode = false + private var emitCode = false + + /** + * This value will be set as true when [visitCode] is called. In [visitEnd], if this value + * is still false, this means that the original method does not have a body. + * + * We want to forcefully inject a method body in [visitEnd] if [createBody] is true. + */ + private var visitedCode = false final override fun visitCode() { + visitedCode = true super.visitCode() try { @@ -122,15 +106,19 @@ abstract class BodyReplacingMethodVisitor( } } + final override fun visitEnd() { + if (!visitedCode && createBody) { + visitCode() + } + super.visitEnd() + } + /** * Subclass must implement it and emit code, and call [visitMaxs] at the end. */ abstract fun emitNewCode() - final override fun visitMaxs( - maxStack: Int, - maxLocals: Int - ) { + final override fun visitMaxs(maxStack: Int, maxLocals: Int) { if (emitCode) { super.visitMaxs(maxStack, maxLocals) } @@ -140,11 +128,11 @@ abstract class BodyReplacingMethodVisitor( // emit any of them, so they are all no-op. final override fun visitFrame( - type: Int, - numLocal: Int, - local: Array<out Any>?, - numStack: Int, - stack: Array<out Any>? + type: Int, + numLocal: Int, + local: Array<out Any>?, + numStack: Int, + stack: Array<out Any>? ) { if (emitCode) { super.visitFrame(type, numLocal, local, numStack, stack) @@ -157,38 +145,29 @@ abstract class BodyReplacingMethodVisitor( } } - final override fun visitIntInsn( - opcode: Int, - operand: Int - ) { + final override fun visitIntInsn(opcode: Int, operand: Int) { if (emitCode) { super.visitIntInsn(opcode, operand) } } - final override fun visitVarInsn( - opcode: Int, - varIndex: Int - ) { + final override fun visitVarInsn(opcode: Int, varIndex: Int) { if (emitCode) { super.visitVarInsn(opcode, varIndex) } } - final override fun visitTypeInsn( - opcode: Int, - type: String? - ) { + final override fun visitTypeInsn(opcode: Int, type: String?) { if (emitCode) { super.visitTypeInsn(opcode, type) } } final override fun visitFieldInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String? + opcode: Int, + owner: String?, + name: String?, + descriptor: String? ) { if (emitCode) { super.visitFieldInsn(opcode, owner, name, descriptor) @@ -196,11 +175,11 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean ) { if (emitCode) { super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) @@ -208,21 +187,20 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitInvokeDynamicInsn( - name: String?, - descriptor: String?, - bootstrapMethodHandle: Handle?, - vararg bootstrapMethodArguments: Any? + name: String?, + descriptor: String?, + bootstrapMethodHandle: Handle?, + vararg bootstrapMethodArguments: Any? ) { if (emitCode) { - super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, - *bootstrapMethodArguments) + super.visitInvokeDynamicInsn( + name, descriptor, bootstrapMethodHandle, + *bootstrapMethodArguments + ) } } - final override fun visitJumpInsn( - opcode: Int, - label: Label? - ) { + final override fun visitJumpInsn(opcode: Int, label: Label?) { if (emitCode) { super.visitJumpInsn(opcode, label) } @@ -240,20 +218,17 @@ abstract class BodyReplacingMethodVisitor( } } - final override fun visitIincInsn( - varIndex: Int, - increment: Int - ) { + final override fun visitIincInsn(varIndex: Int, increment: Int) { if (emitCode) { super.visitIincInsn(varIndex, increment) } } final override fun visitTableSwitchInsn( - min: Int, - max: Int, - dflt: Label?, - vararg labels: Label? + min: Int, + max: Int, + dflt: Label?, + vararg labels: Label? ) { if (emitCode) { super.visitTableSwitchInsn(min, max, dflt, *labels) @@ -261,29 +236,26 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitLookupSwitchInsn( - dflt: Label?, - keys: IntArray?, - labels: Array<out Label>? + dflt: Label?, + keys: IntArray?, + labels: Array<out Label>? ) { if (emitCode) { super.visitLookupSwitchInsn(dflt, keys, labels) } } - final override fun visitMultiANewArrayInsn( - descriptor: String?, - numDimensions: Int - ) { + final override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) { if (emitCode) { super.visitMultiANewArrayInsn(descriptor, numDimensions) } } final override fun visitInsnAnnotation( - typeRef: Int, - typePath: TypePath?, - descriptor: String?, - visible: Boolean + typeRef: Int, + typePath: TypePath?, + descriptor: String?, + visible: Boolean ): AnnotationVisitor? { if (emitCode) { return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible) @@ -292,10 +264,10 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitTryCatchBlock( - start: Label?, - end: Label?, - handler: Label?, - type: String? + start: Label?, + end: Label?, + handler: Label?, + type: String? ) { if (emitCode) { super.visitTryCatchBlock(start, end, handler, type) @@ -303,10 +275,10 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitTryCatchAnnotation( - typeRef: Int, - typePath: TypePath?, - descriptor: String?, - visible: Boolean + typeRef: Int, + typePath: TypePath?, + descriptor: String?, + visible: Boolean ): AnnotationVisitor? { if (emitCode) { return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible) @@ -315,12 +287,12 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitLocalVariable( - name: String?, - descriptor: String?, - signature: String?, - start: Label?, - end: Label?, - index: Int + name: String?, + descriptor: String?, + signature: String?, + start: Label?, + end: Label?, + index: Int ) { if (emitCode) { super.visitLocalVariable(name, descriptor, signature, start, end, index) @@ -328,25 +300,23 @@ abstract class BodyReplacingMethodVisitor( } final override fun visitLocalVariableAnnotation( - typeRef: Int, - typePath: TypePath?, - start: Array<out Label>?, - end: Array<out Label>?, - index: IntArray?, - descriptor: String?, - visible: Boolean + typeRef: Int, + typePath: TypePath?, + start: Array<out Label>?, + end: Array<out Label>?, + index: IntArray?, + descriptor: String?, + visible: Boolean ): AnnotationVisitor? { if (emitCode) { return super.visitLocalVariableAnnotation( - typeRef, typePath, start, end, index, descriptor, visible) + typeRef, typePath, start, end, index, descriptor, visible + ) } return null } - final override fun visitLineNumber( - line: Int, - start: Label? - ) { + final override fun visitLineNumber(line: Int, start: Label?) { if (emitCode) { super.visitLineNumber(line, start) } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 398104d36979..057d653d7c45 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -41,10 +41,10 @@ import org.objectweb.asm.Type * An adapter that generates the "impl" class file from an input class file. */ class ImplGeneratingAdapter( - classes: ClassNodes, - nextVisitor: ClassVisitor, - filter: OutputFilter, - options: Options, + classes: ClassNodes, + nextVisitor: ClassVisitor, + filter: OutputFilter, + options: Options, ) : BaseAdapter(classes, nextVisitor, filter, options) { private var classLoadHooks: List<String> = emptyList() @@ -102,14 +102,14 @@ class ImplGeneratingAdapter( private fun writeClassLoadHookCalls(mv: MethodVisitor) { classLoadHooks.forEach { classLoadHook -> // First argument: the class type. - mv.visitLdcInsn(Type.getType("L" + currentClassName + ";")) + mv.visitLdcInsn(Type.getType("L$currentClassName;")) // Second argument: method name mv.visitLdcInsn(classLoadHook) // Call HostTestUtils.onClassLoaded(). mv.visitMethodInsn( - Opcodes.INVOKESTATIC, + INVOKESTATIC, HostTestUtils.CLASS_INTERNAL_NAME, "onClassLoaded", "(Ljava/lang/Class;Ljava/lang/String;)V", @@ -119,56 +119,49 @@ class ImplGeneratingAdapter( } override fun updateAccessFlags( - access: Int, - name: String, - descriptor: String, + access: Int, + name: String, + descriptor: String, + policy: FilterPolicy, ): Int { - if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) { + if (policy.isMethodRewriteBody) { + // If we are rewriting the entire method body, we need + // to convert native methods to non-native return access and Opcodes.ACC_NATIVE.inv() } return access } override fun visitMethodInner( - access: Int, - name: String, - descriptor: String, - signature: String?, - exceptions: Array<String>?, - policy: FilterPolicyWithReason, - substituted: Boolean, - superVisitor: MethodVisitor?, + access: Int, + name: String, + descriptor: String, + signature: String?, + exceptions: Array<String>?, + policy: FilterPolicyWithReason, + substituted: Boolean, + superVisitor: MethodVisitor?, ): MethodVisitor? { - // Inject method log, if needed. var innerVisitor = superVisitor // If method logging is enabled, inject call to the logging method. val methodCallHooks = filter.getMethodCallHooks(currentClassName, name, descriptor) if (methodCallHooks.isNotEmpty()) { innerVisitor = MethodCallHookInjectingAdapter( - access, name, descriptor, - signature, - exceptions, - innerVisitor, methodCallHooks, - ) + innerVisitor, + ) } // If this class already has a class initializer and a class load hook is needed, then // we inject code. if (classLoadHooks.isNotEmpty() && name == CLASS_INITIALIZER_NAME && - descriptor == CLASS_INITIALIZER_DESC) { - innerVisitor = ClassLoadHookInjectingMethodAdapter( - access, - name, - descriptor, - signature, - exceptions, - innerVisitor, - ) + descriptor == CLASS_INITIALIZER_DESC + ) { + innerVisitor = ClassLoadHookInjectingMethodAdapter(innerVisitor) } fun MethodVisitor.withAnnotation(descriptor: String): MethodVisitor { @@ -177,34 +170,31 @@ class ImplGeneratingAdapter( } log.withIndent { - var willThrow = false - if (policy.policy == FilterPolicy.Throw) { - log.v("Making method throw...") - willThrow = true - innerVisitor = ThrowingMethodAdapter( - access, name, descriptor, signature, exceptions, innerVisitor) - .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR) - } - if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) { - log.v("Rewriting native method...") - return NativeSubstitutingMethodAdapter( - access, name, descriptor, signature, exceptions, innerVisitor) - .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR) - } - if (willThrow) { - return innerVisitor + // When we encounter native methods, we want to forcefully + // inject a method body. Also see [updateAccessFlags]. + val forceCreateBody = (access and Opcodes.ACC_NATIVE) != 0 + when (policy.policy) { + FilterPolicy.Throw -> { + log.v("Making method throw...") + return ThrowingMethodAdapter(forceCreateBody, innerVisitor) + .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR) + } + FilterPolicy.Ignore -> { + log.v("Making method ignored...") + return IgnoreMethodAdapter(descriptor, forceCreateBody, innerVisitor) + .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR) + } + FilterPolicy.NativeSubstitute -> { + log.v("Rewriting native method...") + return NativeSubstitutingMethodAdapter(access, name, descriptor, innerVisitor) + .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR) + } + else -> {} } + } - if (policy.policy == FilterPolicy.Ignore) { - log.v("Making method ignored...") - return IgnoreMethodAdapter( - access, name, descriptor, signature, exceptions, innerVisitor) - .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR) - } - if (filter.hasAnyMethodCallReplace()) { - innerVisitor = MethodCallReplacingAdapter( - access, name, descriptor, signature, exceptions, innerVisitor) - } + if (filter.hasAnyMethodCallReplace()) { + innerVisitor = MethodCallReplacingAdapter(name, innerVisitor) } if (substituted) { innerVisitor?.withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR) @@ -218,27 +208,27 @@ class ImplGeneratingAdapter( * call. */ private inner class ThrowingMethodAdapter( - access: Int, - val name: String, - descriptor: String, - signature: String?, - exceptions: Array<String>?, - next: MethodVisitor? - ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) { + createBody: Boolean, + next: MethodVisitor? + ) : BodyReplacingMethodVisitor(createBody, next) { override fun emitNewCode() { - visitMethodInsn(Opcodes.INVOKESTATIC, - HostTestUtils.CLASS_INTERNAL_NAME, - "onThrowMethodCalled", - "()V", - false) + visitMethodInsn( + INVOKESTATIC, + HostTestUtils.CLASS_INTERNAL_NAME, + "onThrowMethodCalled", + "()V", + false + ) // We still need a RETURN opcode for the return type. // For now, let's just inject a `throw`. visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException") visitInsn(Opcodes.DUP) visitLdcInsn("Unreachable") - visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", - "<init>", "(Ljava/lang/String;)V", false) + visitMethodInsn( + Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", + "<init>", "(Ljava/lang/String;)V", false + ) visitInsn(Opcodes.ATHROW) // visitMaxs(3, if (isStatic) 0 else 1) @@ -250,13 +240,10 @@ class ImplGeneratingAdapter( * A method adapter that replaces the method body with a no-op return. */ private inner class IgnoreMethodAdapter( - access: Int, - name: String, - val descriptor: String, - signature: String?, - exceptions: Array<String>?, - next: MethodVisitor? - ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) { + val descriptor: String, + createBody: Boolean, + next: MethodVisitor? + ) : BodyReplacingMethodVisitor(createBody, next) { override fun emitNewCode() { when (Type.getReturnType(descriptor)) { Type.VOID_TYPE -> visitInsn(Opcodes.RETURN) @@ -287,30 +274,24 @@ class ImplGeneratingAdapter( } /** - * A method adapter that replaces a native method call with a call to the "native substitution" - * class. + * A method adapter that rewrite a native method body with a + * call to a method in the "native substitution" class. */ private inner class NativeSubstitutingMethodAdapter( - val access: Int, - private val name: String, - private val descriptor: String, - signature: String?, - exceptions: Array<String>?, - next: MethodVisitor? - ) : MethodVisitor(OPCODE_VERSION, next) { - override fun visitCode() { - throw RuntimeException("NativeSubstitutingMethodVisitor should be called on " + - " native method, where visitCode() shouldn't be called.") - } + access: Int, + private val name: String, + private val descriptor: String, + next: MethodVisitor? + ) : BodyReplacingMethodVisitor(true, next) { - override fun visitEnd() { - super.visitCode() + private val isStatic = (access and Opcodes.ACC_STATIC) != 0 + override fun emitNewCode() { var targetDescriptor = descriptor var argOffset = 0 // For non-static native method, we need to tweak it a bit. - if ((access and Opcodes.ACC_STATIC) == 0) { + if (!isStatic) { // Push `this` as the first argument. this.visitVarInsn(Opcodes.ALOAD, 0) @@ -327,16 +308,17 @@ class ImplGeneratingAdapter( writeByteCodeToPushArguments(descriptor, this, argOffset) - visitMethodInsn(Opcodes.INVOKESTATIC, - nativeSubstitutionClass, - name, - targetDescriptor, - false) + visitMethodInsn( + INVOKESTATIC, + nativeSubstitutionClass, + name, + targetDescriptor, + false + ) writeByteCodeToReturn(descriptor, this) visitMaxs(99, 0) // We let ASM figure them out. - super.visitEnd() } } @@ -347,25 +329,22 @@ class ImplGeneratingAdapter( * `this(...)`. The logging code will be injected *before* such calls. */ private inner class MethodCallHookInjectingAdapter( - access: Int, - val name: String, - val descriptor: String, - signature: String?, - exceptions: Array<String>?, - next: MethodVisitor?, - val hooks: List<String>, + val name: String, + val descriptor: String, + val hooks: List<String>, + next: MethodVisitor?, ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitCode() { super.visitCode() hooks.forEach { hook -> - mv.visitLdcInsn(Type.getType("L" + currentClassName + ";")) + mv.visitLdcInsn(Type.getType("L$currentClassName;")) visitLdcInsn(name) visitLdcInsn(descriptor) visitLdcInsn(hook) visitMethodInsn( - Opcodes.INVOKESTATIC, + INVOKESTATIC, HostTestUtils.CLASS_INTERNAL_NAME, "callMethodCallHook", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", @@ -379,11 +358,6 @@ class ImplGeneratingAdapter( * Inject a class load hook call. */ private inner class ClassLoadHookInjectingMethodAdapter( - access: Int, - val name: String, - val descriptor: String, - signature: String?, - exceptions: Array<String>?, next: MethodVisitor? ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitCode() { @@ -394,11 +368,7 @@ class ImplGeneratingAdapter( } private inner class MethodCallReplacingAdapter( - access: Int, val callerMethodName: String, - val descriptor: String, - signature: String?, - exceptions: Array<String>?, next: MethodVisitor?, ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitMethodInsn( @@ -417,7 +387,8 @@ class ImplGeneratingAdapter( } } val to = filter.getMethodCallReplaceTo( - currentClassName, callerMethodName, owner!!, name!!, descriptor!!) + currentClassName, callerMethodName, owner!!, name!!, descriptor!! + ) if (to == null // Don't replace if the target is the callsite. diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 6d6d71e165c7..5fde14ff525f 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -1925,7 +1925,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 11, attributes: 2 + interfaces: 0, fields: 1, methods: 12, attributes: 2 int value; descriptor: I flags: (0x0000) @@ -2020,6 +2020,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: #x() android.hosttest.annotation.HostSideTestThrow + public static native void nativeStillKeep(); + descriptor: ()V + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V flags: (0x0009) ACC_PUBLIC, ACC_STATIC diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt index 239b34efd5e7..e41d46d4daaa 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt @@ -2076,7 +2076,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 11, attributes: 3 + interfaces: 0, fields: 1, methods: 12, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2229,13 +2229,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow x: #x() - com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute - x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestThrow + public static native void nativeStillKeep(); + descriptor: ()V + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V flags: (0x0009) ACC_PUBLIC, ACC_STATIC diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt index 296b1d0c19d0..2ca723bea232 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt @@ -2612,7 +2612,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 12, attributes: 3 + interfaces: 0, fields: 1, methods: 13, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2820,13 +2820,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow x: #x() - com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute - x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep RuntimeInvisibleAnnotations: x: #x() android.hosttest.annotation.HostSideTestThrow + public static native void nativeStillKeep(); + descriptor: ()V + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + public static void nativeStillNotSupported_should_be_like_this(); descriptor: ()V flags: (0x0009) ACC_PUBLIC, ACC_STATIC diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index 7f03f0242de8..73b5e2fadbad 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java @@ -15,6 +15,7 @@ */ package com.android.hoststubgen.test.tinyframework; +import android.hosttest.annotation.HostSideTestKeep; import android.hosttest.annotation.HostSideTestNativeSubstitutionClass; import android.hosttest.annotation.HostSideTestThrow; import android.hosttest.annotation.HostSideTestWholeClassKeep; @@ -49,6 +50,9 @@ public class TinyFrameworkNative { @HostSideTestThrow public static native void nativeStillNotSupported(); + @HostSideTestKeep + public static native void nativeStillKeep(); + public static void nativeStillNotSupported_should_be_like_this() { throw new RuntimeException(); } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 1d13507f738b..14229a0ede5f 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -18,7 +18,6 @@ package com.android.hoststubgen.test.tinyframework; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; import com.android.hoststubgen.test.tinyframework.R.Nested; @@ -146,28 +145,22 @@ public class TinyFrameworkClassTest { } @Test - public void testSubstituteNativeWithThrow() throws Exception { - // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class, - // because @Throw implies @Keep (not @Stub), and we currently compile this test - // against the stub jar (so it won't contain @Throw methods). - // - // But the method exists at runtime, so we can use reflections to call it. - // - // In the real Ravenwood environment, we don't use HostStubGen's stub jar at all, - // so it's not a problem. - - final var clazz = TinyFrameworkNative.class; - final var method = clazz.getMethod("nativeStillNotSupported"); + public void testSubstituteNativeWithThrow() { + thrown.expect(RuntimeException.class); + thrown.expectMessage("not yet supported"); - try { - method.invoke(null); + TinyFrameworkNative.nativeStillNotSupported(); + } - fail("java.lang.reflect.InvocationTargetException expected"); + @Test + public void testSubstituteNativeWithKeep() { + // We don't want to complicate the test by setting up JNI, + // so to test out whether the native method is preserved, we + // check whether calling it will throw UnsatisfiedLinkError, + // which would only happen on native methods. + thrown.expect(UnsatisfiedLinkError.class); - } catch (java.lang.reflect.InvocationTargetException e) { - var inner = e.getCause(); - assertThat(inner.getMessage()).contains("not yet supported"); - } + TinyFrameworkNative.nativeStillKeep(); } @Test @@ -176,7 +169,6 @@ public class TinyFrameworkClassTest { thrown.expectMessage("Outer exception"); TinyFrameworkExceptionTester.testException(); - } @Test |