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