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