• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
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 package com.android.hoststubgen.visitors
17 
18 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
19 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
20 import com.android.hoststubgen.asm.CTOR_NAME
21 import com.android.hoststubgen.asm.ClassNodes
22 import com.android.hoststubgen.asm.adjustStackForConstructorRedirection
23 import com.android.hoststubgen.asm.changeMethodDescriptorReturnType
24 import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
25 import com.android.hoststubgen.asm.writeByteCodeToPushArguments
26 import com.android.hoststubgen.asm.writeByteCodeToReturn
27 import com.android.hoststubgen.filters.FilterPolicy
28 import com.android.hoststubgen.filters.FilterPolicyWithReason
29 import com.android.hoststubgen.filters.OutputFilter
30 import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
31 import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
32 import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
33 import com.android.hoststubgen.hosthelper.HostTestUtils
34 import com.android.hoststubgen.log
35 import org.objectweb.asm.ClassVisitor
36 import org.objectweb.asm.MethodVisitor
37 import org.objectweb.asm.Opcodes
38 import org.objectweb.asm.Opcodes.INVOKEINTERFACE
39 import org.objectweb.asm.Opcodes.INVOKESPECIAL
40 import org.objectweb.asm.Opcodes.INVOKESTATIC
41 import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
42 import org.objectweb.asm.Type
43 
44 /**
45  * An adapter that generates the "impl" class file from an input class file.
46  */
47 class ImplGeneratingAdapter(
48     classes: ClassNodes,
49     nextVisitor: ClassVisitor,
50     filter: OutputFilter,
51     options: Options,
52 ) : BaseAdapter(classes, nextVisitor, filter, options) {
53 
54     private var classLoadHooks: List<String> = emptyList()
55 
56     override fun visit(
57         version: Int,
58         origAccess: Int,
59         name: String,
60         signature: String?,
61         superName: String?,
62         interfaces: Array<String>
63     ) {
64         val access = modifyClassAccess(origAccess)
65         super.visit(version, access, name, signature, superName, interfaces)
66 
67         classLoadHooks = filter.getClassLoadHooks(currentClassName)
68 
69         // classLoadHookMethod is non-null, then we need to inject code to call it
70         // in the class initializer.
71         // If the target class already has a class initializer, then we need to inject code to it.
72         // Otherwise, we need to create one.
73 
74         if (classLoadHooks.isNotEmpty()) {
75             log.d("  ClassLoadHooks: $classLoadHooks")
76             if (!classes.hasClassInitializer(currentClassName)) {
77                 injectClassLoadHook()
78             }
79         }
80     }
81 
82     private fun injectClassLoadHook() {
83         writeRawMembers {
84             // Create a class initializer to call onClassLoaded().
85             // Each class can only have at most one class initializer, but the base class
86             // StaticInitMerger will merge it with the existing one, if any.
87             visitMethod(
88                 Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
89                 CLASS_INITIALIZER_NAME,
90                 "()V",
91                 null,
92                 null
93             )!!.let { mv ->
94                 // Method prologue
95                 mv.visitCode()
96 
97                 writeClassLoadHookCalls(mv)
98                 mv.visitInsn(Opcodes.RETURN)
99 
100                 // Method epilogue
101                 mv.visitMaxs(0, 0)
102                 mv.visitEnd()
103             }
104         }
105     }
106 
107     private fun writeClassLoadHookCalls(mv: MethodVisitor) {
108         classLoadHooks.forEach { classLoadHook ->
109             // First argument: the class type.
110             mv.visitLdcInsn(Type.getType("L$currentClassName;"))
111 
112             // Second argument: method name
113             mv.visitLdcInsn(classLoadHook)
114 
115             // Call HostTestUtils.onClassLoaded().
116             mv.visitMethodInsn(
117                 INVOKESTATIC,
118                 HostTestUtils.CLASS_INTERNAL_NAME,
119                 "onClassLoaded",
120                 "(Ljava/lang/Class;Ljava/lang/String;)V",
121                 false
122             )
123         }
124     }
125 
126     override fun updateAccessFlags(
127         access: Int,
128         name: String,
129         descriptor: String,
130         policy: FilterPolicy,
131     ): Int {
132         if (policy.isMethodRewriteBody) {
133             // If we are rewriting the entire method body, we need
134             // to convert native methods to non-native
135             return access and Opcodes.ACC_NATIVE.inv()
136         }
137         return access
138     }
139 
140     override fun visitMethodInner(
141         access: Int,
142         name: String,
143         descriptor: String,
144         signature: String?,
145         exceptions: Array<String>?,
146         policy: FilterPolicyWithReason,
147         substituted: Boolean,
148         superVisitor: MethodVisitor?,
149     ): MethodVisitor? {
150         var innerVisitor = superVisitor
151 
152         //  If method logging is enabled, inject call to the logging method.
153         val methodCallHooks = filter.getMethodCallHooks(currentClassName, name, descriptor)
154         if (methodCallHooks.isNotEmpty()) {
155             innerVisitor = MethodCallHookInjectingAdapter(
156                 name,
157                 descriptor,
158                 methodCallHooks,
159                 innerVisitor,
160             )
161         }
162 
163         // If this class already has a class initializer and a class load hook is needed, then
164         // we inject code.
165         if (classLoadHooks.isNotEmpty() &&
166             name == CLASS_INITIALIZER_NAME &&
167             descriptor == CLASS_INITIALIZER_DESC
168         ) {
169             innerVisitor = ClassLoadHookInjectingMethodAdapter(innerVisitor)
170         }
171 
172         fun MethodVisitor.withAnnotation(descriptor: String): MethodVisitor {
173             this.visitAnnotation(descriptor, true)
174             return this
175         }
176 
177         log.withIndent {
178             // When we encounter native methods, we want to forcefully
179             // inject a method body. Also see [updateAccessFlags].
180             val forceCreateBody = (access and Opcodes.ACC_NATIVE) != 0
181             when (policy.policy) {
182                 FilterPolicy.Throw -> {
183                     log.v("Making method throw...")
184                     return ThrowingMethodAdapter(forceCreateBody, innerVisitor)
185                         .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR)
186                 }
187                 FilterPolicy.Ignore -> {
188                     log.v("Making method ignored...")
189                     return IgnoreMethodAdapter(descriptor, forceCreateBody, innerVisitor)
190                         .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR)
191                 }
192                 FilterPolicy.Redirect -> {
193                     log.v("Redirecting method...")
194                     return RedirectMethodAdapter(
195                         access, name, descriptor,
196                         forceCreateBody, innerVisitor
197                     )
198                         .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
199                 }
200                 else -> {}
201             }
202         }
203 
204         if (filter.hasAnyMethodCallReplace()) {
205             innerVisitor = MethodCallReplacingAdapter(name, innerVisitor)
206         }
207         if (substituted) {
208             innerVisitor?.withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
209         }
210 
211         return innerVisitor
212     }
213 
214     /**
215      * A method adapter that replaces the method body with a HostTestUtils.onThrowMethodCalled()
216      * call.
217      */
218     private inner class ThrowingMethodAdapter(
219         createBody: Boolean,
220         next: MethodVisitor?
221     ) : BodyReplacingMethodVisitor(createBody, next) {
222         override fun emitNewCode() {
223             visitMethodInsn(
224                 INVOKESTATIC,
225                 HostTestUtils.CLASS_INTERNAL_NAME,
226                 "onThrowMethodCalled",
227                 "()V",
228                 false
229             )
230 
231             // We still need a RETURN opcode for the return type.
232             // For now, let's just inject a `throw`.
233             visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException")
234             visitInsn(Opcodes.DUP)
235             visitLdcInsn("Unreachable")
236             visitMethodInsn(
237                 Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
238                 "<init>", "(Ljava/lang/String;)V", false
239             )
240             visitInsn(Opcodes.ATHROW)
241 
242             // visitMaxs(3, if (isStatic) 0 else 1)
243             visitMaxs(0, 0) // We let ASM figure them out.
244         }
245     }
246 
247     /**
248      * A method adapter that replaces the method body with a no-op return.
249      */
250     private inner class IgnoreMethodAdapter(
251         val descriptor: String,
252         createBody: Boolean,
253         next: MethodVisitor?
254     ) : BodyReplacingMethodVisitor(createBody, next) {
255         override fun emitNewCode() {
256             when (Type.getReturnType(descriptor)) {
257                 Type.VOID_TYPE -> visitInsn(Opcodes.RETURN)
258                 Type.BOOLEAN_TYPE, Type.BYTE_TYPE, Type.CHAR_TYPE, Type.SHORT_TYPE,
259                 Type.INT_TYPE -> {
260                     visitInsn(Opcodes.ICONST_0)
261                     visitInsn(Opcodes.IRETURN)
262                 }
263                 Type.LONG_TYPE -> {
264                     visitInsn(Opcodes.LCONST_0)
265                     visitInsn(Opcodes.LRETURN)
266                 }
267                 Type.FLOAT_TYPE -> {
268                     visitInsn(Opcodes.FCONST_0)
269                     visitInsn(Opcodes.FRETURN)
270                 }
271                 Type.DOUBLE_TYPE -> {
272                     visitInsn(Opcodes.DCONST_0)
273                     visitInsn(Opcodes.DRETURN)
274                 }
275                 else -> {
276                     visitInsn(Opcodes.ACONST_NULL)
277                     visitInsn(Opcodes.ARETURN)
278                 }
279             }
280             visitMaxs(0, 0) // We let ASM figure them out.
281         }
282     }
283 
284     /**
285      * A method adapter that rewrite a method body with a
286      * call to a method in the redirection class.
287      */
288     private inner class RedirectMethodAdapter(
289         access: Int,
290         private val name: String,
291         private val descriptor: String,
292         createBody: Boolean,
293         next: MethodVisitor?
294     ) : BodyReplacingMethodVisitor(createBody, next) {
295 
296         private val isStatic = (access and Opcodes.ACC_STATIC) != 0
297 
298         override fun emitNewCode() {
299             var targetDescriptor = descriptor
300             var argOffset = 0
301 
302             // For non-static method, we need to tweak it a bit.
303             if (!isStatic) {
304                 // Push `this` as the first argument.
305                 this.visitVarInsn(Opcodes.ALOAD, 0)
306 
307                 // Update the descriptor -- add this class's type as the first argument
308                 // to the method descriptor.
309                 targetDescriptor = prependArgTypeToMethodDescriptor(
310                     descriptor,
311                     currentClassName,
312                 )
313 
314                 // Shift the original arguments by one.
315                 argOffset = 1
316             }
317 
318             writeByteCodeToPushArguments(descriptor, this, argOffset)
319 
320             visitMethodInsn(
321                 INVOKESTATIC,
322                 redirectionClass,
323                 name,
324                 targetDescriptor,
325                 false
326             )
327 
328             writeByteCodeToReturn(descriptor, this)
329 
330             visitMaxs(99, 0) // We let ASM figure them out.
331         }
332     }
333 
334     /**
335      * Inject calls to the method call hooks.
336      *
337      * Note, when the target method is a constructor, it may contain calls to `super(...)` or
338      * `this(...)`. The logging code will be injected *before* such calls.
339      */
340     private inner class MethodCallHookInjectingAdapter(
341         val name: String,
342         val descriptor: String,
343         val hooks: List<String>,
344         next: MethodVisitor?,
345     ) : MethodVisitor(OPCODE_VERSION, next) {
346         override fun visitCode() {
347             super.visitCode()
348 
349             hooks.forEach { hook ->
350                 mv.visitLdcInsn(Type.getType("L$currentClassName;"))
351                 visitLdcInsn(name)
352                 visitLdcInsn(descriptor)
353                 visitLdcInsn(hook)
354 
355                 visitMethodInsn(
356                     INVOKESTATIC,
357                     HostTestUtils.CLASS_INTERNAL_NAME,
358                     "callMethodCallHook",
359                     "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
360                     false
361                 )
362             }
363         }
364     }
365 
366     /**
367      * Inject a class load hook call.
368      */
369     private inner class ClassLoadHookInjectingMethodAdapter(
370         next: MethodVisitor?
371     ) : MethodVisitor(OPCODE_VERSION, next) {
372         override fun visitCode() {
373             super.visitCode()
374 
375             writeClassLoadHookCalls(this)
376         }
377     }
378 
379     private inner class MethodCallReplacingAdapter(
380         val callerMethodName: String,
381         next: MethodVisitor?,
382     ) : MethodVisitor(OPCODE_VERSION, next) {
383 
384         private fun doReplace(
385             opcode: Int,
386             owner: String,
387             name: String,
388             descriptor: String,
389         ): Boolean {
390             when (opcode) {
391                 INVOKESTATIC, INVOKEVIRTUAL, INVOKEINTERFACE -> {}
392                 // We only support INVOKESPECIAL when replacing constructors.
393                 INVOKESPECIAL -> if (name != CTOR_NAME) return false
394                 // Don't touch other opcodes.
395                 else -> return false
396             }
397 
398             val to = filter.getMethodCallReplaceTo(
399                 owner, name, descriptor
400             )
401 
402             if (to == null
403                 // Don't replace if the target is the callsite.
404                 || (to.className == currentClassName && to.methodName == callerMethodName)
405             ) {
406                 return false
407             }
408 
409             if (opcode != INVOKESPECIAL) {
410                 // It's either a static method call or virtual method call.
411                 // Either way, we don't manipulate the stack and send the original arguments
412                 // as is to the target method.
413                 //
414                 // If the call is a virtual call (INVOKEVIRTUAL or INVOKEINTERFACE), then
415                 // the first argument in the stack is the "this" object, so the target
416                 // method must have an extra argument as the first argument to receive it.
417                 // We update the method descriptor with prependArgTypeToMethodDescriptor()
418                 // to absorb this difference.
419 
420                 val toDesc = if (opcode == INVOKESTATIC) {
421                     descriptor
422                 } else {
423                     prependArgTypeToMethodDescriptor(descriptor, owner)
424                 }
425 
426                 mv.visitMethodInsn(
427                     INVOKESTATIC,
428                     to.className,
429                     to.methodName,
430                     toDesc,
431                     false
432                 )
433             } else {
434                 // Because an object initializer does not return a value, the newly created
435                 // but uninitialized object will be dup-ed at the bottom of the stack.
436                 // We first call the target method to consume the constructor arguments at the top.
437 
438                 val toDesc = changeMethodDescriptorReturnType(descriptor, owner)
439 
440                 // Before stack: { uninitialized, uninitialized, args... }
441                 mv.visitMethodInsn(
442                     INVOKESTATIC,
443                     to.className,
444                     to.methodName,
445                     toDesc,
446                     false
447                 )
448                 // After stack: { uninitialized, uninitialized, obj }
449 
450                 // Next we pop the 2 uninitialized instances out of the stack.
451                 adjustStackForConstructorRedirection(mv)
452             }
453 
454             return true
455         }
456 
457         override fun visitMethodInsn(
458             opcode: Int,
459             owner: String,
460             name: String,
461             descriptor: String,
462             isInterface: Boolean,
463         ) {
464             if (!doReplace(opcode, owner, name, descriptor)) {
465                 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
466             }
467         }
468     }
469 }
470