1 /* <lambda>null2 * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer.adapter 17 18 import com.android.hoststubgen.ClassParseException 19 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC 20 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME 21 import com.android.hoststubgen.asm.CTOR_NAME 22 import com.android.hoststubgen.asm.ClassNodes 23 import com.android.hoststubgen.asm.findAnnotationValueAsType 24 import com.android.hoststubgen.asm.findAnyAnnotation 25 import com.android.hoststubgen.asm.toHumanReadableClassName 26 import com.android.hoststubgen.log 27 import com.android.hoststubgen.visitors.OPCODE_VERSION 28 import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException 29 import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType 30 import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType 31 import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass 32 import com.android.platform.test.ravenwood.ravenizer.noRavenizerAnotType 33 import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType 34 import com.android.platform.test.ravenwood.ravenizer.ruleAnotType 35 import com.android.platform.test.ravenwood.ravenizer.runWithAnotType 36 import com.android.platform.test.ravenwood.ravenizer.testRuleType 37 import org.objectweb.asm.AnnotationVisitor 38 import org.objectweb.asm.ClassVisitor 39 import org.objectweb.asm.FieldVisitor 40 import org.objectweb.asm.MethodVisitor 41 import org.objectweb.asm.Opcodes 42 import org.objectweb.asm.Opcodes.ACC_FINAL 43 import org.objectweb.asm.Opcodes.ACC_PUBLIC 44 import org.objectweb.asm.Opcodes.ACC_STATIC 45 import org.objectweb.asm.commons.AdviceAdapter 46 import org.objectweb.asm.tree.ClassNode 47 48 /** 49 * Class visitor to update the RunWith and inject some necessary rules. 50 * 51 * - Change the @RunWith(RavenwoodAwareTestRunner.class). 52 * - If the original class has a @RunWith(...), then change it to an @InnerRunner(...). 53 * - Add RavenwoodAwareTestRunner's member rules as junit rules. 54 * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX. 55 */ 56 class RunnerRewritingAdapter private constructor( 57 protected val classes: ClassNodes, 58 nextVisitor: ClassVisitor, 59 ) : ClassVisitor(OPCODE_VERSION, nextVisitor) { 60 /** Arbitrary cut-off point when deciding whether to change the order or an existing rule.*/ 61 val RULE_ORDER_TWEAK_CUTOFF = 1973020500 62 63 /** Current class's internal name */ 64 lateinit var classInternalName: String 65 66 /** [ClassNode] for the current class */ 67 lateinit var classNode: ClassNode 68 69 /** True if this visitor is generating code. */ 70 var isGeneratingCode = false 71 72 /** Run a [block] with [isGeneratingCode] set to true. */ 73 private inline fun <T> generateCode(block: () -> T): T { 74 isGeneratingCode = true 75 try { 76 return block() 77 } finally { 78 isGeneratingCode = false 79 } 80 } 81 82 override fun visit( 83 version: Int, 84 access: Int, 85 name: String?, 86 signature: String?, 87 superName: String?, 88 interfaces: Array<out String>?, 89 ) { 90 classInternalName = name!! 91 classNode = classes.getClass(name) 92 if (!isTestLookingClass(classes, name)) { 93 throw RavenizerInternalException("This adapter shouldn't be used for non-test class") 94 } 95 super.visit(version, access, name, signature, superName, interfaces) 96 97 generateCode { 98 injectRunWithAnnotation() 99 if (!classes.hasClassInitializer(classInternalName)) { 100 injectStaticInitializer() 101 } 102 injectRules() 103 } 104 } 105 106 /** 107 * Remove the original @RunWith annotation. 108 */ 109 override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { 110 if (!isGeneratingCode && runWithAnotType.desc == descriptor) { 111 return null 112 } 113 return super.visitAnnotation(descriptor, visible) 114 } 115 116 override fun visitField( 117 access: Int, 118 name: String, 119 descriptor: String, 120 signature: String?, 121 value: Any? 122 ): FieldVisitor { 123 val fallback = super.visitField(access, name, descriptor, signature, value) 124 if (isGeneratingCode) { 125 return fallback 126 } 127 return FieldRuleOrderRewriter(name, fallback) 128 } 129 130 /** Inject an empty <clinit>. The body will be injected by [visitMethod]. */ 131 private fun injectStaticInitializer() { 132 visitMethod( 133 Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC, 134 CLASS_INITIALIZER_NAME, 135 CLASS_INITIALIZER_DESC, 136 null, 137 null 138 )!!.let { mv -> 139 mv.visitCode() 140 mv.visitInsn(Opcodes.RETURN) 141 mv.visitMaxs(0, 0) 142 mv.visitEnd() 143 } 144 } 145 146 /** 147 * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has 148 * a `@RunWith`, then change it to add a `@InnerRunner`. 149 */ 150 private fun injectRunWithAnnotation() { 151 // Extract the original RunWith annotation and its value. 152 val runWith = classNode.findAnyAnnotation(runWithAnotType.descAsSet) 153 val runWithClass = runWith?.let { an -> 154 findAnnotationValueAsType(an, "value") 155 } 156 157 if (runWith != null) { 158 if (runWithClass == ravenwoodTestRunnerType.type) { 159 // It already uses RavenwoodTestRunner. We'll just keep it, but we need to 160 // inject it again because the original one is removed by visitAnnotation(). 161 log.d("Class ${classInternalName.toHumanReadableClassName()}" + 162 " already uses RavenwoodTestRunner.") 163 visitAnnotation(runWithAnotType.desc, true)!!.let { av -> 164 av.visit("value", ravenwoodTestRunnerType) 165 av.visitEnd() 166 } 167 return 168 } 169 if (runWithClass == null) { 170 throw ClassParseException("@RunWith annotation doesn't have a property \"value\"" 171 + " in class ${classInternalName.toHumanReadableClassName()}") 172 } 173 174 // Inject an @InnerRunner. 175 visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av -> 176 av.visit("value", runWithClass) 177 av.visitEnd() 178 } 179 } 180 181 // Inject a @RunWith(RavenwoodAwareTestRunner.class). 182 visitAnnotation(runWithAnotType.desc, true)!!.let { av -> 183 av.visit("value", ravenwoodTestRunnerType.type) 184 av.visitEnd() 185 } 186 log.v("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}") 187 } 188 189 /* 190 Generate the fields and the ctor, which should looks like this: 191 192 public static final org.junit.rules.TestRule sRavenwoodImplicitClassMinRule; 193 descriptor: Lorg/junit/rules/TestRule; 194 flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL 195 RuntimeVisibleAnnotations: 196 0: #49(#50=I#51) 197 org.junit.ClassRule( 198 order=-2147483648 199 ) 200 201 public static final org.junit.rules.TestRule sRavenwoodImplicitClassMaxRule; 202 descriptor: Lorg/junit/rules/TestRule; 203 flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL 204 RuntimeVisibleAnnotations: 205 0: #49(#50=I#52) 206 org.junit.ClassRule( 207 order=2147483647 208 ) 209 210 public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMinRule; 211 descriptor: Lorg/junit/rules/TestRule; 212 flags: (0x0011) ACC_PUBLIC, ACC_FINAL 213 RuntimeVisibleAnnotations: 214 0: #53(#50=I#51) 215 org.junit.Rule( 216 order=-2147483648 217 ) 218 219 public final org.junit.rules.TestRule sRavenwoodImplicitInstanceMaxRule; 220 descriptor: Lorg/junit/rules/TestRule; 221 flags: (0x0011) ACC_PUBLIC, ACC_FINAL 222 RuntimeVisibleAnnotations: 223 0: #53(#50=I#52) 224 org.junit.Rule( 225 order=2147483647 226 ) 227 */ 228 229 val sRavenwood_ClassRuleMin = "sRavenwood_ClassRuleMin" 230 val sRavenwood_ClassRuleMax = "sRavenwood_ClassRuleMax" 231 val mRavenwood_InstRuleMin = "mRavenwood_InstRuleMin" 232 val mRavenwood_InstRuleMax = "mRavenwood_InstRuleMax" 233 234 private fun injectRules() { 235 injectRule(sRavenwood_ClassRuleMin, true, Integer.MIN_VALUE) 236 injectRule(sRavenwood_ClassRuleMax, true, Integer.MAX_VALUE) 237 injectRule(mRavenwood_InstRuleMin, false, Integer.MIN_VALUE) 238 injectRule(mRavenwood_InstRuleMax, false, Integer.MAX_VALUE) 239 } 240 241 private fun injectRule(fieldName: String, isStatic: Boolean, order: Int) { 242 visitField( 243 ACC_PUBLIC or ACC_FINAL or (if (isStatic) ACC_STATIC else 0), 244 fieldName, 245 testRuleType.desc, 246 null, 247 null, 248 ).let { fv -> 249 val anot = if (isStatic) { classRuleAnotType } else { ruleAnotType } 250 fv.visitAnnotation(anot.desc, true).let { 251 it.visit("order", order) 252 it.visitEnd() 253 } 254 fv.visitEnd() 255 } 256 } 257 258 override fun visitMethod( 259 access: Int, 260 name: String, 261 descriptor: String, 262 signature: String?, 263 exceptions: Array<String>?, 264 ): MethodVisitor { 265 val next = super.visitMethod(access, name, descriptor, signature, exceptions) 266 if (name == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) { 267 return ClassInitializerVisitor( 268 access, name, descriptor, signature, exceptions, next) 269 } 270 if (name == CTOR_NAME) { 271 return ConstructorVisitor( 272 access, name, descriptor, signature, exceptions, next) 273 } 274 return next 275 } 276 277 /* 278 279 static {}; 280 descriptor: ()V 281 flags: (0x0008) ACC_STATIC 282 Code: 283 stack=1, locals=0, args_size=0 284 0: getstatic #36 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule; 285 3: putstatic #39 // Field sRavenwoodImplicitClassMinRule:Lorg/junit/rules/TestRule; 286 6: getstatic #42 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule; 287 9: putstatic #45 // Field sRavenwoodImplicitClassMaxRule:Lorg/junit/rules/TestRule; 288 12: return 289 LineNumberTable: 290 line 33: 0 291 line 36: 6 292 */ 293 private inner class ClassInitializerVisitor( 294 access: Int, 295 val name: String, 296 val descriptor: String, 297 signature: String?, 298 exceptions: Array<String>?, 299 next: MethodVisitor?, 300 ) : MethodVisitor(OPCODE_VERSION, next) { 301 override fun visitCode() { 302 visitFieldInsn(Opcodes.GETSTATIC, 303 ravenwoodTestRunnerType.internlName, 304 IMPLICIT_CLASS_OUTER_RULE_NAME, 305 testRuleType.desc 306 ) 307 visitFieldInsn(Opcodes.PUTSTATIC, 308 classInternalName, 309 sRavenwood_ClassRuleMin, 310 testRuleType.desc 311 ) 312 313 visitFieldInsn(Opcodes.GETSTATIC, 314 ravenwoodTestRunnerType.internlName, 315 IMPLICIT_CLASS_INNER_RULE_NAME, 316 testRuleType.desc 317 ) 318 visitFieldInsn(Opcodes.PUTSTATIC, 319 classInternalName, 320 sRavenwood_ClassRuleMax, 321 testRuleType.desc 322 ) 323 324 super.visitCode() 325 } 326 } 327 328 /* 329 public com.android.ravenwoodtest.bivalenttest.runnertest.RavenwoodRunnerTest(); 330 descriptor: ()V 331 flags: (0x0001) ACC_PUBLIC 332 Code: 333 stack=2, locals=1, args_size=1 334 0: aload_0 335 1: invokespecial #1 // Method java/lang/Object."<init>":()V 336 4: aload_0 337 5: getstatic #7 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule; 338 8: putfield #13 // Field sRavenwoodImplicitInstanceMinRule:Lorg/junit/rules/TestRule; 339 11: aload_0 340 12: getstatic #18 // Field android/platform/test/ravenwood/RavenwoodAwareTestRunner.RavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule; 341 15: putfield #21 // Field sRavenwoodImplicitInstanceMaxRule:Lorg/junit/rules/TestRule; 342 18: return 343 LineNumberTable: 344 line 31: 0 345 line 38: 4 346 line 41: 11 347 LocalVariableTable: 348 Start Length Slot Name Signature 349 0 19 0 this Lcom/android/ravenwoodtest/bivalenttest/runnertest/RavenwoodRunnerTest; 350 */ 351 private inner class ConstructorVisitor( 352 access: Int, 353 name: String, 354 descriptor: String, 355 signature: String?, 356 exceptions: Array<String>?, 357 next: MethodVisitor?, 358 ) : AdviceAdapter(OPCODE_VERSION, next, ACC_ENUM, name, descriptor) { 359 override fun onMethodEnter() { 360 visitVarInsn(ALOAD, 0) 361 visitFieldInsn(Opcodes.GETSTATIC, 362 ravenwoodTestRunnerType.internlName, 363 IMPLICIT_INST_OUTER_RULE_NAME, 364 testRuleType.desc 365 ) 366 visitFieldInsn(Opcodes.PUTFIELD, 367 classInternalName, 368 mRavenwood_InstRuleMin, 369 testRuleType.desc 370 ) 371 372 visitVarInsn(ALOAD, 0) 373 visitFieldInsn(Opcodes.GETSTATIC, 374 ravenwoodTestRunnerType.internlName, 375 IMPLICIT_INST_INNER_RULE_NAME, 376 testRuleType.desc 377 ) 378 visitFieldInsn(Opcodes.PUTFIELD, 379 classInternalName, 380 mRavenwood_InstRuleMax, 381 testRuleType.desc 382 ) 383 } 384 } 385 386 /** 387 * Rewrite "order" of the existing junit rules to make sure no rules use a MAX or MIN order. 388 * 389 * Currently, we do it a hacky way -- use an arbitrary cut-off point, and if the order 390 * is larger than that, decrement by 1, and if it's smaller than the negative cut-off point, 391 * increment it by 1. 392 * 393 * (or the arbitrary number is already used.... then we're unlucky, let's change the cut-off 394 * point.) 395 */ 396 private inner class FieldRuleOrderRewriter( 397 val fieldName: String, 398 next: FieldVisitor, 399 ) : FieldVisitor(OPCODE_VERSION, next) { 400 override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { 401 val fallback = super.visitAnnotation(descriptor, visible) 402 if (descriptor != ruleAnotType.desc && descriptor != classRuleAnotType.desc) { 403 return fallback 404 } 405 return RuleOrderRewriter(fallback) 406 } 407 408 private inner class RuleOrderRewriter( 409 next: AnnotationVisitor, 410 ) : AnnotationVisitor(OPCODE_VERSION, next) { 411 override fun visit(name: String?, origValue: Any?) { 412 if (name != "order") { 413 return super.visit(name, origValue) 414 } 415 var order = origValue as Int 416 if (order == RULE_ORDER_TWEAK_CUTOFF || order == -RULE_ORDER_TWEAK_CUTOFF) { 417 // Oops. If this happens, we'll need to change RULE_ORDER_TWEAK_CUTOFF. 418 // Or, we could scan all the rules in the target jar and find an unused number. 419 // Because rules propagate to subclasses, we'll at least check all the 420 // super classes of the current class. 421 throw RavenizerInternalException( 422 "OOPS: Field $classInternalName.$fieldName uses $order." 423 + " We can't update it.") 424 } 425 if (order > RULE_ORDER_TWEAK_CUTOFF) { 426 order -= 1 427 } 428 if (order < -RULE_ORDER_TWEAK_CUTOFF) { 429 order += 1 430 } 431 super.visit(name, order) 432 } 433 } 434 } 435 436 companion object { 437 const val IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule" 438 const val IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule" 439 const val IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule" 440 const val IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule" 441 442 fun shouldProcess(classes: ClassNodes, className: String): Boolean { 443 if (!isTestLookingClass(classes, className)) { 444 return false 445 } 446 // Don't process a class if it has a @NoRavenizer annotation. 447 classes.findClass(className)?.let { cn -> 448 if (cn.findAnyAnnotation(noRavenizerAnotType.descAsSet) != null) { 449 log.i("Class ${className.toHumanReadableClassName()} has" + 450 " @${noRavenizerAnotType.humanReadableName}. Skipping." 451 ) 452 return false 453 } 454 } 455 return true 456 } 457 458 fun maybeApply( 459 className: String, 460 classes: ClassNodes, 461 nextVisitor: ClassVisitor, 462 ): ClassVisitor { 463 if (!shouldProcess(classes, className)) { 464 return nextVisitor 465 } else { 466 return RunnerRewritingAdapter(classes, nextVisitor) 467 } 468 } 469 } 470 } 471