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