1 /* 2 * Copyright (C) 2022 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.android.plugin 18 19 import com.android.build.api.instrumentation.AsmClassVisitorFactory 20 import com.android.build.api.instrumentation.ClassContext 21 import com.android.build.api.instrumentation.ClassData 22 import com.android.build.api.instrumentation.InstrumentationParameters 23 import java.io.File 24 import org.gradle.api.provider.Property 25 import org.gradle.api.tasks.Internal 26 import org.objectweb.asm.ClassReader 27 import org.objectweb.asm.ClassVisitor 28 import org.objectweb.asm.FieldVisitor 29 import org.objectweb.asm.MethodVisitor 30 import org.objectweb.asm.Opcodes 31 32 /** 33 * ASM Adapter that transforms @AndroidEntryPoint-annotated classes to extend the Hilt 34 * generated android class, including the @HiltAndroidApp application class. 35 */ 36 class AndroidEntryPointClassVisitor( 37 private val apiVersion: Int, 38 nextClassVisitor: ClassVisitor, 39 private val classContext: ClassContext 40 ) : ClassVisitor(apiVersion, nextClassVisitor) { 41 42 abstract class Factory : AsmClassVisitorFactory<InstrumentationParameters.None> { createClassVisitornull43 override fun createClassVisitor( 44 classContext: ClassContext, 45 nextClassVisitor: ClassVisitor 46 ): ClassVisitor { 47 return AndroidEntryPointClassVisitor( 48 apiVersion = instrumentationContext.apiVersion.get(), 49 nextClassVisitor = nextClassVisitor, 50 classContext = classContext 51 ) 52 } 53 54 /** 55 * Check if a class should be transformed. 56 * 57 * Only classes that are an Android entry point should be transformed. 58 */ isInstrumentablenull59 override fun isInstrumentable(classData: ClassData) = 60 classData.classAnnotations.any { ANDROID_ENTRY_POINT_ANNOTATIONS.contains(it) } 61 } 62 63 // The name of the Hilt generated superclass in it internal form. 64 // e.g. "my/package/Hilt_MyActivity" 65 lateinit var newSuperclassName: String 66 67 lateinit var oldSuperclassName: String 68 visitnull69 override fun visit( 70 version: Int, 71 access: Int, 72 name: String, 73 signature: String?, 74 superName: String?, 75 interfaces: Array<out String>? 76 ) { 77 val packageName = name.substringBeforeLast('/') 78 val className = name.substringAfterLast('/') 79 newSuperclassName = 80 packageName + "/Hilt_" + className.replace("$", "_") 81 oldSuperclassName = superName ?: error { "Superclass of $name is null!" } 82 val newSignature = signature?.replaceFirst(oldSuperclassName, newSuperclassName) 83 super.visit(version, access, name, newSignature, newSuperclassName, interfaces) 84 } 85 visitMethodnull86 override fun visitMethod( 87 access: Int, 88 name: String, 89 descriptor: String, 90 signature: String?, 91 exceptions: Array<out String>? 92 ): MethodVisitor { 93 val nextMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) 94 val invokeSpecialVisitor = InvokeSpecialAdapter( 95 apiVersion = apiVersion, 96 nextClassVisitor = nextMethodVisitor, 97 isConstructor = name == "<init>" 98 ) 99 if (name == ON_RECEIVE_METHOD_NAME && 100 descriptor == ON_RECEIVE_METHOD_DESCRIPTOR && 101 hasOnReceiveBytecodeInjectionMarker() 102 ) { 103 return OnReceiveAdapter(apiVersion, invokeSpecialVisitor) 104 } 105 return invokeSpecialVisitor 106 } 107 108 /** 109 * Adapter for super calls (e.g. super.onCreate()) that rewrites the owner reference of the 110 * invokespecial instruction to use the new superclass. 111 * 112 * The invokespecial instruction is emitted for code that between other things also invokes a 113 * method of a superclass of the current class. The opcode invokespecial takes two operands, each 114 * of 8 bit, that together represent an address in the constant pool to a method reference. The 115 * method reference is computed at compile-time by looking the direct superclass declaration, but 116 * at runtime the code behaves like invokevirtual, where as the actual method invoked is looked up 117 * based on the class hierarchy. 118 * 119 * However, it has been observed that on APIs 19 to 22 the Android Runtime (ART) jumps over the 120 * direct superclass and into the method reference class, causing unexpected behaviours. 121 * Therefore, this method performs the additional transformation to rewrite direct super call 122 * invocations to use a method reference whose class in the pool is the new superclass. 123 * 124 * @see: https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial 125 * @see: https://source.android.com/devices/tech/dalvik/dalvik-bytecode 126 */ 127 inner class InvokeSpecialAdapter( 128 apiVersion: Int, 129 nextClassVisitor: MethodVisitor, 130 private val isConstructor: Boolean 131 ) : MethodVisitor(apiVersion, nextClassVisitor) { 132 133 // Flag to know that we have visited the first invokespecial instruction in a constructor call 134 // which corresponds to the `super()` constructor call required as the first statement of an 135 // overridden constructor body. 136 private var visitedSuperConstructorInvokeSpecial = false 137 visitMethodInsnnull138 override fun visitMethodInsn( 139 opcode: Int, 140 owner: String, 141 name: String, 142 descriptor: String, 143 isInterface: Boolean 144 ) { 145 if (opcode == Opcodes.INVOKESPECIAL && owner == oldSuperclassName) { 146 // Update the owner of INVOKESPECIAL instructions, including those found in constructors. 147 super.visitMethodInsn(opcode, getAdaptedOwner(name) ?: owner, name, descriptor, isInterface) 148 } else { 149 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) 150 } 151 } 152 153 // Gets the updated owner of an INVOKESPECIAL found in the method being visited. getAdaptedOwnernull154 private fun getAdaptedOwner(methodRefName: String): String? { 155 // If the method reference is a constructor and we are visiting a constructor then only the 156 // first INVOKESPECIAL instruction found should be transformed since that correponds to the 157 // super constructor call. 158 if (methodRefName == "<init>" && isConstructor && !visitedSuperConstructorInvokeSpecial) { 159 visitedSuperConstructorInvokeSpecial = true 160 return newSuperclassName 161 } 162 // If the method reference is not a constructor then the instruction for a super call that 163 // should be transformed. 164 if (methodRefName != "<init>") { 165 return newSuperclassName 166 } 167 return null 168 } 169 } 170 171 /** 172 * Method adapter for a BroadcastReceiver's onReceive method to insert a super call since with 173 * its new superclass, onReceive will no longer be abstract (it is implemented by Hilt generated 174 * receiver). 175 */ 176 inner class OnReceiveAdapter( 177 apiVersion: Int, 178 nextClassVisitor: MethodVisitor 179 ) : MethodVisitor(apiVersion, nextClassVisitor) { visitCodenull180 override fun visitCode() { 181 super.visitCode() 182 super.visitIntInsn(Opcodes.ALOAD, 0) // Load 'this' 183 super.visitIntInsn(Opcodes.ALOAD, 1) // Load method param 1 (Context) 184 super.visitIntInsn(Opcodes.ALOAD, 2) // Load method param 2 (Intent) 185 super.visitMethodInsn( 186 Opcodes.INVOKESPECIAL, 187 newSuperclassName, 188 ON_RECEIVE_METHOD_NAME, 189 ON_RECEIVE_METHOD_DESCRIPTOR, 190 false 191 ) 192 } 193 } 194 195 /** 196 * Check if Hilt generated class is a BroadcastReceiver with the marker annotation which means 197 * a super.onReceive invocation has to be inserted in the implementation. 198 */ hasOnReceiveBytecodeInjectionMarkernull199 private fun hasOnReceiveBytecodeInjectionMarker(): Boolean { 200 val newSuperclassFQName = newSuperclassName.toFQName() 201 return classContext.loadClassData(newSuperclassFQName) 202 ?.classAnnotations?.contains(ON_RECEIVE_MARKER_ANNOTATION) 203 ?: error("Cannot load class $newSuperclassFQName!") 204 } 205 206 /** 207 * Return a fully qualified name from an internal name. 208 * See https://asm.ow2.io/javadoc/org/objectweb/asm/Type.html#getInternalName() 209 */ toFQNamenull210 private fun String.toFQName() = this.replace('/', '.') 211 212 companion object { 213 val ANDROID_ENTRY_POINT_ANNOTATIONS = setOf( 214 "dagger.hilt.android.AndroidEntryPoint", 215 "dagger.hilt.android.HiltAndroidApp" 216 ) 217 const val ON_RECEIVE_METHOD_NAME = "onReceive" 218 const val ON_RECEIVE_METHOD_DESCRIPTOR = 219 "(Landroid/content/Context;Landroid/content/Intent;)V" 220 const val ON_RECEIVE_MARKER_ANNOTATION = "dagger.hilt.android.internal.OnReceiveBytecodeInjectionMarker" 221 } 222 } 223