Home | History | Annotate | Download | only in invokecustom
      1 /*
      2  * Copyright (C) 2017 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 
     17 package invokecustom;
     18 
     19 import java.io.FileInputStream;
     20 import java.io.FileOutputStream;
     21 import java.io.IOException;
     22 import java.lang.invoke.CallSite;
     23 import java.lang.invoke.MethodHandle;
     24 import java.lang.invoke.MethodHandles;
     25 import java.lang.invoke.MethodType;
     26 import java.nio.file.Path;
     27 import java.nio.file.Paths;
     28 import java.util.function.Consumer;
     29 import org.objectweb.asm.ClassReader;
     30 import org.objectweb.asm.ClassVisitor;
     31 import org.objectweb.asm.ClassWriter;
     32 import org.objectweb.asm.Handle;
     33 import org.objectweb.asm.MethodVisitor;
     34 import org.objectweb.asm.Opcodes;
     35 import org.objectweb.asm.Type;
     36 
     37 public class TestGenerator {
     38 
     39   private final Path baseName;
     40 
     41   public static void main(String[] args) throws IOException {
     42     assert args.length == 1;
     43     TestGenerator testGenerator = new TestGenerator(Paths.get(args[0]));
     44     testGenerator.generateTests();
     45   }
     46 
     47   public TestGenerator(Path baseName) {
     48     this.baseName = baseName;
     49   }
     50 
     51   private void generateTests() throws IOException {
     52     generateTest(this::generateMethodTest1, "invokecustom/InvokeCustom1");
     53     generateTest(this::generateMethodTest2, "invokecustom/InvokeCustom2");
     54     generateTest(this::generateMethodTest3, "invokecustom/InvokeCustom3");
     55     // skip generateMethodTest4 - dexdump doesn't support invoke-direct handles
     56     generateTest(this::generateMethodTest5, "invokecustom/InvokeCustom5");
     57     generateTest(this::generateMethodTest6, "invokecustom/InvokeCustom6");
     58     generateTest(this::generateMethodTest7, "invokecustom/InvokeCustom7");
     59     generateTest(this::generateMethodTest8, "invokecustom/InvokeCustom8");
     60     // skip generateMethodTest9 - dexdump doesn't support invoke-interface handles
     61     generateTest(this::generateMethodMain, "invokecustom/InvokeCustom");
     62   }
     63 
     64   private void generateTest(Consumer<ClassWriter> generator, String className) throws IOException {
     65     ClassReader cr =
     66         new ClassReader(
     67             new FileInputStream(baseName.resolve("invokecustom/InvokeCustom.class").toFile()));
     68     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
     69     cr.accept(
     70         new ClassVisitor(Opcodes.ASM5, cw) {
     71           @Override
     72           public void visit(
     73               int version,
     74               int access,
     75               String name,
     76               String signature,
     77               String superName,
     78               String[] interfaces) {
     79             super.visit(version, access, className, signature, superName, interfaces);
     80           }
     81 
     82           @Override
     83           public void visitEnd() {
     84             generator.accept(cw);
     85             super.visitEnd();
     86           }
     87         },
     88         0);
     89     new FileOutputStream(baseName.resolve(className + ".class").toFile()).write(cw.toByteArray());
     90   }
     91 
     92   /* generate main method that only call all test methods. */
     93   private void generateMethodMain(ClassVisitor cv) {
     94     MethodVisitor mv =
     95         cv.visitMethod(
     96             Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
     97     String internalName = Type.getInternalName(InvokeCustom.class);
     98     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "1", "test1", "()V", false);
     99     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "2", "test2", "()V", false);
    100     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "3", "test3", "()V", false);
    101     // mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "4", "test4", "()V", false);
    102     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "5", "test5", "()V", false);
    103     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "6", "test6", "()V", false);
    104     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "7", "test7", "()V", false);
    105     mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "8", "test8", "()V", false);
    106     // mv.visitMethodInsn(Opcodes.INVOKESTATIC, internalName + "9", "test9", "()V", false);
    107     mv.visitInsn(Opcodes.RETURN);
    108     mv.visitMaxs(-1, -1);
    109   }
    110 
    111   /**
    112    * Generate test with an invokedynamic, a static bootstrap method without extra args and no arg to
    113    * the target method.
    114    */
    115   private void generateMethodTest1(ClassVisitor cv) {
    116     MethodVisitor mv =
    117         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test1", "()V", null, null);
    118     MethodType mt =
    119         MethodType.methodType(
    120             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    121     Handle bootstrap =
    122         new Handle(
    123             Opcodes.H_INVOKESTATIC,
    124             Type.getInternalName(InvokeCustom.class),
    125             "bsmLookupStatic",
    126             mt.toMethodDescriptorString(),
    127             false);
    128     mv.visitInvokeDynamicInsn("targetMethodTest1", "()V", bootstrap);
    129     mv.visitInsn(Opcodes.RETURN);
    130     mv.visitMaxs(-1, -1);
    131   }
    132 
    133   /**
    134    * Generate test with an invokedynamic, a static bootstrap method without extra args and args to
    135    * the target method.
    136    */
    137   private void generateMethodTest2(ClassVisitor cv) {
    138     MethodVisitor mv =
    139         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test2", "()V", null, null);
    140     MethodType mt =
    141         MethodType.methodType(
    142             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    143     Handle bootstrap =
    144         new Handle(
    145             Opcodes.H_INVOKESTATIC,
    146             Type.getInternalName(InvokeCustom.class),
    147             "bsmLookupStatic",
    148             mt.toMethodDescriptorString(),
    149             false);
    150     mv.visitLdcInsn(new Boolean(true));
    151     mv.visitLdcInsn(new Byte((byte) 127));
    152     mv.visitLdcInsn(new Character('c'));
    153     mv.visitLdcInsn(new Short((short) 1024));
    154     mv.visitLdcInsn(new Integer(123456));
    155     mv.visitLdcInsn(new Float(1.2f));
    156     mv.visitLdcInsn(new Long(123456789));
    157     mv.visitLdcInsn(new Double(3.5123456789));
    158     mv.visitLdcInsn("String");
    159     mv.visitInvokeDynamicInsn("targetMethodTest2", "(ZBCSIFJDLjava/lang/String;)V", bootstrap);
    160     mv.visitInsn(Opcodes.RETURN);
    161     mv.visitMaxs(-1, -1);
    162   }
    163 
    164   /**
    165    * Generate test with an invokedynamic, a static bootstrap method with extra args and no arg to
    166    * the target method.
    167    */
    168   private void generateMethodTest3(ClassVisitor cv) {
    169     MethodVisitor mv =
    170         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test3", "()V", null, null);
    171     MethodType mt =
    172         MethodType.methodType(
    173             CallSite.class,
    174             MethodHandles.Lookup.class,
    175             String.class,
    176             MethodType.class,
    177             int.class,
    178             long.class,
    179             float.class,
    180             double.class);
    181     Handle bootstrap =
    182         new Handle(
    183             Opcodes.H_INVOKESTATIC,
    184             Type.getInternalName(InvokeCustom.class),
    185             "bsmLookupStaticWithExtraArgs",
    186             mt.toMethodDescriptorString(),
    187             false);
    188     mv.visitInvokeDynamicInsn(
    189         "targetMethodTest3",
    190         "()V",
    191         bootstrap,
    192         new Integer(1),
    193         new Long(123456789),
    194         new Float(123.456),
    195         new Double(123456.789123));
    196     mv.visitInsn(Opcodes.RETURN);
    197     mv.visitMaxs(-1, -1);
    198   }
    199 
    200   /**
    201    * Generate test with an invokedynamic, a static bootstrap method with an extra arg that is a
    202    * MethodHandle of kind invokespecial.
    203    */
    204   private void generateMethodTest4(ClassVisitor cv) {
    205     MethodVisitor mv =
    206         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test4", "()V", null, null);
    207     MethodType mt =
    208         MethodType.methodType(
    209             CallSite.class,
    210             MethodHandles.Lookup.class,
    211             String.class,
    212             MethodType.class,
    213             MethodHandle.class);
    214     Handle bootstrap =
    215         new Handle(
    216             Opcodes.H_INVOKESTATIC,
    217             Type.getInternalName(InvokeCustom.class),
    218             "bsmCreateCallSite",
    219             mt.toMethodDescriptorString(),
    220             false);
    221     mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(InvokeCustom.class));
    222     mv.visitInsn(Opcodes.DUP);
    223     mv.visitMethodInsn(
    224         Opcodes.INVOKESPECIAL, Type.getInternalName(InvokeCustom.class), "<init>", "()V", false);
    225     mv.visitInvokeDynamicInsn(
    226         "targetMethodTest4",
    227         "(Linvokecustom/InvokeCustom;)V",
    228         bootstrap,
    229         new Handle(
    230             Opcodes.H_INVOKESPECIAL,
    231             Type.getInternalName(Super.class),
    232             "targetMethodTest4",
    233             "()V",
    234             false));
    235     mv.visitInsn(Opcodes.RETURN);
    236     mv.visitMaxs(-1, -1);
    237   }
    238 
    239   /**
    240    * Generate a test with an invokedynamic where the target generates a result that the call site
    241    * prints out.
    242    */
    243   private void generateMethodTest5(ClassVisitor cv) {
    244     MethodVisitor mv =
    245         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test5", "()V", null, null);
    246     MethodType mt =
    247         MethodType.methodType(
    248             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    249     Handle bootstrap =
    250         new Handle(
    251             Opcodes.H_INVOKESTATIC,
    252             Type.getInternalName(InvokeCustom.class),
    253             "bsmLookupStatic",
    254             mt.toMethodDescriptorString(),
    255             false);
    256     mv.visitIntInsn(Opcodes.SIPUSH, 1000);
    257     mv.visitIntInsn(Opcodes.SIPUSH, -923);
    258     mv.visitIntInsn(Opcodes.SIPUSH, 77);
    259     mv.visitInvokeDynamicInsn("targetMethodTest5", "(III)I", bootstrap);
    260     mv.visitVarInsn(Opcodes.ISTORE, 0);
    261     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    262     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
    263     mv.visitInsn(Opcodes.DUP);
    264     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
    265     mv.visitLdcInsn("targetMethodTest5 returned: ");
    266     mv.visitMethodInsn(
    267         Opcodes.INVOKEVIRTUAL,
    268         "java/lang/StringBuilder",
    269         "append",
    270         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
    271     mv.visitVarInsn(Opcodes.ILOAD, 0);
    272     mv.visitMethodInsn(
    273         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
    274     mv.visitMethodInsn(
    275         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
    276     mv.visitMethodInsn(
    277         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
    278     mv.visitInsn(Opcodes.RETURN);
    279     mv.visitMaxs(-1, -1);
    280   }
    281 
    282   /**
    283    * Generate a test with an invokedynamic where the call site invocation tests the summation of two
    284    * long values and returns a long.
    285    */
    286   private void generateMethodTest6(ClassVisitor cv) {
    287     MethodVisitor mv =
    288         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test6", "()V", null, null);
    289     MethodType mt =
    290         MethodType.methodType(
    291             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    292     Handle bootstrap =
    293         new Handle(
    294             Opcodes.H_INVOKESTATIC,
    295             Type.getInternalName(InvokeCustom.class),
    296             "bsmLookupStatic",
    297             mt.toMethodDescriptorString(),
    298             false);
    299     mv.visitLdcInsn(0x77777777777l);
    300     mv.visitLdcInsn(-0x11111111111l);
    301     mv.visitLdcInsn(0x66666666666l);
    302     mv.visitInvokeDynamicInsn("targetMethodTest6", "(JJJ)J", bootstrap);
    303     mv.visitVarInsn(Opcodes.LSTORE, 0);
    304     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    305     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
    306     mv.visitInsn(Opcodes.DUP);
    307     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
    308     mv.visitLdcInsn("targetMethodTest6 returned: ");
    309     mv.visitMethodInsn(
    310         Opcodes.INVOKEVIRTUAL,
    311         "java/lang/StringBuilder",
    312         "append",
    313         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
    314     mv.visitVarInsn(Opcodes.LLOAD, 0);
    315     mv.visitMethodInsn(
    316         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
    317     mv.visitMethodInsn(
    318         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
    319     mv.visitMethodInsn(
    320         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
    321     mv.visitInsn(Opcodes.RETURN);
    322     mv.visitMaxs(-1, -1);
    323   }
    324 
    325   /**
    326    * Generate a test with an invokedynamic where the call site invocation tests the product of two
    327    * float values and returns a double.
    328    */
    329   private void generateMethodTest7(ClassVisitor cv) {
    330     MethodVisitor mv =
    331         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test7", "()V", null, null);
    332     MethodType mt =
    333         MethodType.methodType(
    334             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    335     Handle bootstrap =
    336         new Handle(
    337             Opcodes.H_INVOKESTATIC,
    338             Type.getInternalName(InvokeCustom.class),
    339             "bsmLookupStatic",
    340             mt.toMethodDescriptorString(),
    341             false);
    342     double x = 0.5009765625;
    343     double y = -x;
    344     mv.visitLdcInsn((float) x);
    345     mv.visitLdcInsn((float) y);
    346     mv.visitLdcInsn(x * y);
    347     mv.visitInvokeDynamicInsn("targetMethodTest7", "(FFD)D", bootstrap);
    348     mv.visitVarInsn(Opcodes.DSTORE, 0);
    349     mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    350     mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
    351     mv.visitInsn(Opcodes.DUP);
    352     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
    353     mv.visitLdcInsn("targetMethodTest6 returned: ");
    354     mv.visitMethodInsn(
    355         Opcodes.INVOKEVIRTUAL,
    356         "java/lang/StringBuilder",
    357         "append",
    358         "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
    359     mv.visitVarInsn(Opcodes.DLOAD, 0);
    360     mv.visitMethodInsn(
    361         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(D)Ljava/lang/StringBuilder;");
    362     mv.visitMethodInsn(
    363         Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
    364     mv.visitMethodInsn(
    365         Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
    366     mv.visitInsn(Opcodes.RETURN);
    367     mv.visitMaxs(-1, -1);
    368   }
    369 
    370   /**
    371    * Generate a test with multiple invokedynamic bytecodes operating on the same parameters. These
    372    * invocations should each produce invoke-custom bytecodes with unique call site ids.
    373    */
    374   private void generateMethodTest8(ClassVisitor cv) {
    375     MethodVisitor mv =
    376         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test8", "()V", null, null);
    377     MethodType mt =
    378         MethodType.methodType(
    379             CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
    380     // These should be two distinct call sites and both invoke the
    381     // bootstrap method. An erroneous implementation might treat them
    382     // as the same call site because the handle arguments are the same.
    383     Handle bootstrap1 =
    384         new Handle(
    385             Opcodes.H_INVOKESTATIC,
    386             Type.getInternalName(InvokeCustom.class),
    387             "bsmLookupStatic",
    388             mt.toMethodDescriptorString(),
    389             false);
    390     mv.visitLdcInsn("First invokedynamic invocation");
    391     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
    392 
    393     Handle bootstrap2 =
    394         new Handle(
    395             Opcodes.H_INVOKESTATIC,
    396             Type.getInternalName(InvokeCustom.class),
    397             "bsmLookupStatic",
    398             mt.toMethodDescriptorString(),
    399             false);
    400     mv.visitLdcInsn("Second invokedynamic invocation");
    401     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap2);
    402 
    403     // Using same handle again creates a new call site so invokes the bootstrap method.
    404     mv.visitLdcInsn("Dupe first invokedynamic invocation");
    405     mv.visitInvokeDynamicInsn("targetMethodTest8", "(Ljava/lang/String;)V", bootstrap1);
    406     mv.visitInsn(Opcodes.RETURN);
    407     mv.visitMaxs(-1, -1);
    408   }
    409 
    410   /** Generate a test with different kinds of constant method handles. */
    411   private void generateMethodTest9(ClassVisitor cv) {
    412     MethodVisitor mv =
    413         cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test9", "()V", null, null);
    414     MethodType mt =
    415         MethodType.methodType(
    416             CallSite.class,
    417             MethodHandles.Lookup.class,
    418             String.class,
    419             MethodType.class,
    420             MethodHandle.class,
    421             MethodHandle.class,
    422             MethodHandle.class,
    423             MethodHandle.class,
    424             MethodHandle.class,
    425             MethodHandle.class,
    426             MethodHandle.class);
    427     String internalName = Type.getInternalName(InvokeCustom.class);
    428     Handle bootstrap =
    429         new Handle(
    430             Opcodes.H_INVOKESTATIC,
    431             internalName,
    432             "bsmLookupTest9",
    433             mt.toMethodDescriptorString(),
    434             false);
    435     Handle staticSetter =
    436         new Handle(Opcodes.H_GETSTATIC, internalName, "staticFieldTest9", "I", false);
    437     Handle staticGetter =
    438         new Handle(Opcodes.H_PUTSTATIC, internalName, "staticFieldTest9", "I", false);
    439     Handle setter = new Handle(Opcodes.H_GETFIELD, internalName, "fieldTest9", "F", false);
    440     Handle getter = new Handle(Opcodes.H_PUTFIELD, internalName, "fieldTest9", "F", false);
    441     Handle instanceInvoke =
    442         new Handle(Opcodes.H_INVOKEVIRTUAL, internalName, "helperMethodTest9", "()V", false);
    443     // H_INVOKESTATIC and H_INVOKESPECIAL are tested elsewhere.
    444     Handle constructor =
    445         new Handle(Opcodes.H_NEWINVOKESPECIAL, internalName, "<init>", "(I)V", false);
    446     Handle interfaceInvoke =
    447         new Handle(
    448             Opcodes.H_INVOKEINTERFACE, Type.getInternalName(Runnable.class), "run", "()V", true);
    449 
    450     mv.visitInvokeDynamicInsn(
    451         "targetMethodTest9",
    452         "()V",
    453         bootstrap,
    454         staticSetter,
    455         staticGetter,
    456         setter,
    457         getter,
    458         instanceInvoke,
    459         constructor,
    460         interfaceInvoke);
    461     mv.visitInsn(Opcodes.RETURN);
    462     mv.visitMaxs(-1, -1);
    463   }
    464 }
    465